initializeEntity($dataset); } #[Ignore] public function initializeEntity(iterable|null $dataset = null) : void { # Attributs can attach themselves on events thrown by this trait $this->entityRegisterAttributes(); $this->datasetHandler = new DatasetHandler(static::resolveEntity(), $this->entityStrictFieldsDeclaration); if ($dataset) { $this->fromArray($dataset); } $this->resetVirtualProperties(); } #[Ignore] public function entityRegisterAttributes() : void { foreach($this->resolveEntity()->reflectedClass->getProperties(true) as $field => $property) { foreach($property->attributes as $tag) { if ( $tag->object instanceof RegisterEntityEventInterface ) { $tag->object->attach($this, $field); } } } } #[Ignore] public function entityFillFromDataset(iterable $dataset, bool $overwriteDataset = false) : self { $properties = $this->resolveEntity()->reflectedClass->getProperties(true); $loaded = $this->isLoaded(); $generator = $this->datasetHandler->push($dataset); foreach($generator as $field => $value) { foreach($properties[$field]->attributes as $tag) { if ( $tag->object instanceof EntityValueModifier ) { try { $value = $tag->object->push($value, $properties[$field]); } catch(\Throwable $ex) { throw new \LogicException( message: sprintf("An error occured trying to push an EntityValueModifier results : %s", $ex->getMessage()), code: $ex->getCode(), previous: $ex, ); } } } $this->$field = $value; } $this->eventExecute(Event\Entity\DatasetFill::class, $this); $this->entityDatasetUnmatchedFields = $generator->getReturn(); # Keeping original data to diff on UPDATE query if ( ! $loaded ) { $this->entityLoadedDataset = array_change_key_case(is_array($dataset) ? $dataset : iterator_to_array($dataset), \CASE_LOWER); } elseif ($overwriteDataset) { $this->entityLoadedDataset = array_change_key_case(is_array($dataset) ? $dataset : iterator_to_array($dataset), \CASE_LOWER) + $this->entityLoadedDataset; } return $this; } #[Ignore] public function entityGetDataset(bool $includeRelations = false, bool $returnSource = false, bool $rewriteValue = true) : array { if ( $returnSource ) { return $this->entityLoadedDataset; } return iterator_to_array($this->entityYieldDataset($includeRelations, $rewriteValue)); } #[Ignore] protected function entityYieldDataset(bool $includeRelations = false, bool $rewriteValue = true) : \Generator { foreach($this->datasetHandler->pull($this) as $field => $value) { yield $field => $rewriteValue ? static::repository()->adapter->adapter()->writableValue($value) : $value; } if ($includeRelations) { foreach($this->datasetHandler->pullRelation($this) as $field => $object) { yield $field => $object; } } } #[Ignore] public function entityGetDiffDataset() : array { $return = []; foreach($this->datasetHandler->pull($this, false) as $field => $value) { $return[$field] = static::repository()->adapter->adapter()->writableValue($value); } return $return; } #[Ignore] public function resetVirtualProperties() : self { foreach($this->resolveEntity()->reflectedClass->getProperties(true) as $field => $property) { foreach($property->attributes as $tag) { if ( $tag->object instanceof ResettablePropertyInterface ) { unset($this->$field); } } } return $this; } #[Ignore] public function fromArray(iterable|EntityInterface $dataset) : static { if ($dataset instanceof EntityInterface) { $dataset = $dataset->toArray(); } return $this->entityFillFromDataset($dataset); } #[Ignore] public function toArray($includeRelations = false, array $filterFields = [], bool $rewriteValue = true) : array { $dataset = $this->entityGetDataset($includeRelations, false, $rewriteValue); return $filterFields ? array_intersect_key($dataset, array_flip($filterFields)) : $dataset; } #[Ignore] public function toCollection() : EntityCollection { return static::entityCollection([ $this ]); } #[Ignore] public function isLoaded() : bool { if (empty($this->entityLoadedDataset)) { return false; } if ( null === $pkField = $this->resolveEntity()->getPrimaryKeyField() ) { if ( null !== $compoundKeyFields = $this->resolveEntity()->getCompoundKeyFields() ) { $loaded = false; foreach ($compoundKeyFields->column as $column) { $field = $this->resolveEntity()->field($column); if (! $field->allowsNull() ) { $loaded |= isset($this->{$field->name}); } } return $loaded; }; } else { $key = key($pkField); return isset($this->$key); } throw new Exception\EntityPrimaryKeyUnknown(sprintf("Entity %s has no field containing attributes 'primary_key'", static::class)); } #[Ignore] public function __get(string $name) : mixed { $relation = new Repository\RelationBuilder($this); if ( false !== $data = $relation->searchRelation($name) ) { return $this->$name = $data; } throw new \Exception(sprintf("[%s] - Undefined variable: %s", static::class, $name)); } #[Ignore] public function __isset(string $name) : bool { $rel = static::resolveEntity()->searchFieldAnnotation($name, [ Attribute\Property\Relation::class ]); if ( $rel && $this->isLoaded() ) { return true; } if (isset($this->$name)) { return true; } # Unexisting variable if (! new \ReflectionClass($this)->hasProperty($name)) { return false; } # Handling undefined 'nullable' return new \ReflectionProperty($this, $name)->isInitialized($this); } #[Ignore] public function __sleep() { return array_merge(array_keys($this->resolveEntity()->fieldList()), [ 'entityLoadedDataset' ] ); } #[Ignore] public function __wakeup() { $this->resetVirtualProperties(); } #[Ignore] public function __clone() { if ( null !== $pkField = $this->resolveEntity()->getPrimaryKeyField($this) ) { $key = key($pkField); unset($this->$key); } } #[Ignore] public function jsonSerialize() : mixed { # Allows overridding of this method return $this->_jsonSerialize(); } #[Ignore] public function _jsonSerialize() : mixed { $resolver = static::resolveEntity(); $objectAttribute = $resolver->getAttributeImplementing(JsonSerialize::class); $dataset = []; foreach($this->entityYieldDataset($objectAttribute->includeRelations ?? true, false, false) as $key => $value) { $field = $resolver->searchField($key, EntityResolver::KEY_COLUMN_NAME); # ?: $resolver->relation($key, false); if ($field) { $jsonSerialize = $field->getAttribute(\Ulmus\Attribute\Property\JsonSerialize::class); if ($jsonSerialize) { # Should we ignore the field ? if ($jsonSerialize->object->ignoreField) { continue; } } } if (is_object($value) && ($value instanceof \JsonSerializable) ) { $dataset[$key] = $value->jsonSerialize(); } else { $dataset[$key] = $value; } } return $dataset; } #[Ignore] public static function resolveEntity() : EntityResolver { return Ulmus::resolveEntity(static::class); } #[Ignore] public static function repository(string $alias = Repository::DEFAULT_ALIAS, ?ConnectionAdapter $adapter = null) : Repository { return Ulmus::repository(static::class, $alias, $adapter); } #[Ignore] public static function entityCollection(...$arguments) : EntityCollection { $collection = new EntityCollection(...$arguments); $collection->entityClass = static::class; return $collection; } #[Ignore] public static function queryBuilder() : QueryBuilderInterface { return Ulmus::queryBuilder(static::class); } #[Ignore] public static function field($name, null|string|false $alias = Repository::DEFAULT_ALIAS, bool $select = false) : EntityField { $default = ( $alias === false ? '' : static::repository()::DEFAULT_ALIAS ); # bw compatibility, to be deprecated $alias = $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : $default; return new EntityField( name: $name, entityClass: static::class, alias: $alias, entityResolver: Ulmus::resolveEntity(static::class), select: $select ); } #[Ignore] public static function fields(array $fields, null|string|false $alias = Repository::DEFAULT_ALIAS, string $separator = ', ', bool $select = false) : string { return implode($separator, array_map(fn($e) => static::field($e, $alias, $select), $fields)); } #[Ignore] public static function searchRequest(...$arguments) : SearchRequest\SearchRequestInterface { return new /* #[SearchRequest\Attribute\SearchRequestParameter(YourEntityClass::class)] */ class(... $arguments) extends SearchRequest\SearchRequest { # Define searchable properties here, some ex: # #[SearchParameter(method: SearchMethodEnum::Where)] # public ? string $username = null; # #[SearchParameter(method: SearchMethodEnum::Where, toggle: true)] # public ? bool $hidden = null; # #[SearchParameter(method: SearchMethodEnum::Like)] # public ? string $word = null; }; } }