Skip to content

Owner

phil/doctrine-behaviours
TypeKindRole
OwnerIdvalueA stable identifier of at most 255 bytes, without @
Contract\OwnableinterfaceownerId(), claimOwner(), transferOwner()
OwnableEntitytraitThe column owner_id, 255 characters, nullable
OwnerListenerlistener: prePersistGives a new record the owner the resolver names
Contract\OwnerResolverinterfacecurrent(): ?OwnerId, who new records belong to now
FixedOwnerOwnerResolverAlways the same owner, or nobody
OwnerEraserserviceFinds and replaces an owner in every owned table
Exception\InvalidOwnerexceptionWhat cannot be an owner

An owner is who a record belongs to, where blame is who last touched it. It is set once, as the record is persisted, unless it was given one, and no update changes it. Handing a record over is the application's call, transferOwner().

An identifier, not an e-mail address

An owner is copied into every row the person owns and kept for as long as the row, so OwnerId refuses anything that holds an @ as it is built. The error never repeats the value. A resolver names people by something stable that is no personal information, such as a UUID.

new OwnerId('0192f0a0-0000-7000-8000-000000000001');   // fine
new OwnerId('ada@example.com');                        // InvalidOwner

There is deliberately no fallback on an account's login: SecurityOwnerResolver, in the bundle, requires a method to be named. A row that was written with an @ by other means makes ownerId() throw when it is read.

Access and erasure requests

OwnerEraser answers them over every Ownable entity that maps the field ownerId. It works on the tables directly, so soft-deleted and deactivated rows are found too, and no listener runs. tables() lists every owned table, found or not, for a report that names each store.

$eraser->find($owner);                             // ['article' => 12, 'page' => 3]
$eraser->replace($owner, new OwnerId('former-81'));
$eraser->replace($owner);                          // nobody owns them any more
$eraser->tables();                                 // ['article', 'page']