From f3be11a59021f22c761bcac9fad1b026f78ba0a4 Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Tue, 28 May 2024 08:59:12 -0400 Subject: [PATCH] - WIP on notes 2.x --- src/Annotation/Classes/Collation.php | 7 - src/Annotation/Classes/Method.php | 7 - src/Annotation/Classes/Table.php | 27 --- src/Annotation/Property/Field.php | 33 --- src/Annotation/Property/Field/Bigint.php | 11 - src/Annotation/Property/Field/Blob.php | 11 - src/Annotation/Property/Field/CreatedAt.php | 13 -- src/Annotation/Property/Field/Date.php | 11 - src/Annotation/Property/Field/Datetime.php | 11 - src/Annotation/Property/Field/ForeignKey.php | 20 -- src/Annotation/Property/Field/Id.php | 15 -- src/Annotation/Property/Field/Longblob.php | 11 - src/Annotation/Property/Field/Longtext.php | 11 - src/Annotation/Property/Field/Mediumblob.php | 11 - src/Annotation/Property/Field/Mediumtext.php | 11 - src/Annotation/Property/Field/PrimaryKey.php | 15 -- src/Annotation/Property/Field/Text.php | 11 - src/Annotation/Property/Field/Time.php | 11 - src/Annotation/Property/Field/Tinyblob.php | 11 - src/Annotation/Property/Field/Tinyint.php | 11 - src/Annotation/Property/Field/UpdatedAt.php | 14 -- src/Annotation/Property/Filter.php | 15 -- src/Annotation/Property/FilterJoin.php | 15 -- src/Annotation/Property/GroupBy.php | 15 -- src/Annotation/Property/Having.php | 5 - src/Annotation/Property/Join.php | 31 --- src/Annotation/Property/On.php | 5 - src/Annotation/Property/OrWhere.php | 14 -- src/Annotation/Property/OrderBy.php | 21 -- src/Annotation/Property/Relation.php | 88 -------- src/Annotation/Property/Relation/Ignore.php | 5 - src/Annotation/Property/Virtual.php | 19 -- src/Annotation/Property/Where.php | 30 --- src/Annotation/Property/WithJoin.php | 15 -- src/Attribute/Property/Join.php | 2 +- src/Attribute/Property/Relation.php | 2 +- .../Property/ResettablePropertyInterface.php | 8 + src/Attribute/Property/Virtual.php | 2 +- src/Common/EntityResolver.php | 88 ++------ src/Entity/DatasetHandler.php | 153 +++++++++++++ src/EntityTrait.php | 213 +++++------------- src/Repository/RelationBuilder.php | 61 +++-- 42 files changed, 270 insertions(+), 820 deletions(-) delete mode 100644 src/Annotation/Classes/Collation.php delete mode 100644 src/Annotation/Classes/Method.php delete mode 100644 src/Annotation/Classes/Table.php delete mode 100644 src/Annotation/Property/Field.php delete mode 100644 src/Annotation/Property/Field/Bigint.php delete mode 100644 src/Annotation/Property/Field/Blob.php delete mode 100644 src/Annotation/Property/Field/CreatedAt.php delete mode 100644 src/Annotation/Property/Field/Date.php delete mode 100644 src/Annotation/Property/Field/Datetime.php delete mode 100644 src/Annotation/Property/Field/ForeignKey.php delete mode 100644 src/Annotation/Property/Field/Id.php delete mode 100644 src/Annotation/Property/Field/Longblob.php delete mode 100644 src/Annotation/Property/Field/Longtext.php delete mode 100644 src/Annotation/Property/Field/Mediumblob.php delete mode 100644 src/Annotation/Property/Field/Mediumtext.php delete mode 100644 src/Annotation/Property/Field/PrimaryKey.php delete mode 100644 src/Annotation/Property/Field/Text.php delete mode 100644 src/Annotation/Property/Field/Time.php delete mode 100644 src/Annotation/Property/Field/Tinyblob.php delete mode 100644 src/Annotation/Property/Field/Tinyint.php delete mode 100644 src/Annotation/Property/Field/UpdatedAt.php delete mode 100644 src/Annotation/Property/Filter.php delete mode 100644 src/Annotation/Property/FilterJoin.php delete mode 100644 src/Annotation/Property/GroupBy.php delete mode 100644 src/Annotation/Property/Having.php delete mode 100644 src/Annotation/Property/Join.php delete mode 100644 src/Annotation/Property/On.php delete mode 100644 src/Annotation/Property/OrWhere.php delete mode 100644 src/Annotation/Property/OrderBy.php delete mode 100644 src/Annotation/Property/Relation.php delete mode 100644 src/Annotation/Property/Relation/Ignore.php delete mode 100644 src/Annotation/Property/Virtual.php delete mode 100644 src/Annotation/Property/Where.php delete mode 100644 src/Annotation/Property/WithJoin.php create mode 100644 src/Attribute/Property/ResettablePropertyInterface.php create mode 100644 src/Entity/DatasetHandler.php diff --git a/src/Annotation/Classes/Collation.php b/src/Annotation/Classes/Collation.php deleted file mode 100644 index acb3fb4..0000000 --- a/src/Annotation/Classes/Collation.php +++ /dev/null @@ -1,7 +0,0 @@ -name = $name; - } - - if ( $engine !== null ) { - $this->engine = $engine; - } - } -} diff --git a/src/Annotation/Property/Field.php b/src/Annotation/Property/Field.php deleted file mode 100644 index 7b8dea0..0000000 --- a/src/Annotation/Property/Field.php +++ /dev/null @@ -1,33 +0,0 @@ -type = $type; - } - - if ( $length !== null ) { - $this->length = $length; - } - } -} diff --git a/src/Annotation/Property/Field/Bigint.php b/src/Annotation/Property/Field/Bigint.php deleted file mode 100644 index 01b49ac..0000000 --- a/src/Annotation/Property/Field/Bigint.php +++ /dev/null @@ -1,11 +0,0 @@ -nullable = false; - $this->type = "timestamp"; - $this->attributes['default'] = "CURRENT_TIMESTAMP"; - } -} diff --git a/src/Annotation/Property/Field/Date.php b/src/Annotation/Property/Field/Date.php deleted file mode 100644 index 2bbf061..0000000 --- a/src/Annotation/Property/Field/Date.php +++ /dev/null @@ -1,11 +0,0 @@ -nullable); - $this->attributes['primary_key'] = false; - $this->attributes['auto_increment'] = false; - } - -} diff --git a/src/Annotation/Property/Field/Id.php b/src/Annotation/Property/Field/Id.php deleted file mode 100644 index 5824909..0000000 --- a/src/Annotation/Property/Field/Id.php +++ /dev/null @@ -1,15 +0,0 @@ -attributes['unsigned'] = true; - $this->attributes['auto_increment'] = true; - - parent::__construct('bigint'); - } - -} diff --git a/src/Annotation/Property/Field/Longblob.php b/src/Annotation/Property/Field/Longblob.php deleted file mode 100644 index ef29876..0000000 --- a/src/Annotation/Property/Field/Longblob.php +++ /dev/null @@ -1,11 +0,0 @@ -nullable = false; - $this->attributes['primary_key'] = true; - - parent::__construct($type, $length); - } - -} diff --git a/src/Annotation/Property/Field/Text.php b/src/Annotation/Property/Field/Text.php deleted file mode 100644 index fb7843f..0000000 --- a/src/Annotation/Property/Field/Text.php +++ /dev/null @@ -1,11 +0,0 @@ -nullable = true; - $this->type = "timestamp"; - $this->attributes['update'] = "CURRENT_TIMESTAMP"; - $this->attributes['default'] = null; - } -} diff --git a/src/Annotation/Property/Filter.php b/src/Annotation/Property/Filter.php deleted file mode 100644 index ee8c048..0000000 --- a/src/Annotation/Property/Filter.php +++ /dev/null @@ -1,15 +0,0 @@ -method = $method; - } - } -} \ No newline at end of file diff --git a/src/Annotation/Property/FilterJoin.php b/src/Annotation/Property/FilterJoin.php deleted file mode 100644 index 2f7bac9..0000000 --- a/src/Annotation/Property/FilterJoin.php +++ /dev/null @@ -1,15 +0,0 @@ -method = $method; - } - } -} \ No newline at end of file diff --git a/src/Annotation/Property/GroupBy.php b/src/Annotation/Property/GroupBy.php deleted file mode 100644 index e556a1d..0000000 --- a/src/Annotation/Property/GroupBy.php +++ /dev/null @@ -1,15 +0,0 @@ -fields = $field; - } - } -} diff --git a/src/Annotation/Property/Having.php b/src/Annotation/Property/Having.php deleted file mode 100644 index d2cb5a1..0000000 --- a/src/Annotation/Property/Having.php +++ /dev/null @@ -1,5 +0,0 @@ -type = $type; - } - - if ($key !== null) { - $this->key = $key; - } - - if ($foreignKey !== null) { - $this->foreignKey = $foreignKey; - } - } -} diff --git a/src/Annotation/Property/On.php b/src/Annotation/Property/On.php deleted file mode 100644 index fd7f9b0..0000000 --- a/src/Annotation/Property/On.php +++ /dev/null @@ -1,5 +0,0 @@ -field = $field; - } - - if ( $order !== null ) { - $this->order = $order; - } - } -} diff --git a/src/Annotation/Property/Relation.php b/src/Annotation/Property/Relation.php deleted file mode 100644 index 1749670..0000000 --- a/src/Annotation/Property/Relation.php +++ /dev/null @@ -1,88 +0,0 @@ -type = $type; - } - } - - public function entity() { - try { - $e = $this->entity; - } catch (\Throwable $ex) { - throw new \Exception("Your @Relation annotation seems to be missing an `entity` entry."); - } - - return new $e(); - } - - public function bridge() { - $e = $this->bridge; - - return new $e(); - } - - public function normalizeType() : string - { - return strtolower(str_replace(['-', '_', ' '], '', $this->type)); - } - - public function isOneToOne() : bool - { - return $this->normalizeType() === 'onetoone'; - } - - public function isOneToMany() : bool - { - return $this->normalizeType() === 'onetomany'; - } - - public function isManyToMany() : bool - { - return $this->normalizeType() === 'manytomany'; - } - - public function function() : string - { - if ($this->function) { - return $this->function; - } - elseif ($this->isOneToOne()) { - return 'load'; - } - - return 'loadAll'; - } - - public function hasBridge() : bool - { - return isset($this->bridge); - } -} diff --git a/src/Annotation/Property/Relation/Ignore.php b/src/Annotation/Property/Relation/Ignore.php deleted file mode 100644 index 541fe3c..0000000 --- a/src/Annotation/Property/Relation/Ignore.php +++ /dev/null @@ -1,5 +0,0 @@ -closure = $closure; - } - } -} diff --git a/src/Annotation/Property/Where.php b/src/Annotation/Property/Where.php deleted file mode 100644 index a712a43..0000000 --- a/src/Annotation/Property/Where.php +++ /dev/null @@ -1,30 +0,0 @@ -field = $field; - } - - if ( $value !== null ) { - $this->value = $value; - } - - $this->operator = $operator !== null ? $operator : Query\Where::OPERATOR_EQUAL; - $this->condition = $condition !== null ? $condition : Query\Where::CONDITION_AND; - } -} diff --git a/src/Annotation/Property/WithJoin.php b/src/Annotation/Property/WithJoin.php deleted file mode 100644 index 035fea6..0000000 --- a/src/Annotation/Property/WithJoin.php +++ /dev/null @@ -1,15 +0,0 @@ -joins = (array)$joins; - } - } -} diff --git a/src/Attribute/Property/Join.php b/src/Attribute/Property/Join.php index 9bc8ae5..6d825fd 100644 --- a/src/Attribute/Property/Join.php +++ b/src/Attribute/Property/Join.php @@ -5,7 +5,7 @@ namespace Ulmus\Attribute\Property; use Ulmus\Attribute\Attribute; #[\Attribute] -class Join { +class Join implements ResettablePropertyInterface { public function __construct( public string $type, public null|string|\Stringable|array $key = null, diff --git a/src/Attribute/Property/Relation.php b/src/Attribute/Property/Relation.php index 58e6f0d..083bbf4 100644 --- a/src/Attribute/Property/Relation.php +++ b/src/Attribute/Property/Relation.php @@ -5,7 +5,7 @@ namespace Ulmus\Attribute\Property; use Ulmus\Attribute\Attribute; #[\Attribute(\Attribute::TARGET_PROPERTY)] -class Relation { +class Relation implements ResettablePropertyInterface { public function __construct( public Relation\RelationTypeEnum|string $type, public \Stringable|string|array $key = "", diff --git a/src/Attribute/Property/ResettablePropertyInterface.php b/src/Attribute/Property/ResettablePropertyInterface.php new file mode 100644 index 0000000..650cade --- /dev/null +++ b/src/Attribute/Property/ResettablePropertyInterface.php @@ -0,0 +1,8 @@ +reflectedClass = ObjectReflection::fromClass($entityClass, $cache)->reflectClass(); } - public function field($name, $fieldKey = self::KEY_ENTITY_NAME, $throwException = true) : ? ReflectedProperty + public function field($name, $fieldKey = self::KEY_ENTITY_NAME, $throwException = true) : null|ReflectedProperty { try{ return $this->fieldList($fieldKey)[$name] ?? null; @@ -51,7 +49,7 @@ class EntityResolver { return null; } - public function searchField($name) : null|array + public function searchField($name) : null|ReflectedProperty { return $this->field($name, self::KEY_ENTITY_NAME, false) ?: $this->field($name, self::KEY_COLUMN_NAME, false); } @@ -62,8 +60,8 @@ class EntityResolver { foreach($this->reflectedClass->getProperties(true) as $item) { foreach($item->getAttributes() as $tag) { - if ( $tag->object instanceof Attribute\Property\Field ) { - if ( $skipVirtual && $tag->object instanceof Attribute\Property\Virtual ) { + if ( $tag->object instanceof Field ) { + if ( $skipVirtual && $tag->object instanceof Virtual ) { break; } @@ -102,7 +100,7 @@ class EntityResolver { try{ if ( $property ) { foreach($property->getAttributes() as $tag) { - if ( $tag->object instanceof Relation or $tag->object instanceof Attribute\Property\Relation ) { + if ( $tag->object instanceof Relation ) { return $property; } } @@ -156,7 +154,7 @@ class EntityResolver { return $table->name ?? ""; } - public function tableAnnotation($required = false) : null|Table|Attribute\Obj\Table + public function tableAnnotation($required = false) : null|Table { if ( null === $table = $this->getTableAttribute() ) { if ($required) { @@ -167,7 +165,7 @@ class EntityResolver { return $table; } - public function databaseName() : ? string + public function databaseName() : null|string { return $this->tableAnnotation(false)->database ?? $this->databaseAdapter()->adapter()->databaseName() ?? null; } @@ -196,7 +194,7 @@ class EntityResolver { return $this->sqlAdapter(); } - public function schemaName(bool $required = false) : ? string + public function schemaName(bool $required = false) : null|string { if ( null === $table = $this->getTableAttribute() ) { throw new \LogicException("Your entity {$this->entityClass} seems to be missing a @Table() annotation"); @@ -209,10 +207,10 @@ class EntityResolver { return $table->schema ?? null; } - public function getPrimaryKeyField() : ? array + public function getPrimaryKeyField() : null|array { foreach($this->fieldList() as $key => $value) { - $field = $this->searchFieldAnnotation($key, [ Attribute\Property\Field::class, Field::class ]); + $field = $this->searchFieldAnnotation($key, [ Field::class ]); if ( null !== $field ) { if ( false !== ( $field->attributes['primary_key'] ?? false ) ) { return [ $key => $field ]; @@ -235,55 +233,15 @@ class EntityResolver { protected function getAdapterInterfaceAttribute() : null|object { - return $this->getAttributeImplementing(Attribute\Obj\AdapterAttributeInterface::class); + return $this->getAttributeImplementing(AdapterAttributeInterface::class); } protected function getTableAttribute() { - return $this->getAttributeImplementing(Attribute\Obj\Table::class) ?: $this->getAnnotationFromClassname( Table::class, false ); + return $this->getAttributeImplementing(Table::class); } - /** - * Transform an annotation into it's object's counterpart - */ - public function getAnnotationFromClassname(string $className, bool $throwError = true) : ? object - { - exit(__FILE__); - if ( $name = $this->uses[$className] ?? false ) { - foreach(array_reverse($this->class['tags']) as $item) { - if ( $item['tag'] === $name ) { - return $this->instanciateAnnotationObject($item); - } - - foreach($this->properties as $item) { - foreach(array_reverse($item['tags']) as $item) { - if ( $item['tag'] === $name ) { - return $this->instanciateAnnotationObject($item); - } - } - } - - foreach($this->methods as $item) { - foreach(array_reverse($item['tags']) as $item) { - if ( $item['tag'] === $name ) { - return $this->instanciateAnnotationObject($item); - } - } - } - } - - if ($throwError) { - throw new \TypeError("Annotation `$className` could not be found within your object `{$this->entityClass}`"); - } - } - elseif ($throwError) { - throw new \InvalidArgumentException("Class `$className` was not found within {$this->entityClass} uses statement (or it's children / traits)"); - } - - return null; - } - - public function getAttributeImplementing(string $interface) : ? object + public function getAttributeImplementing(string $interface) : null|object { foreach (array_reverse($this->reflectedClass->getAttributes(true)) as $item) { if ($item->object instanceof $interface) { @@ -293,14 +251,4 @@ class EntityResolver { return null; } - - public function instanciateAnnotationObject(array|\ReflectionAttribute $tagDefinition) : object - { - - if ($tagDefinition instanceof \ReflectionAttribute) { - $obj = $tagDefinition->newInstance(); - } - - return $obj; - } } diff --git a/src/Entity/DatasetHandler.php b/src/Entity/DatasetHandler.php new file mode 100644 index 0000000..fc39230 --- /dev/null +++ b/src/Entity/DatasetHandler.php @@ -0,0 +1,153 @@ +entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true) as $key => $field) { + $annotation = $this->entityResolver->searchFieldAnnotation($key,[ Field::class ]); + + if ( $entity->__isset($key) ) { + yield $annotation->name ?? $key => $entity->$key; + } + elseif ( $field->allowsNull() ) { + yield $annotation->name ?? $key => null; + } + } + } + + public function push(iterable $dataset) : Generator|array + { + $unmatched = []; + + foreach($dataset as $key => $value) { + $field = $this->entityResolver->field(strtolower($key), EntityResolver::KEY_COLUMN_NAME, false) ?? $this->entityResolver->field(strtolower($key), EntityResolver::KEY_LC_ENTITY_NAME, false); + + if ( $field === null ) { + if ($this->entityStrictFieldsDeclaration ) { + throw new \Exception("Field `$key` can not be found within your entity ".static::class); + } + else { + $unmatched[$key] = $value; + } + + continue; + } + + $type = $field->getTypes()[0]; + + if ( is_null($value) ) { + yield $field->name => null; + } + elseif ( $field->expectType('array') ) { + if ( is_string($value)) { + if (substr($value, 0, 1) === "a") { + + yield $field->name => unserialize($value); + } + else { + $data = json_decode($value, true); + + if (json_last_error() !== \JSON_ERROR_NONE) { + throw new \Exception(sprintf("JSON error while decoding in EntityTrait : '%s' given %s", json_last_error_msg(), $value)); + } + + yield $field->name => $data; + } + } + elseif ( is_array($value) ) { + yield $field->name => $value; + } + } + elseif ( EntityField::isScalarType($type->type) ) { + + if ( $type->type === 'string' ) { + $annotation = $this->entityResolver->searchFieldAnnotation($field->name, [ Field::class ] ); + + if ( $annotation->length ?? null ) { + $value = mb_substr($value, 0, $annotation->length); + } + } + elseif ( $type->type === 'bool' ) { + $value = (bool) $value; + } + + yield $field->name => $value; + } + elseif ( $value instanceof \UnitEnum ) { + yield $field->name => $value; + } + elseif (enum_exists($type->type)) { + yield $field->name => $type->type::from($value); + } + elseif ( ! $type->builtIn ) { + try { + yield $field->name => Ulmus::instanciateObject($type->type, [ $value ]); + } + catch(\Error $e) { + throw new \Error(sprintf("%s for class '%s' on field '%s'", $e->getMessage(), get_class($this), $field->name)); + } + } + } + + return $unmatched; + } + + public function pullRelation(object $entity) : Generator + { + foreach($this->entityResolver->reflectedClass->getProperties(true) as $name => $field){ + $relation = $this->entityResolver->searchFieldAnnotation($name, [ Relation::class ] ); + + if ($relation) { + $ignore = $this->entityResolver->searchFieldAnnotation($name, [ Relation\Ignore::class ] ); + + if ($ignore && $ignore->ignoreExport) { + if ( $relation->isOneToOne() ) { + # @TODO TO INCLUDED INTO getTypes() RETURNED CLASS WHEN DONE ! + yield $name => ( new \ReflectionClass($field->getTypes()[0]) )->newInstanceWithoutConstructor(); + } + else { + # empty collection + yield $name => []; + } + + continue; + } + + # @TODO Must fix recursive bug.. this last check is way too basic to work + if ( $entity->__isset($name) && ($relation->entity ?? $relation->bridge) !== static::class ) { + if ( null !== $value = $entity->__isset($name) ?? null ) { + if ( is_iterable($value) ) { + $list = []; + + foreach($value as $entityObj) { + $list[] = $entityObj->entityGetDataset(false); + } + + yield $name => $list; + } + elseif ( is_object($value) ) { + yield $name => $value->entityGetDataset(false); + } + } + } + } + } + + } +} \ No newline at end of file diff --git a/src/EntityTrait.php b/src/EntityTrait.php index 6d79f16..2cb9005 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -4,7 +4,15 @@ namespace Ulmus; use Notes\Attribute\Ignore; use Psr\Http\Message\ServerRequestInterface; -use Ulmus\{Common\EntityResolver, Common\EntityField, Entity\EntityInterface, QueryBuilder\QueryBuilderInterface}; +use Ulmus\{Attribute\Property\Join, + Attribute\Property\Relation, + Attribute\Property\ResettablePropertyInterface, + Attribute\Property\Virtual, + Common\EntityResolver, + Common\EntityField, + Entity\DatasetHandler, + Entity\EntityInterface, + QueryBuilder\QueryBuilderInterface}; use Ulmus\SearchRequest\{Attribute\SearchParameter, SearchMethodEnum, SearchRequestInterface, @@ -14,6 +22,9 @@ use Ulmus\SearchRequest\{Attribute\SearchParameter, trait EntityTrait { use EventTrait; + #[Ignore] + public array $entityLoadedDataset = []; + #[Ignore] protected bool $entityStrictFieldsDeclaration = false; @@ -21,14 +32,23 @@ trait EntityTrait { protected array $entityDatasetUnmatchedFields = []; #[Ignore] - public array $entityLoadedDataset = []; + protected DatasetHandler $datasetHandler; #[Ignore] - public function __construct(array $dataset = null) { + public function __construct(iterable|null $dataset = null) + { + $this->initializeEntity($dataset); + } + + #[Ignore] + public function initializeEntity(iterable|null $dataset = null) : void + { if ($dataset) { - $this->entityFillFromDataset($dataset); + $this->fromArray($dataset); } + $this->datasetHandler = new DatasetHandler(static::resolveEntity(), $this->entityStrictFieldsDeclaration); + $this->resetVirtualProperties(); } @@ -37,98 +57,55 @@ trait EntityTrait { { $loaded = $this->isLoaded(); - $entityResolver = $this->resolveEntity(); + $handler = $this->datasetHandler->push($dataset); - foreach($dataset as $key => $value) { - $field = $entityResolver->field(strtolower($key), EntityResolver::KEY_COLUMN_NAME, false) ?? $entityResolver->field(strtolower($key), EntityResolver::KEY_LC_ENTITY_NAME, false); + foreach($handler as $field => $value) { + $this->$field = $value; + } - # Temp. fix, incoming patch soon - $type = $field->getTypes()[0]; + $this->entityDatasetUnmatchedFields = $handler->getReturn(); - if ( $field === null ) { - if ($this->entityStrictFieldsDeclaration ) { - throw new \Exception("Field `$key` can not be found within your entity ".static::class); - } - else { - $this->entityDatasetUnmatchedFields[$key] = $value; - } - } - elseif ( is_null($value) ) { - $this->{$field->name} = null; - } - elseif ( $field->expectType('array') ) { - if ( is_string($value)) { - if (substr($value, 0, 1) === "a") { - $this->{$field->name} = unserialize($value); - } - else { - $data = json_decode($value, true); - - if (json_last_error() !== \JSON_ERROR_NONE) { - throw new \Exception(sprintf("JSON error while decoding in EntityTrait : '%s' given %s", json_last_error_msg(), $value)); - } - - $this->{$field->name} = $data; - } - } - elseif ( is_array($value) ) { - $this->{$field->name} = $value; - } - } - elseif ( EntityField::isScalarType($type->type) ) { - - if ( $type->type === 'string' ) { - $annotation = $entityResolver->searchFieldAnnotation($field->name, [ Attribute\Property\Field::class ] ); - - if ( $annotation->length ?? null ) { - $value = mb_substr($value, 0, $annotation->length); - } - } - elseif ( $type->type === 'bool' ) { - $value = (bool) $value; - } - - $this->{$field->name} = $value; - } - elseif ( $value instanceof \UnitEnum ) { - $this->{$field->name} = $value; - } - elseif (enum_exists($type->type)) { - $this->{$field->name} = $type->type::from($value); - } - elseif ( ! $type->builtIn ) { - try { - $this->{$field->name} = Ulmus::instanciateObject($type->type, [ $value ]); - } - catch(\Error $e) { - $f = $type->type; - throw new \Error(sprintf("%s for class '%s' on field '%s'", $e->getMessage(), get_class($this), $field->name)); - } - } - - # Keeping original data to diff on UPDATE query - if ( ! $loaded /* || $isLoadedDataset */ ) { - #if ( $field !== null ) { - # $annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() ); - # $this->entityLoadedDataset[$annotation ? $annotation->name : $field['name']] = $dataset; # <--------- THIS TO FIX !!!!!! - #} - $this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER); - } - elseif ($overwriteDataset) { - $this->entityLoadedDataset = iterator_to_array(array_change_key_case($dataset, \CASE_LOWER)) + $this->entityLoadedDataset; - } + # 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; + } + + $dataset = []; + + foreach($this->datasetHandler->pull($this) as $field => $value) { + $dataset[$field] = $rewriteValue ? static::repository()->adapter->adapter()->writableValue($value) : $value; + } + + if ($includeRelations) { + foreach($this->datasetHandler->pullRelation($this) as $field => $object) { + $dataset[$field] = $object; + } + } + + return $dataset; + } + #[Ignore] public function resetVirtualProperties() : self { - foreach($this->resolveEntity()->reflectedClass->getProperties(true) as $prop => $property) { + foreach($this->resolveEntity()->reflectedClass->getProperties(true) as $field => $property) { foreach($property->attributes as $tag) { - if ( in_array(strtolower($tag->tag), [ 'relation', 'join', 'virtual' ] ) ) { - unset($this->$prop); + + if ( $tag->object instanceof ResettablePropertyInterface ) { + unset($this->$field); } } } @@ -146,74 +123,6 @@ trait EntityTrait { return $this->entityFillFromDataset($dataset); } - public function entityGetDataset(bool $includeRelations = false, bool $returnSource = false, bool $rewriteValue = true) : array - { - if ( $returnSource ) { - return $this->entityLoadedDataset; - } - - $dataset = []; - - $entityResolver = $this->resolveEntity(); - - foreach($entityResolver->fieldList(Common\EntityResolver::KEY_ENTITY_NAME, true) as $key => $field) { - $annotation = $entityResolver->searchFieldAnnotation($key, [ Attribute\Property\Field::class ]); - - if ( isset($this->$key) ) { - $dataset[$annotation->name ?? $key] = $rewriteValue ? - static::repository()->adapter->adapter()->writableValue($this->$key) - : - $this->$key; - } - elseif ( $field->allowsNull() ) { - $dataset[$annotation->name ?? $key] = null; - } - } - - if ($includeRelations) { - foreach($entityResolver->reflectedClass->getProperties(true) as $name => $field){ - $relation = $entityResolver->searchFieldAnnotation($name, [ Attribute\Property\Relation::class ] ); - - if ($relation) { - $ignore = $entityResolver->searchFieldAnnotation($name, [ Attribute\Property\Relation\Ignore::class ] ); - - if ($ignore && $ignore->ignoreExport) { - if ( $relation->isOneToOne() ) { - # empty object - $dataset[$name] = ( new \ReflectionClass($field->getTypes()[0]) )->newInstanceWithoutConstructor(); - } - else { - # empty collection - $dataset[$name] = []; - } - - continue; - } - - # @TODO Must fix recursive bug.. this last check is way too basic to work - if ( isset($this->$name) && ($relation->entity ?? $relation->bridge) !== static::class ) { - if ( null !== $value = $this->$name ?? null ) { - if ( is_iterable($value) ) { - $list = []; - - foreach($value as $entity) { - $list[] = $entity->entityGetDataset(false); - } - - $dataset[$name] = $list; - } - elseif ( is_object($value) ) { - $dataset[$name] = $value->entityGetDataset(false); - } - } - } - } - } - } - - return $dataset; - } - #[Ignore] public function toArray($includeRelations = false, array $filterFields = null, bool $rewriteValue = true) : array { diff --git a/src/Repository/RelationBuilder.php b/src/Repository/RelationBuilder.php index ea98938..f5b9805 100644 --- a/src/Repository/RelationBuilder.php +++ b/src/Repository/RelationBuilder.php @@ -2,20 +2,13 @@ namespace Ulmus\Repository; -use Ulmus\{Attribute\Property\Field, - Ulmus, - Annotation, - Attribute, - Query, - Common, - Common\EntityResolver, - Repository, - Event, - EntityCollection}; +use Ulmus\{ + Ulmus, Query, Common\EntityResolver, Repository, Event, +}; -use Ulmus\Annotation\Property\{Filter, OrderBy, Relation, Relation\Ignore as RelationIgnore, Where, WithJoin, }; - -use Closure; +use Ulmus\Attribute\Property\{ + Filter, Join, OrderBy, Relation, Virtual, Where, WithJoin, +}; class RelationBuilder { @@ -64,7 +57,7 @@ class RelationBuilder } else { - if ( $relation = $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Relation::class , Relation::class ] ) ) { + if ( $relation = $this->resolver->searchFieldAnnotation($name, [ Relation::class ] ) ) { return $this->instanciateEmptyObject($name, $relation); } elseif ($virtual = $this->resolveVirtual($name)) { @@ -77,7 +70,7 @@ class RelationBuilder protected function resolveVirtual(string $name) : mixed { - if (null !== ($virtual = $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Virtual::class, Annotation\Property\Virtual::class ]))) { + if (null !== ($virtual = $this->resolver->searchFieldAnnotation($name, Virtual::class))) { try { $arguments = [ $this->entity ]; @@ -98,11 +91,11 @@ class RelationBuilder protected function resolveRelation(string $name) : mixed { - if ( null !== ( $relation = $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Relation::class, Relation::class ] ) ) ) { - $this->orders = $this->resolver->searchFieldAnnotationList($name, [ Attribute\Property\OrderBy::class, OrderBy::class ] ); - $this->wheres = $this->resolver->searchFieldAnnotationList($name, [ Attribute\Property\Where::class, Where::class ] ); - $this->filters = $this->resolver->searchFieldAnnotationList($name, [ Attribute\Property\Filter::class, Filter::class ] ); - $this->joins = $this->resolver->searchFieldAnnotationList($name, [ Attribute\Property\WithJoin::class, WithJoin::class ] ); + if ( null !== ( $relation = $this->resolver->searchFieldAnnotation($name, [ Relation::class ] ) ) ) { + $this->orders = $this->resolver->searchFieldAnnotationList($name, [ OrderBy::class ] ); + $this->wheres = $this->resolver->searchFieldAnnotationList($name, [ Where::class ] ); + $this->filters = $this->resolver->searchFieldAnnotationList($name, [ Filter::class ] ); + $this->joins = $this->resolver->searchFieldAnnotationList($name, [ WithJoin::class ] ); switch( true ) { case $relation->isOneToOne(): @@ -178,22 +171,22 @@ class RelationBuilder } } - protected function instanciateEmptyEntity(string $name, Relation|Attribute\Property\Relation $relation) : object + protected function instanciateEmptyEntity(string $name, Relation $relation) : object { - $class = $relation->entity ?? $this->resolver->properties[$name]['type']; + $class = $relation->entity ?? $this->resolver->reflectedClass->getProperties()[$name]->getTypes()[0]->type; return new $class(); } - protected function instanciateEmptyObject(string $name, Relation|Attribute\Property\Relation $relation) : object + protected function instanciateEmptyObject(string $name, Relation $relation) : object { switch( true ) { case $relation->isOneToOne(): return $this->instanciateEmptyEntity($name, $relation); case $relation->isOneToMany(): - return ($relation->entity ?? $this->resolver->properties[$name]['type'])::entityCollection(); + return ($relation->entity ?? $this->resolver->reflectedClass->getProperties()[$name]->getTypes()[0]->type)::entityCollection(); case $relation->isManyToMany(): extract($this->relationAnnotations($name, $relation)); @@ -206,14 +199,14 @@ class RelationBuilder protected function fetchFromDataset($name, ? array $data = null) : object|bool { - $annotation = $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Join::class, Annotation\Property\Join::class ]) ?: - $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Relation::class, Annotation\Property\Relation::class ]); + $annotation = $this->resolver->searchFieldAnnotation($name, [ Join::class ]) ?: + $this->resolver->searchFieldAnnotation($name, [ Relation::class ]); if ( $annotation ) { $vars = []; $len = strlen( $name ) + 1; - $isRelation = ( $annotation instanceof Relation ) || ( $annotation instanceof Attribute\Property\Relation ); + $isRelation = $annotation instanceof Relation; if ( $isRelation && $annotation->isManyToMany() ) { $entity = $this->relationAnnotations($name, $annotation)['relationRelation']->entity; @@ -259,12 +252,12 @@ class RelationBuilder return false; } - public function oneToOne(string $name, Relation|Attribute\Property\Relation $relation) : Repository + public function oneToOne(string $name, Relation $relation) : Repository { return $this->oneToMany($name, $relation)->limit(1); } - public function oneToMany(string $name, Relation|Attribute\Property\Relation $relation) : Repository + public function oneToMany(string $name, Relation $relation) : Repository { $baseEntity = $relation->entity ?? $this->resolver->reflectedClass->getProperties()[$name]->getTypes()[0]->type; @@ -290,7 +283,7 @@ class RelationBuilder return $this->applyFilter($this->repository, $name); } - public function manyToMany(string $name, Relation|Attribute\Property\Relation $relation, Relation|Attribute\Property\Relation & $relationRelation = null, bool $selectBridgeField = true) : Repository + public function manyToMany(string $name, Relation $relation, Relation & $relationRelation = null, bool $selectBridgeField = true) : Repository { extract($this->relationAnnotations($name, $relation)); @@ -318,7 +311,7 @@ class RelationBuilder return $this->applyFilter($this->repository, $name); } - public static function relationAnnotations(string $name, Relation|Attribute\Property\Relation $relation) : array + public static function relationAnnotations(string $name, Relation $relation) : array { if ( $relation->isOneToOne() || $relation->isManyToMany() ) { if ( ! $relation->hasBridge() ) { @@ -326,14 +319,14 @@ class RelationBuilder } $bridgeEntity = Ulmus::resolveEntity($relation->bridge); - $bridgeRelation = $bridgeEntity->searchFieldAnnotation($relation->field, [ Attribute\Property\Relation::class, Relation::class ]); - $relationRelation = $bridgeEntity->searchFieldAnnotation($relation->foreignField, [ Attribute\Property\Relation::class, Relation::class ]); + $bridgeRelation = $bridgeEntity->searchFieldAnnotation($relation->field, [ Relation::class ]); + $relationRelation = $bridgeEntity->searchFieldAnnotation($relation->foreignField, [ Relation::class ]); if ($relationRelation === null) { throw new \Exception("@Relation annotation not found for field `{$relation->foreignField}` in entity {$relation->bridge}"); } - $relationRelation->entity ??= $relation->bridge::resolveEntity()->properties[$relation->foreignField]['type']; + $relationRelation->entity ??= $relation->bridge::resolveEntity()->reflectedClass->getProperties()[$relation->foreignField]->getTypes()[0]->type; return [ 'bridgeEntity' => $bridgeEntity,