From 9e84bd35363cea826a8c15a4bf7de83095e1b8a1 Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Fri, 4 Dec 2020 13:21:59 -0500 Subject: [PATCH 1/2] - Set ID as a bigint by default now. - EntityCollection now transforms array received from append() into entity and then append them. - Corrected a big bug within QueryBuilder which was missed when it was made compatible with MSSQL. --- src/Annotation/Property/Field/Id.php | 2 +- src/EntityCollection.php | 30 +++++++++++++++++++++------- src/QueryBuilder.php | 12 +++++------ 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/Annotation/Property/Field/Id.php b/src/Annotation/Property/Field/Id.php index 931b0ae..277373c 100644 --- a/src/Annotation/Property/Field/Id.php +++ b/src/Annotation/Property/Field/Id.php @@ -11,7 +11,7 @@ class Id extends \Ulmus\Annotation\Property\Field { $this->attributes['primary_key'] = true; $this->attributes['auto_increment'] = true; - parent::__construct('int'); + parent::__construct('bigint'); } } diff --git a/src/EntityCollection.php b/src/EntityCollection.php index 5f94cfe..aad784f 100644 --- a/src/EntityCollection.php +++ b/src/EntityCollection.php @@ -221,19 +221,35 @@ class EntityCollection extends \ArrayObject { public function fromArray(array $datasets, ? string /*stringable*/ $entityClass = null) : self { - if ( ! ($this->entityClass || $entityClass) ) { - throw new \Exception("An entity class name must be provided to be instanciated and populated before insertion into this collection."); - } - - $className = $entityClass ?: $this->entityClass; - + foreach($datasets as $dataset) { - $this->append( (new $className() )->fromArray($dataset) ); + $this->append( $this->arrayToEntity($dataset, $entityClass) ); } return $this; } + public function arrayToEntity(array $dataset, ? string /*stringable*/ $entityClass = null) : object + { + if ( ! ($this->entityClass || $entityClass) ) { + throw new \Exception("An entity class name must be provided to be instanciated and populated before insertion into this collection."); + } + + $className = $this->entityClass; + + return ( new $className() )->fromArray($dataset); + } + + public function append($value) : void + { + if ( is_array($value) ) { + $this->append( $this->arrayToEntity($value) ); + } + else { + parent::append($value); + } + } + public function mergeWith( /*array|EntityCollection*/ $datasets ) : self { if ( is_object($datasets) ) { diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 5ce92a9..2d0bb8e 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -64,11 +64,11 @@ class QueryBuilder { if ( null === $this->getFragment(Query\Insert::class) ) { if ( $schema ) { - $table = "\"$schema\".$table"; + $table = "$schema.$table"; } - + if ( $database ) { - $table = "\"$database\".$table"; + $table = "$database.$table"; } $insert = new Query\Insert(); @@ -327,11 +327,11 @@ class QueryBuilder { if ( null === $this->getFragment(Query\Create::class) ) { if ( $schema ) { - $table = "\"$schema\".$table"; + $table = "$schema.$table"; } - + if ( $database ) { - $table = "\"$database\".$table"; + $table = "$database.$table"; } $create = new Query\Create(); From 7dd64abf294212365488e3d6283df3c5e8698885 Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Wed, 6 Jan 2021 19:35:45 +0000 Subject: [PATCH 2/2] - Added a LoadRelation for the repository. - Added a new @Filter() annotation to the relation; adding it to JOIN is also coming. - Some bugfixes done within the Where and ConditionTrait. --- src/Annotation/Property/Filter.php | 15 ++++++++ src/Annotation/Property/Where.php | 12 +++---- src/EntityCollection.php | 47 +++++++++++++++++++----- src/EntityTrait.php | 20 ++++++++--- src/Repository.php | 58 ++++++++++++++++++++++++++++++ src/Repository/ConditionTrait.php | 8 ++--- 6 files changed, 136 insertions(+), 24 deletions(-) create mode 100644 src/Annotation/Property/Filter.php diff --git a/src/Annotation/Property/Filter.php b/src/Annotation/Property/Filter.php new file mode 100644 index 0000000..9374e6d --- /dev/null +++ b/src/Annotation/Property/Filter.php @@ -0,0 +1,15 @@ +method = $method; + } + } +} diff --git a/src/Annotation/Property/Where.php b/src/Annotation/Property/Where.php index f7b0d35..8f56f8b 100644 --- a/src/Annotation/Property/Where.php +++ b/src/Annotation/Property/Where.php @@ -12,7 +12,9 @@ class Where implements \Ulmus\Annotation\Annotation { public string $operator; - public function __construct(? string $field = null, $value = null, ? string $operator = null) + public string $condition; + + public function __construct(? string $field = null, $value = null, ? string $operator = null, ? string $condition = null) { if ( $field !== null ) { $this->field = $field; @@ -22,11 +24,7 @@ class Where implements \Ulmus\Annotation\Annotation { $this->value = $value; } - if ( $operator !== null ) { - $this->operator = $operator; - } - else { - $this->operator = Query\Where::OPERATOR_EQUAL; - } + $this->operator = $operator !== null ? $operator : Query\Where::OPERATOR_EQUAL; + $this->condition = $condition !== null ? $condition : Query\Where::CONDITION_AND; } } diff --git a/src/EntityCollection.php b/src/EntityCollection.php index aad784f..e9f4293 100644 --- a/src/EntityCollection.php +++ b/src/EntityCollection.php @@ -26,11 +26,11 @@ class EntityCollection extends \ArrayObject { } } - public function filtersCollection(Callable $callback, bool $yieldValueOnly = false, bool $replaceCollection = false) : self + public function filtersCollection(Callable $callback, bool $replaceCollection = false) : self { $collection = new static(); - foreach($this->filters($callback, $yieldValueOnly) as $item) { + foreach($this->filters($callback, true) as $item) { $collection->append($item); } @@ -78,6 +78,13 @@ class EntityCollection extends \ArrayObject { return $removed; } + public function clear() : self + { + $this->exchangeArray([]); + + return $this; + } + public function search($value, string $field, bool $strict = true) : Generator { foreach($this->filters(fn($v) => $strict ? $v->$field === $value : $v->$field == $value) as $key => $item) { @@ -106,6 +113,11 @@ class EntityCollection extends \ArrayObject { return $obj; } + public function searchInstances(string $className) : self + { + return $this->filtersCollection(fn($obj) => is_a($obj, $className)); + } + public function column($field, bool $unique = false) : array { $list = []; @@ -118,8 +130,8 @@ class EntityCollection extends \ArrayObject { $value = $item->$field; } - if ($unique && in_array($value, $list)) { - break; + if ($unique && in_array($value, $list, true)) { + continue; } $list[] = $value; @@ -250,13 +262,20 @@ class EntityCollection extends \ArrayObject { } } - public function mergeWith( /*array|EntityCollection*/ $datasets ) : self + public function mergeWith(... $datasets) : self { - if ( is_object($datasets) ) { - $datasets = $datasets->getArrayCopy(); + $list = []; + + foreach($datasets as $dataset) { + if ( is_object($dataset) ) { + $list = array_merge($dataset->getArrayCopy(), $list); + } + else { + $list = array_merge($dataset, $list); + } } - $this->exchangeArray( array_merge( $this->getArrayCopy(), $datasets ) ); + $this->exchangeArray( array_merge( $this->getArrayCopy(), $list ) ); return $this; } @@ -287,4 +306,16 @@ class EntityCollection extends \ArrayObject { return $this; } + + public function rsort(callable $callback, $function = "uasort") : self + { + $this->sort(...func_get_args()); + + return $this->reverse(); + } + + public function reverse() : self + { + return $this->replaceWith(array_reverse($this->getArrayCopy()));; + } } diff --git a/src/EntityTrait.php b/src/EntityTrait.php index 153e59d..55e2315 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -8,7 +8,7 @@ use Ulmus\Repository, Ulmus\Common\EntityField; use Ulmus\Annotation\Classes\{ Method, Table, Collation, }; -use Ulmus\Annotation\Property\{ Field, Relation, OrderBy, Where, Join, Virtual }; +use Ulmus\Annotation\Property\{ Field, Filter, Relation, OrderBy, Where, Join, Virtual }; use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, }; trait EntityTrait { @@ -63,14 +63,15 @@ trait EntityTrait { $order = $entityResolver->searchFieldAnnotationList($name, new OrderBy() ); $where = $entityResolver->searchFieldAnnotationList($name, new Where() ); - + $filters = $entityResolver->searchFieldAnnotationList($name, new Filter() ); + if ( $relation->entity ?? false ) { $baseEntity = $relation->entity(); $repository = $baseEntity->repository(); foreach($where as $condition) { - $repository->where($condition->field, is_callable($condition->value) ? $f = call_user_func_array($condition->value, [ $this ]) : $condition->value, $condition->operator); + $repository->where($condition->field, is_callable($condition->value) ? call_user_func_array($condition->value, [ $this ]) : $condition->value, $condition->operator, $condition->condition); } foreach($order as $item) { @@ -79,6 +80,14 @@ trait EntityTrait { $field = $relation->key; } + + $applyFilter = function($repository) use ($filters, $name) { + foreach($filters as $filter) { + $repository = call_user_func_array([ $this, $filter->method ], [ $repository, $name ]); + } + + return $repository; + }; switch( $relationType ) { case 'onetoone': @@ -100,9 +109,10 @@ trait EntityTrait { if ($relation->foreignKey) { $repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity->field($relation->foreignKey), is_callable($field) ? $field($this) : $this->$field ); } + $this->eventExecute(Event\EntityRelationLoadInterface::class, $name, $repository); - return $this->$name = call_user_func([$repository, $relation->function]); + return $this->$name = call_user_func([$applyFilter($repository), $relation->function]); case 'manytomany': if ( false === $relation->bridge ?? false ) { @@ -138,7 +148,7 @@ trait EntityTrait { $this->eventExecute(Event\EntityRelationLoadInterface::class, $name, $repository); - $this->$name = call_user_func([ $repository, $relationRelation->function ]); + $this->$name = call_user_func([ $applyFilter($repository), $relationRelation->function ]); if ($relation->bridgeField ?? false) { $repository = $relationRelation->entity::repository(); diff --git a/src/Repository.php b/src/Repository.php index a2d831c..8832245 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -176,6 +176,64 @@ class Repository } } + public function loadCollectionRelation(EntityCollection $collection, /*array|string*/ $fields) : void + { + foreach((array) $fields as $name) { + if ( null !== ( $relation = $this->entityResolver->searchFieldAnnotation($name, new Annotation\Property\Relation() ) ) ) { + $relationType = strtolower(str_replace(['-', '_', ' '], '', $relation->type)); + + $order = $this->entityResolver->searchFieldAnnotationList($name, new Annotation\Property\OrderBy() ); + $where = $this->entityResolver->searchFieldAnnotationList($name, new Annotation\Property\Where() ); + + $baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->properties[$name]['type']; + $baseEntityResolver = $baseEntity::resolveEntity(); + + $property = ( $baseEntityResolver->field($relation->foreignKey, 01, false) ?: $baseEntityResolver->field($relation->foreignKey, 02) )['name']; + $entityProperty = ( $this->entityResolver->field($relation->key, 01, false) ?: $this->entityResolver->field($relation->key, 02) )['name']; + + $repository = $baseEntity::repository(); + + foreach($where as $condition) { + $repository->where($condition->field, is_callable($condition->value) ? call_user_func_array($condition->value, [ $this ]) : $condition->value, $condition->operator, $condition->condition); + } + + foreach($order as $item) { + $repository->orderBy($item->field, $item->order); + } + + $field = $relation->key; + + $values = []; + + $key = is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity::field($relation->foreignKey); + + foreach($collection as $item) { + $values[] = is_callable($field) ? $field($item) : $item->$entityProperty; + } + + $repository->where($key, $values); + + switch( $relationType ) { + case 'onetoone': + $results = call_user_func([ $repository, "loadOne" ]); + $item->$name = $results ?: new $baseEntity(); + + break; + + case 'onetomany': + $results = call_user_func([ $repository, $relation->function ]); + + foreach($collection as $item) { + $item->$name = $baseEntity::entityCollection(); + $item->$name->mergeWith( $results->filtersCollection(fn($e) => $e->$property === $item->$entityProperty ) ); + } + + break; + } + } + } + } + public function truncate(? string $table = null, ? string $alias = null, ? string $schema = null) : self { $schema = $schema ?: $this->entityResolver->schemaName(); diff --git a/src/Repository/ConditionTrait.php b/src/Repository/ConditionTrait.php index 3c681e0..d8eee98 100644 --- a/src/Repository/ConditionTrait.php +++ b/src/Repository/ConditionTrait.php @@ -25,9 +25,9 @@ trait ConditionTrait return $this; } - public function where($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self + public function where($field, $value, string $operator = Query\Where::OPERATOR_EQUAL, $condition = Query\Where::CONDITION_AND) : self { - $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_AND); + $this->queryBuilder->where($field, $value, $operator, $condition); return $this; } @@ -67,9 +67,9 @@ trait ConditionTrait return $this; } - public function having($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self + public function having($field, $value, string $operator = Query\Where::OPERATOR_EQUAL, $condition = Query\Where::CONDITION_AND) : self { - $this->queryBuilder->having($field, $value, $operator, Query\Where::CONDITION_AND); + $this->queryBuilder->having($field, $value, $operator, $condition); return $this; }