From 7fb591e1f559f38ba6ff86ed5e6d1d97438e929e Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Mon, 24 May 2021 20:03:38 +0000 Subject: [PATCH] - A lot of fixes made again in this patch --- src/Annotation/Property/FilterJoin.php | 15 +++ src/Annotation/Property/OrWhere.php | 14 +++ src/Annotation/Property/Relation.php | 2 + src/Common/EntityResolver.php | 2 +- src/EntityCollection.php | 29 +++++- src/EntityTrait.php | 20 +++- src/Event/Common/PdoObjectSelectInterface.php | 10 ++ src/Query/Alter.php | 35 +++++++ src/QueryBuilder.php | 37 +++++++- src/Repository.php | 93 +++++++++++-------- src/Repository/ConditionTrait.php | 9 ++ src/Repository/RelationBuilder.php | 8 +- 12 files changed, 222 insertions(+), 52 deletions(-) create mode 100644 src/Annotation/Property/FilterJoin.php create mode 100644 src/Annotation/Property/OrWhere.php create mode 100644 src/Event/Common/PdoObjectSelectInterface.php create mode 100644 src/Query/Alter.php diff --git a/src/Annotation/Property/FilterJoin.php b/src/Annotation/Property/FilterJoin.php new file mode 100644 index 0000000..d46129a --- /dev/null +++ b/src/Annotation/Property/FilterJoin.php @@ -0,0 +1,15 @@ +method = $method; + } + } +} \ No newline at end of file diff --git a/src/Annotation/Property/OrWhere.php b/src/Annotation/Property/OrWhere.php new file mode 100644 index 0000000..385411a --- /dev/null +++ b/src/Annotation/Property/OrWhere.php @@ -0,0 +1,14 @@ +filters(fn($v) => $strict ? $v->$field === $value : $v->$field == $value) as $key => $item) { + foreach($this->filters(fn($v) => isset($v->$field) ? ( $strict ? $v->$field === $value : $v->$field == $value ) : false) as $key => $item) { yield $key => $item; } } @@ -104,12 +104,31 @@ class EntityCollection extends \ArrayObject { return null; } - public function searchAll($value, string $field, bool $strict = true) : self + public function searchAll(/* mixed*/ $values, string $field, bool $strict = true, bool $compareArray = false) : self { $obj = new static(); - foreach($this->search($value, $field, $strict) as $item) { - $obj->append($item); + $values = is_array($values) && $compareArray ? [ $values ] : $values; + + foreach((array) $values as $value) { + foreach ($this->search($value, $field, $strict) as $item) { + $obj->append($item); + } + } + + return $obj; + } + + public function diffAll(/* mixed */ $values, string $field, bool $strict = true, bool $compareArray = false) : self + { + $obj = new static($this->getArrayCopy()); + + $values = is_array($values) && $compareArray ? [ $values ] : $values; + + foreach((array) $values as $value) { + foreach($obj->search($value, $field, $strict) as $key => $item) { + $obj->offsetUnset($key); + } } return $obj; diff --git a/src/EntityTrait.php b/src/EntityTrait.php index 842c21e..475fd6d 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, Filter, Relation, OrderBy, Where, Join, Virtual, On, WithJoin, }; +use Ulmus\Annotation\Property\{ Field, Filter, FilterJoin, Relation, OrderBy, Where, OrWhere, Join, Virtual, On, WithJoin, }; use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, Datetime as DateTime, Date, Time, Bigint, Tinyint, Text, Mediumtext, Longtext, }; use Ulmus\Annotation\Property\Relation\{ Ignore as RelationIgnore }; @@ -254,7 +254,23 @@ trait EntityTrait { { return array_keys($this->resolveEntity()->fieldList()); } - + + /** + * @Ignore + */ + public function __clone() + { + foreach($this as $prop) { + + } + + if ( null !== $pkField = $this->resolveEntity()->getPrimaryKeyField($this) ) { + $key = key($pkField); + + unset($this->$key); + } + } + /** * @Ignore */ diff --git a/src/Event/Common/PdoObjectSelectInterface.php b/src/Event/Common/PdoObjectSelectInterface.php new file mode 100644 index 0000000..e831ebc --- /dev/null +++ b/src/Event/Common/PdoObjectSelectInterface.php @@ -0,0 +1,10 @@ +renderSegments([ + static::SQL_TOKEN, $this->renderTables($this->table), $this->renderFields(), + ]); + } + + public function renderFields() : string + { + return "(" . PHP_EOL . implode("," . PHP_EOL, array_map(function($field) { + return " " . EntityField::generateAlterColumn($field); + }, $this->fieldList)) . PHP_EOL . ")"; + } +} diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 6134203..35742d6 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -351,6 +351,30 @@ class QueryBuilder implements Query\QueryBuilderInterface return $this; } + public function alter(array $fieldlist, string $table, ? string $database = null, ? string $schema = null) : self + { + if ( null === $this->getFragment(Query\Create::class) ) { + if ( $schema ) { + $table = "$schema.$table"; + } + + if ( $database ) { + $table = "$database.$table"; + } + + $alter = new Query\Alter(); + $this->push($alter); + + $alter->fieldList = $fieldlist; + $alter->table = $table; + } + else { + throw new \Exception("A create SQL fragment was already found within the query builder"); + } + + return $this; + } + public function engine(string $value) : self { if ( null === $engine = $this->getFragment(Query\Engine::class) ) { @@ -413,13 +437,22 @@ class QueryBuilder implements Query\QueryBuilderInterface return null; } - public function removeFragment(Query\Fragment $fragment) : void + public function removeFragment(/*Query\Fragment|array*/ $fragment) : void { + is_object($fragment) && $fragment = get_class($fragment); + foreach($this->queryStack as $key => $item) { - if ( $item === $fragment ) { + if ( get_class($item) === $fragment ) { unset($this->queryStack[$key]); } } + + if ( $fragment === Query\Where::class ) { + unset($this->where); + } + elseif ( $fragment === Query\Having::class ) { + unset($this->having); + } } public function getFragments() : array diff --git a/src/Repository.php b/src/Repository.php index 627a17f..d768547 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -2,7 +2,7 @@ namespace Ulmus; -use Ulmus\Annotation\Property\{Field, Where, Having, Relation, Join, WithJoin, Relation\Ignore as RelationIgnore}; +use Ulmus\Annotation\Property\{ Field, Filter, FilterJoin, Where, Having, Relation, Join, WithJoin, Relation\Ignore as RelationIgnore }; use Ulmus\Common\EntityResolver; class Repository @@ -63,7 +63,7 @@ class Repository public function count() : int { - $this->removeQueryFragment($this->queryBuilder->getFragment(Query\Select::class)); + $this->removeQueryFragment(Query\Select::class); if ( $this->queryBuilder->getFragment(Query\GroupBy::class) ) { $this->select( "DISTINCT COUNT(*) OVER ()" ); @@ -79,12 +79,12 @@ class Repository return Ulmus::runSelectQuery($this->queryBuilder, $this->adapter)->fetchColumn(0); } - protected function deleteOne() + public function deleteOne() { return $this->limit(1)->deleteSqlQuery()->runQuery(); } - protected function deleteAll() + public function deleteAll() { return $this->deleteSqlQuery()->runQuery(); } @@ -125,7 +125,7 @@ class Repository } } - public function save(object $entity) : bool + public function save(object $entity, ? array $fieldsAndValue = null) : bool { if ( ! $this->matchEntity($entity) ) { throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`"); @@ -136,7 +136,7 @@ class Repository $primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField(); if ( ! $entity->isLoaded() ) { - $statement = $this->insertSqlQuery($dataset)->runQuery(); + $statement = $this->insertSqlQuery($fieldsAndValue ?? $dataset)->runQuery(); if ( ( 0 !== $statement->lastInsertId ) && ( null !== $primaryKeyDefinition )) { @@ -152,7 +152,9 @@ class Repository throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass)); } - if ( [] !== $diff = $this->generateDatasetDiff($entity) ) { + $diff = $fieldsAndValue ?? $this->generateDatasetDiff($entity); + + if ( [] !== $diff ) { $pkField = key($primaryKeyDefinition); $pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField; $this->where($pkFieldName, $dataset[$pkFieldName]); @@ -211,13 +213,6 @@ class Repository } } - public function removeQueryFragment(? Query\Fragment $fragment) : self - { - $fragment && $this->queryBuilder->removeFragment($fragment); - - return $this; - } - public function selectEntity(string $entity, string $alias, string $prependField = "") : self { $prependField and ($prependField .= "$"); @@ -435,7 +430,6 @@ class Repository $this->select("{$this->alias}.*"); } - # @TODO Apply FILTER annotation to this too ! foreach(array_filter((array) $fields) as $item) { $annotation = $this->entityResolver->searchFieldAnnotation($item, new Join) ?: $this->entityResolver->searchFieldAnnotation($item, new Relation); @@ -467,6 +461,10 @@ class Repository $this->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->value, $condition->operator); } + foreach($this->entityResolver->searchFieldAnnotationList($item, new Filter() ) as $filter) { + call_user_func_array([ $this->entityClass, $filter->method ], [ $this, $item ]); + } + $this->close(); $key = is_string($annotation->key) ? $this->entityClass::field($annotation->key) : $annotation->key; @@ -488,6 +486,10 @@ class Repository $join->where(is_object($field) ? $field : $entity::field($field, $alias), $condition->value, $condition->operator); } } + + foreach($this->entityResolver->searchFieldAnnotationList($item, new FilterJoin() ) as $filter) { + call_user_func_array([ $this->entityClass, $filter->method ], [ $join, $item ]); + } }); } else { @@ -560,30 +562,6 @@ class Repository return $this; } - public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest, bool $count = true) : self - { - if ($count) { - $searchRequest->count = $searchRequest->filter($this->serverRequestCountRepository()) - ->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) - ->likes($searchRequest->likes(), Query\Where::CONDITION_OR) - ->groups($searchRequest->groups()) - ->count(); - } - - return $searchRequest->filter($this) - ->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) - ->likes($searchRequest->likes(), Query\Where::CONDITION_OR) - ->orders($searchRequest->orders()) - ->groups($searchRequest->groups()) - ->offset($searchRequest->offset()) - ->limit($searchRequest->limit()); - } - - protected function serverRequestCountRepository() : Repository - { - return new Repository\ServerRequestCountRepository($this->entityClass, $this->alias, $this->adapter); - } - public function collectionFromQuery(? string $entityClass = null) : EntityCollection { $class = $entityClass ?: $this->entityClass; @@ -677,7 +655,7 @@ class Repository public function createSqlQuery() : self { if ( null === $this->queryBuilder->getFragment(Query\Create::class) ) { - $this->queryBuilder->create($this->escapeFieldList($this->entityResolver->fieldList()), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName()); + $this->queryBuilder->create($this->escapeFieldList($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true)), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName()); } if ( null === $this->queryBuilder->getFragment(Query\Engine::class) ) { @@ -689,6 +667,16 @@ class Repository return $this; } + public function alterSqlQuery(array $fields) : self + { + if ( null === $this->queryBuilder->getFragment(Query\Alter::class) ) { + $this->queryBuilder->create($this->escapeFieldList($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true)), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName()); + } + + + return $this; + } + protected function fromRow($row) : self { @@ -770,4 +758,29 @@ class Repository } protected function finalizeQuery() : void {} + + public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest, bool $count = true) : self + { + if ($count) { + # @TODO Must be placed inside an event instead of directly there ! + $searchRequest->count = $searchRequest->filter($this->serverRequestCountRepository()) + ->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) + ->likes($searchRequest->likes(), Query\Where::CONDITION_OR) + ->groups($searchRequest->groups()) + ->count(); + } + + return $searchRequest->filter($this) + ->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) + ->likes($searchRequest->likes(), Query\Where::CONDITION_OR) + ->orders($searchRequest->orders()) + ->groups($searchRequest->groups()) + ->offset($searchRequest->offset()) + ->limit($searchRequest->limit()); + } + + protected function serverRequestCountRepository() : Repository + { + return new Repository\ServerRequestCountRepository($this->entityClass, $this->alias, $this->adapter); + } } \ No newline at end of file diff --git a/src/Repository/ConditionTrait.php b/src/Repository/ConditionTrait.php index d8eee98..b904235 100644 --- a/src/Repository/ConditionTrait.php +++ b/src/Repository/ConditionTrait.php @@ -150,4 +150,13 @@ trait ConditionTrait return $this; } + + public function removeQueryFragment(/* Query\Fragment | stringable | array */ $fragment) : self + { + foreach((array) $fragment as $item) { + $this->queryBuilder->removeFragment($item); + } + + return $this; + } } \ No newline at end of file diff --git a/src/Repository/RelationBuilder.php b/src/Repository/RelationBuilder.php index 4ff6d34..6df3d15 100644 --- a/src/Repository/RelationBuilder.php +++ b/src/Repository/RelationBuilder.php @@ -9,6 +9,10 @@ use Closure; class RelationBuilder { + const SUBQUERY_FIELD_SUFFIX = "%s\$collection"; + + const JOIN_FIELD_SEPARATOR = "%s\$"; + protected Repository $repository; protected /*object|string*/ $entity; @@ -152,7 +156,7 @@ class RelationBuilder } foreach($data ?: $this->entity->entityLoadedDataset as $key => $value) { - if ( $key === "{$name}\$collection" ) { + if ( $key === sprintf(static::SUBQUERY_FIELD_SUFFIX, $name) ) { if ($value) { if ( null === ( $dataset = \json_decode($value, true) ) ) { throw new \Exception(sprintf("JSON error '%s' from '%s'", \json_last_error_msg(), $value)); @@ -164,7 +168,7 @@ class RelationBuilder return $entity::entityCollection(); } } - elseif ( substr($key, 0, $len ) === "{$name}\$" ) { + elseif ( substr($key, 0, $len ) === sprintf(static::JOIN_FIELD_SEPARATOR, $name) ) { $vars[substr($key, $len)] = $value; } }