Methods75
function addFlags(int $flags): bool| $flags | int | a bitmask of iEntity::FLAG_* to set |
| returns | bool | true if any of $flags was not already set |
Append a link.
function addLink(Psr\Link\LinkInterface $link): Psr\Link\LinkInterface| $link | LinkInterface | |
| returns | LinkInterface | $link, for chaining |
This function is called to 'delete' the entity.
function archive(): boolWE DON'T DELETE really the entity to prevent unresolvable reference.
Repository controller SHOULD NEVER delete the entity but instead "archive" it.| returns | bool | true if status became ARCHIVE |
Build (once per class, then cached in the static $ArrayProperties array) the list of every #[ArrayProperty] declared on this class, its parent classes, and the traits they use -- by reflection over class- and property-level attributes. Also builds this class's Schema as a side effect. Called automatically by every other method in this trait: rarely called directly.
static function ArrayProperties(): array| returns | array | \MMO\core\attributes\ArrayProperty} name => ArrayProperty, in schema-sort order |
| throws | | |
Person::ArrayProperties(); // ['name' => ArrayProperty(name: 'name', ...), 'age' => ArrayProperty(...)]
static function ArrayProperty(string $name): MMO\core\attributes\ArrayProperty| $name | string | |
| returns | ArrayProperty | the property's metadata (schema, getter, setter, ...) |
| throws | | if $name is not a declared #[ArrayProperty] |
Person::ArrayProperty('name')->schema['type']; // ['string']
Check data before it touches any entity: would an entity made from $data be valid? Nothing is created, changed or saved: the data is loaded into a throwaway instance, which is then validate()d.
static function CheckData(array $data, ?array &$errors = null): boolA key that is none of the entity's properties is an error too (`unknown property. did you mean [name]?`): loaded,
it would be ignored, and its value lost.
To check a change to an existing entity, pass its current values with the change on top.if (!Person::CheckData($input, $errors)) {return ['errors' => array_map(fn($e) => $e->message, $errors)];}
$people->createEntity($input);Person::CheckData([...$person->toArray(), ...$input], $errors); // a change to $person
Reset change tracking: clears iEntity::FLAG_CHANGED and forgets every recorded change.
function clearChanges(): voidCompare value
function compareTo(mixed $value): int|bool$person->compareTo($otherPerson); // via \MMO\core\objects\compare on both entities' toArray()
The error recorded for a value that could not be set at all (usually: it could not be converted to its property's type), in the same form as a validation error.
protected function conversionError(string $key, Throwable $e): MMO\core\schemas\ValidatorErrorfunction debugName(): stringRead a (possibly nested) value by json-path, e.g. 'address/city'. Unlike a plain get call, a path through a genuinely UNDECLARED array property still throws (it is a programming error, not a "missing value"); $default only kicks in for a structurally valid path with nothing at the end (e.g. a missing list index).
function get(string $path, mixed $default = null): mixed$person->get('address/city', 'unknown');
Read a single array property's current value, through its getter (a direct property, a path alias, or a method).
function getArrayProperty(string $key, &$isRef = null, bool $store = false): mixed| $key | string | the array property's name |
| $isRef = null | | |
| $store = false | bool | if true, use the property's store getter instead of its normal getter |
| returns | mixed | a reference to the value (a typed property that was never assigned returns null rather than throwing PHP's usual "must not be accessed before initialization" error) |
$person->getArrayProperty('name'); // 'Marc'
function getChanges(bool $asArray = false, bool $store = true): array- See
tArrayProperties::getArrayProperty) instead of its normal getter
| $asArray = false | bool | if true, a changed property that is itself an entity is recursively expanded to a plain array (via toArray(null, true, true)) instead of staying an object |
| $store = true | bool | if true, read each changed value through its store getter (see |
| returns | array | property name => its current value, for every property marked changed since the last clearChanges |
$person->getChanges(); // e.g. ['name' => 'Updated']
function getErrors(): array| returns | array | property name => coercion/validation errors recorded while setting values (see setArrayProperty) |
function getFlags(): int| returns | int | the raw bitmask of iEntity::FLAG_* currently set |
function getLabel(): string| returns | string | the entity's short name, per the schema's @label template (or its default label rule) |
function getLinks(): arrayGet a list of Reference that have a specific relationship.
function getLinksByRel(array|string $rels, bool $all = true): array| $rels | array | string | one or multiple rels seperated by space |
| $all = true | bool | if true get only the References with all the relationship. Else any Reference with at least one of the relationship. |
| returns | array | |
$entity->getLinksByRel('self');$entity->getLinksByRel(['edit', 'delete'], false); // any link tagged 'edit' OR 'delete'
function getMetadata(): array| returns | array | assembled from title/label/links/errors (only the non-empty ones), plus 'opacified' => true when isOpacified |
function getStatus(): MMO\core\entities\cStatusfunction getTitle(): string| returns | string | the entity's long name, per the schema's @title template (e.g. @title {lastname} {name}), falling back to getLabel if there is no @title or it renders empty |
function getView(): MMO\core\helpers\Uri|string|null| returns | Uri | string | null | View, rendered with this entity's own values if it's a path template (starts with '/') -- turned into an absolute Uri via \MMO\url(); returned as-is otherwise |
function hasChanged(string ...$keys): bool| ...$keys | string | |
| returns | bool | true if any of $keys is currently marked changed |
function hasFlags(int $flags, bool $strict = false): bool| $flags | int | a bitmask of iEntity::FLAG_* to test |
| $strict = false | bool | if false (the default), true if AT LEAST ONE of $flags is set; if true, true only if EVERY one of $flags is set |
| returns | bool | |
$entity->hasFlags(iEntity::FLAG_CREATED | iEntity::FLAG_CHANGED); // true if created OR changed$entity->hasFlags(iEntity::FLAG_CREATED | iEntity::FLAG_CHANGED, true); // true only if BOTH
function hasStatusChanged(): boolThe hidden property (ArrayProperty::isHidden) a json-path goes through, known from the declared types alone: of this class, or of a class a property on the way may hold -- every class of its type (owner/passhash when owner is an iClient, and User is one). A query tells the values it filters or sorts on through what it returns: an API query cannot use it (Route::extractQuery). What a property of no class holds (a plain array, mixed) is not looked into.
static function HiddenPath(array|string $path): ?string| $path | array | string | |
| returns | null | string | the path to the hidden property (owner/passhash), or null if $path goes through none |
User::HiddenPath('passhash'); // 'passhash'File::HiddenPath('owner/passhash'); // 'owner/passhash': its owner may be a UserUser::HiddenPath('roles/0/label'); // null
Temporarily suppress markChanged (used e.g. by loadFromStore, so hydrating an entity from storage doesn't get recorded as an edit).
function ignoreChanges(bool $value = true): boolfunction isActive(): bool| returns | bool | true if getStatus is cStatus::ACTIVE (the default status) |
function isArchive(): boolfunction isArrayPropertySet(string $key, bool $store = false): bool- See
iArrayProperties value is considered set if it is itself non-isEmpty)
| $key | string | the array property's name |
| $store = false | bool | if true, read through the store getter instead of the normal getter |
| returns | bool | true if the property's current value differs from its schema default (a nested |
(new Person())->isArrayPropertySet('name') === false // default value ('') is still in place
function isChanged(): boolfunction isCreated(): bool| returns | bool | true if this entity was just constructed (set in the constructor, never cleared automatically) |
function isDraft(): boolfunction isEmpty(): bool| returns | bool | true if every storable property still holds its schema default value (i.e. nothing meaningful has been set on this instance yet) |
(new Person())->isEmpty() === true(new Person())->loadArray(['name' => 'Marc'])->isEmpty() === false
function isFinal(): boolfunction isHiddenPath(string $path): bool| $path | string | a property's name, or a json-path through the values of this object (owner/passhash) |
| returns | bool | true if $path goes through a hidden property (ArrayProperty::isHidden), of this object or of one met on the way: what it holds is never exported (toArray) |
$user->isHiddenPath('passhash'); // true: `@hidden`$file->isHiddenPath('owner/passhash'); // true when its owner is a User$file->isHiddenPath('owner/username'); // false
function isOpacified(): bool| returns | bool | true if opacify(true) was called: toArray then exposes only 'uid'/'metadata' |
function isPending(): boolfunction isRemoved(): boolCompare entities using unique fields (@unique), to find if they represent the same entity
function isSimilar(self|array $value): bool| $value | self | array | another entity, or a raw array of property values |
| returns | bool | true if $value matches this entity on every key of at least one of the schema's @unique property groups (see Schema::getUniquePropertyGroups) NOTE: Entity::isSimilar (the base every concrete entity normally extends) layers an additional shortcut on top of this: two entities with the same non-null 'id' are ALWAYS similar, even with no @unique group defined at all, or one that doesn't match. |
// class UniqueEntity extends Entity {...} with docblock `@unique email`
(new UniqueEntity(['email' => 'a@x.com']))->isSimilar(['email' => 'a@x.com']); // true: matches on 'email'
(new UniqueEntity(['email' => 'a@x.com']))->isSimilar(['email' => 'b@x.com']); // false: no unique group matches
Tells if an entity has enough information to be unique (at least one unique group has no null)
function isUnique(): bool| returns | bool | true if every key of at least one @unique property group is currently non-null. NOTE: the check is a strict === null, so a non-nullable property (e.g. string $email = '') already counts as "set" at its schema default -- this only catches a genuinely nullable, still-unset field. |
(new UniqueEntity(['email' => 'a@x.com']))->isUnique(); // true(new UniqueEntity())->isUnique(); // true too: $email's default is '', not null
function isValid(): bool| returns | bool | true if no property currently has a recorded error |
function isValidated(): boolGet a list of all the classes and their properties that are using references to this class.
static function ListClassReferences(bool $onlyStorable = false): array| $onlyStorable = false | bool | if true, will only list properties that are storable. This is useful for getting all the entities that might make a reference in the repository to this class |
| returns | array | (classname => [name => ArrayProperty]) |
// if PersonWithFriend has: #[ArrayProperty] protected ?Person $friend;
Person::ListClassReferences(); // [PersonWithFriend::class => ['friend' => ArrayProperty(...)]]
Set every entry of $data as an array property, through setArrayProperty (so each value is coerced by its property's schema). Unknown keys are dropped with a logged \MMO\error() rather than throwing.
function loadArray(array $data, array $coerceOptions = [], bool $fromStore = false): static| $data | array | key => value, as produced by toArray / store |
| $coerceOptions = [] | array | options forwarded to the schema coercer |
| $fromStore = false | bool | if true, readonly properties can still be set (needed to reload a stored/persisted record, whose readonly properties were legitimately set once at store time) |
| returns | static | $this, for chaining |
$person = (new Person())->loadArray(['name' => 'Marc', 'age' => 42]);
Load $data as this entity's initial state (e.g. when hydrating from a repository), WITHOUT marking any of it as a "change" (ignoreChanges): this is the entity's starting point, not an edit.
function loadFromStore(array $data): voidMark one or multiple key as changed
function markChanged(string ...$keys): bool\MMO\core\interfaces\iMutationObserver::mutate -- besides the normal tArrayProperties::mutate behavior, walks up $path to find the nearest enclosing aEntity (the mutation may have happened deep inside a nested array/object property) and marks ITS top-level property changed, not just $this's.
function mutate(string $type, array $path, mixed $value, mixed $oldValue): bool\ArrayAccess::offsetExists -- tells whether $key is a *declared* #[ArrayProperty], not whether it currently holds a non-default value (use isArrayPropertySet for that).
function offsetExists(mixed $key): boolisset($person['name']) === trueisset($person['does-not-exist']) === false
\ArrayAccess::offsetGet
function offsetGet(mixed $key): mixed$person['name']; // 'Marc'
\ArrayAccess::offsetSet
function offsetSet(mixed $key, mixed $value): void$person['age'] = 43;
\ArrayAccess::offsetUnset -- equivalent to setting the property to null (its coerced value depends on the property's schema: e.g. a non-nullable string ends up as '', not literally null -- see setArrayProperty)
function offsetUnset(mixed $key): voidunset($person['name']); // $person['name'] becomes '' (the schema's coerced form of null)
Hide this entity's properties from toArray output (typically used to redact an entity the current viewer isn't allowed to see the details of, while still letting it be referenced by uid/metadata).
function opacify(bool $value = true): void$entity->opacify(); $entity->toArray(); // ['uid' => ..., 'metadata' => [...]] only
Append a value to a (possibly nested) list property by json-path.
function push(string $path, mixed $value): void$person->push('tags', 'vip');
function removeFlags(int $flags): bool| $flags | int | a bitmask of iEntity::FLAG_* to clear |
| returns | bool | true if any of $flags was actually set (and is now cleared) |
Remove a link (by identity). A no-op if $link isn't currently one of this entity's links.
function removeLink(Psr\Link\LinkInterface $link): void| $link | LinkInterface | |
| returns | void | |
Remove this entity from the repository once MMO\commit() is called WARNING: This will permanently remove the entity.
function removePermanently(): voidThe JSON-Schema describing every #[ArrayProperty] of this class (built once and cached by ArrayProperties).
static function Schema(): MMO\core\schemas\SchemaPerson::Schema(); // a Schema instance with 'name' (string) and 'age' (integer) properties
static function SearchProperty(string $name): MMO\core\attributes\SearchPropertyPerson::SearchProperty('label')->name === 'label'
Write a (possibly nested) value by json-path, creating intermediate structure as needed.
function set(string $path, mixed $value): void$person->set('address/city', 'Paris');
setArrayProperty($key, $value, $coerceOptions, $ignoreReadonly)
aEntity.php:602Same as tArrayProperties::setArrayProperty, plus: any coercion/validation failure is caught and recorded in getErrors (making the entity isValid()-false) instead of propagating as an exception, and a successful set through a direct property getter/setter is automatically markChanged.
function setArrayProperty(
mixed $key,
mixed $value,
array $coerceOptions = [],
bool $ignoreReadonly = false
): bool| $key | mixed | |
| $value | mixed | |
| $coerceOptions = [] | array | |
| $ignoreReadonly = false | bool | |
| returns | bool | false if the value was rejected (an error was recorded) or nothing changed |
| throws | | this one is NOT caught -- it means the property itself is misconfigured, a bug to fix rather than a bad input value to report |
$entity->setArrayProperty('name', ['not', 'a', 'string']); // false, and getErrors()['name'] is set
setArrayPropertyOffset($key, $offset, $value, $coerceOptions)
aEntity.php:651Same as tArrayProperties::setArrayPropertyOffset, plus: when pushing (offset === null) a new entity onto a list property, if an existing item is isSimilar to the new value, that item's offset is reused instead of appending a duplicate -- so pushing "the same" entity twice updates it in place.
function setArrayPropertyOffset(
string $key,
string|int|null $offset,
mixed $value,
array $coerceOptions = []
): boolReplace every link with $links.
function setLinks(array $links): boolfunction setStatus(MMO\core\entities\cStatus $status): bool| $status | cStatus | |
| returns | bool | true if the status actually changed (also markChanged('status')) |
Export all array properties with store set to true.
function store(): arrayUsed to store the entity in a database$person->store(); // ['name' => 'Marc', 'age' => 42] -- every property here is storable by default
function test(MMO\core\interfaces\iFilter $filter): bool| $filter | iFilter | |
| returns | bool | true if this entity matches $filter |
Same as tArrayProperties::toArray, except an isOpacified entity always exports only its 'uid' and 'metadata' (its other properties are hidden from output).
function toArray(?array $fields = null, bool $flatten = false, bool $recursive = false): arrayfunction toLink(array $attributes = []): MMO\core\server\Link| $attributes = [] | array | extra Link attributes, merged with 'label'/'title' derived from the entity |
| returns | Link | a Link pointing at this entity's uid |
\MMO\core\interfaces\iMutationObserver::mutate -- reacts to a mutation that happened deep inside one of this object's array properties (e.g. an item of a nested array/object was changed through its own reference) by re-running that top-level property's setter, so any custom coercion/validation logic still applies.
function traitMutate(string $type, array $path, mixed $value, mixed $oldValue): bool| $type | string | the kind of mutation ('+', '-', '=', ...) |
| $path | array | the property path that changed, e.g. ['tags', 0] |
| $value | mixed | the new value |
| $oldValue | mixed | the previous value |
| returns | bool | true (this trait never rejects a mutation) |
Set array property
function traitSetArrayProperty(
string $key,
mixed $value,
array $coerceOptions = [],
bool $fromStore = false
): bool| $key | string | the array property's name |
| $value | mixed | the value to set |
| $coerceOptions = [] | array | options to pass to the schema's coercer |
| $fromStore = false | bool | if true, won't throw an exception for readonly properties if it is an actual storable property |
| returns | bool | true if the value actually changed (a set to an equal value returns false and does nothing) |
| throws | | when a readonly properties is set. |
$person->setArrayProperty('age', 43); // true: value changed$person->setArrayProperty('age', 43); // false the second time: already 43, no-op
Set/push a value into an array property
function traitSetArrayPropertyOffset(
string $key,
string|int|null $offset,
mixed $value,
array $coerceOptions = []
): bool| $key | string | the array property's name |
| $offset | string | int | null | the offset at wich to set the value |
| $value | mixed | if null (and $offset is not null), unset the offset instead of setting it |
| $coerceOptions = [] | array | the coerce options |
| returns | bool | true if the value has changed |
$person->setArrayPropertyOffset('tags', null, 'x'); // pushes 'x' onto the end of the list$person->setArrayPropertyOffset('tags', 0, 'z'); // sets index 0 to 'z'$person->setArrayPropertyOffset('tags', 0, null); // unsets index 0
Export fields to an array.
function traitToArray(?array $fields = null, bool $flatten = false, bool $recursive = false): arrayPath in fields are allowed, so field 'a/b/c' will look for value at $this['a']['b']['c']
A hidden property ([`ArrayProperty::isHidden`](https://mmoframework.fr/documentation/mmo.core/php/attributes/ArrayProperty.md#arrayproperty-ishidden)) is never exported: not even named in $fields, nor further along a
path (`owner/passhash`).| $fields = null | null | array | if null, export all non-hidden array properties. |
| $flatten = false | bool | if true and a field is a path, the structure is not created to set the value |
| $recursive = false | bool | if true, any nested iArrayProperties value is itself recursively converted with toArray(null, true, true); if false (the default), a nested value is left as-is (e.g. still an object, not a plain array) |
| returns | array | |
| throws | | |
$person->toArray(); // ['name' => 'Marc', 'age' => 42]$personWithFriend->toArray(); // ['name' => 'Marc', 'friend' => Person{...}, 'tags' => [...]] -- $friend stays an object$personWithFriend->toArray(null, false, true); // same, but 'friend' => ['name' => ..., 'age' => ...] (recursively expanded)$personWithFriend->toArray(['friend/name']); // ['friend' => ['name' => 'Alice']] -- path fields rebuild the nesting$personWithFriend->toArray(['friend/name'], true); // ['friend/name' => 'Alice'] -- $flatten=true keeps the path as a single key$user->toArray(['username', 'passhash']); // ['username' => 'marc'] -- passhash is `@hidden`
Validate this entity: its current values against its own tArrayProperties::Schema, plus every value that could not even be set -- one that could not be converted to its property's type never reaches the property, which keeps its previous value, so the schema alone would find nothing wrong. Runs in every mode, unlike the check made as each value is set (setArrayProperty), which only runs in dev mode.
function validate(?array &$errors = null): bool$person->set('age', 'abc');
$person->validate($errors); // false: $errors['age']->message is 'not a valid [null|integer].'
static function View(): ?string| returns | null | string | this class's declared front-end view ($view in its schema, e.g. @view /users/{id}), if any |
function __construct(array $data = [])