From 139504f8dde7d6807fa31a474374908beb0df374 Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Tue, 6 Oct 2020 11:00:12 -0400 Subject: [PATCH] - Added the HAVING and TRUNCATE keyword - Fixed GroupBy which was not implemented yet - Added a new SQL raw() function - From statement now accepts subqueries --- src/Common/Sql.php | 5 + src/EntityCollection.php | 50 ++++ src/EntityTrait.php | 9 +- .../CollectionFromQueryInterface.php} | 4 +- src/Query/Fragment.php | 16 ++ src/Query/From.php | 15 +- src/Query/GroupBy.php | 13 +- src/Query/Having.php | 141 ++++++++- src/Query/Join.php | 24 +- src/Query/OrderBy.php | 2 +- src/Query/Select.php | 9 + src/Query/Truncate.php | 26 ++ src/Query/Where.php | 6 +- src/QueryBuilder.php | 75 +++-- src/Repository.php | 267 +++++------------- src/Repository/ConditionTrait.php | 153 ++++++++++ .../SearchRequestPaginationTrait.php | 2 + 17 files changed, 566 insertions(+), 251 deletions(-) rename src/Event/{RepositoryCollectionFromQueryInterface.php => Repository/CollectionFromQueryInterface.php} (59%) create mode 100644 src/Query/Truncate.php create mode 100644 src/Repository/ConditionTrait.php diff --git a/src/Common/Sql.php b/src/Common/Sql.php index a03ed06..1d5c01c 100644 --- a/src/Common/Sql.php +++ b/src/Common/Sql.php @@ -55,6 +55,11 @@ abstract class Sql { } }; } + + public static function raw(string $sql) : object + { + return static::identifier($sql); + } public static function escape($value) { diff --git a/src/EntityCollection.php b/src/EntityCollection.php index 91ea2e5..42faa81 100644 --- a/src/EntityCollection.php +++ b/src/EntityCollection.php @@ -6,6 +6,8 @@ use Generator; class EntityCollection extends \ArrayObject { + public ? string $entityClass = null; + public function filters(Callable $callback) : Generator { $idx = 0; @@ -29,6 +31,17 @@ class EntityCollection extends \ArrayObject { return $collection; } + + public function iterate(Callable $callback) : array + { + $results = []; + + foreach($this as $item) { + $results[] = $callback($item); + } + + return $results; + } public function removeOne($value, string $field, bool $strict = true) : ? object { @@ -72,6 +85,17 @@ class EntityCollection extends \ArrayObject { } } + public function column($field) : array + { + $list = []; + + foreach($this as $item) { + $list[] = $item->$field; + } + + return $list; + } + public function buildArray(string $keyColumn, /* string|callable|null */ $value = null) : array { $list = []; @@ -107,4 +131,30 @@ class EntityCollection extends \ArrayObject { return $list; } + + public function fromArray(array $datasets, ? string $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) ); + } + + return $this; + } + + public function mergeWith( /*array|EntityCollection*/ $datasets ) : self + { + if ( is_object($datasets) ) { + $datasets = $datasets->getArrayCopy(); + } + + $this->exchangeArray( array_merge( $this->getArrayCopy(), $datasets ) ); + + return $this; + } } diff --git a/src/EntityTrait.php b/src/EntityTrait.php index c19077e..a85e5e5 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -321,9 +321,9 @@ trait EntityTrait { /** * @Ignore */ - public function toCollection() : array + public function toCollection() : EntityCollection { - return new EntityCollection($this->toArray()); + return static::entityCollection([ $this->toArray() ]); } /** @@ -377,7 +377,10 @@ trait EntityTrait { */ public static function entityCollection(...$arguments) : EntityCollection { - return new EntityCollection(...$arguments); + $collection = new EntityCollection(...$arguments); + $collection->entityClass = static::class; + + return $collection; } /** diff --git a/src/Event/RepositoryCollectionFromQueryInterface.php b/src/Event/Repository/CollectionFromQueryInterface.php similarity index 59% rename from src/Event/RepositoryCollectionFromQueryInterface.php rename to src/Event/Repository/CollectionFromQueryInterface.php index 1c167e4..b18fa89 100644 --- a/src/Event/RepositoryCollectionFromQueryInterface.php +++ b/src/Event/Repository/CollectionFromQueryInterface.php @@ -1,9 +1,9 @@ $table) { + $list[] = ! is_numeric($alias) ? "$table $alias" : $table; + + if ( ++$i === $maxTableCount ) { + break; + } + } + + return implode(", ", $list); + } } diff --git a/src/Query/From.php b/src/Query/From.php index 3d291a0..3ff11ee 100644 --- a/src/Query/From.php +++ b/src/Query/From.php @@ -10,6 +10,8 @@ class From extends Fragment { public array $tables = []; + public string $sql; + public function set(array $tables) : self { $this->tables = $tables; @@ -29,18 +31,7 @@ class From extends Fragment { public function render() : string { return $this->renderSegments([ - static::SQL_TOKEN, $this->renderTables(), + static::SQL_TOKEN, $this->sql ?? $this->renderTables($this->tables), ]); } - - protected function renderTables() : string - { - $list = []; - - foreach((array) $this->tables as $alias => $table) { - $list[] = ! is_numeric($alias) ? "$table $alias" : $table; - } - - return implode(", ", $list); - } } diff --git a/src/Query/GroupBy.php b/src/Query/GroupBy.php index 7a05b4b..fd969cf 100644 --- a/src/Query/GroupBy.php +++ b/src/Query/GroupBy.php @@ -3,22 +3,23 @@ namespace Ulmus\Query; class GroupBy extends Fragment { - - const SQL_TOKEN = "GROUP BY"; - - public int $order = 70; + public int $order = 75; public array $groupBy = []; + + const SQL_TOKEN = "GROUP BY"; public function set(array $order) : self { - $this->groupBy = $order; + $this->groupBy = [ $orderĀ ]; + return $this; } - public function add(string $field, ? string $direction = null) : self + public function add(string $field) : self { $this->groupBy[] = $field; + return $this; } diff --git a/src/Query/Having.php b/src/Query/Having.php index 5d349cd..166a56a 100644 --- a/src/Query/Having.php +++ b/src/Query/Having.php @@ -2,7 +2,142 @@ namespace Ulmus\Query; -class Having extends Where { - const SQL_TOKEN = "HAVING"; - public int $order = 75; +use Ulmus\QueryBuilder; + +use Ulmus\Common\EntityField, + Ulmus\Common\Sql; + +class Having extends Fragment { + const SQL_TOKEN = "HAVING"; + + public int $order = 80; + + public array $conditionList; + + public QueryBuilder $queryBuilder; + + public ? Having $parent = null; + + public string $condition = Where::CONDITION_AND; + + public function __construct(? QueryBuilder $queryBuilder, $condition = Where::CONDITION_AND) + { + $this->queryBuilder = $queryBuilder; + $this->condition = $condition; + $this->parent = $queryBuilder->having ?? null; + } + + public function add($field, $value, string $operator, string $condition, bool $not = false) : self + { + $this->conditionList[] = [ + $field, + $value, + $operator ?: $this->queryBuilder->conditionOperator, + $condition, + $not + ]; + + return $this; + } + + public function render(bool $skipToken = false) : string + { + $stack = []; + + foreach ($this->conditionList ?? [] as $key => $item) { + if ( $item instanceof static ) { + if ( $item->conditionList ?? false ) { + $stack[] = ( $key !== 0 ? "{$item->condition} " : "" ) . "(" . $item->render($skipToken) . ")"; + } + } + else { + list($field, $value, $operator, $condition, $not) = $item; + $stack[] = $latest = $this->havingCondition($field, $value, $operator, $key !== 0 ? $condition : "", $not); + } + } + + return $this->renderSegments([ + ! $this->parent && ! $skipToken ? static::SQL_TOKEN : "", + implode(" ", $stack) + ]); + } + + protected function havingCondition($field, $value, string $operator = Where::OPERATOR_EQUAL, string $condition = Where::CONDITION_AND, bool $not = false) { + return new class($this->queryBuilder, $field, $value, $operator, $condition, $not) { + + public $value; + public bool $not = false; + public string $field; + public string $operator; + public string $condition; + public QueryBuilder $queryBuilder; + + protected string $content = ""; + + public function __construct(QueryBuilder $queryBuilder, string $field, $value, string $operator, string $condition, bool $not) { + $this->queryBuilder = $queryBuilder; + $this->field = $field; + $this->value = $value; + $this->condition = $condition; + $this->operator = $operator; + $this->not = $not; + } + + public function render() : string + { + $value = $this->value(); + + return $this->content ?: $this->content = implode(" ", array_filter([ + $this->condition, + $this->not ? Where::CONDITION_NOT : "", + $this->field, + $this->operator(), + $value, + ])); + } + + protected function operator() : string + { + if ( is_array($this->value) ) { + return (in_array($this->operator, [ '!=', '<>' ]) ? Where::CONDITION_NOT . " " : "") . Where::COMPARISON_IN; + } + + return $this->operator; + } + + protected function value() + { + if ( is_array($this->value) ) { + $stack = []; + + foreach($this->value as $item) { + $stack[] = $this->filterValue($item); + } + + return "(" . implode(", ", $stack) . ")"; + } + + return $this->filterValue($this->value); + } + + protected function filterValue($value) + { + if ( $value === null ) { + $this->operator = in_array($this->operator, [ '!=', '<>' ]) ? Where::COMPARISON_IS . " " . Where::CONDITION_NOT : Where::COMPARISON_IS; + return Where::COMPARISON_NULL; + } + elseif ( is_object($value) && ( $value instanceof EntityField ) ) { + return $value->name(); + } + else { + return $this->queryBuilder->addParameter($value); + } + } + + public function __toString() : string + { + return $this->render(); + } + }; + } } diff --git a/src/Query/Join.php b/src/Query/Join.php index 238a3f4..58a0726 100644 --- a/src/Query/Join.php +++ b/src/Query/Join.php @@ -3,9 +3,13 @@ namespace Ulmus\Query; use Ulmus\QueryBuilder; +use Ulmus\Repository\ConditionTrait; -class Join extends Fragment { +class Join extends Fragment +{ + use ConditionTrait; + const SQL_OUTER = "OUTER"; const SQL_TOKEN = "JOIN"; const TYPE_LEFT = "LEFT"; @@ -33,8 +37,10 @@ class Join extends Fragment { public /* QueryBuilder */ $queryBuilder; - public function __construct(QueryBuilder $queryBuilder) { - $this->queryBuilder = $queryBuilder; + public function __construct(QueryBuilder $queryBuilder) + { + $this->queryBuilder = new QueryBuilder(); + $this->queryBuilder->parent = $queryBuilder; } public function set(string $side, /* QueryBuilder|string */ $table, string $field, /* QueryBuilder|string */ $value) @@ -44,9 +50,15 @@ class Join extends Fragment { $this->field = $field; $this->value = $value; } - + public function render() : string { - return $this->renderSegments([ strtoupper($this->side), static::SQL_TOKEN, $this->table, $this->alias ?? "", $this->attachment, $this->field, "=", $this->value ]); + if ($this->queryBuilder->where ?? false ) { + $where = $this->renderSegments([Where::CONDITION_AND, $this->queryBuilder->render(true)]); + } + + return $this->renderSegments([ + strtoupper($this->side), $this->outer ? static::SQL_OUTER : "", static::SQL_TOKEN, $this->table, $this->alias ?? "", $this->attachment, $this->field, "=", $this->value, $where ?? "" + ]); } -} +} \ No newline at end of file diff --git a/src/Query/OrderBy.php b/src/Query/OrderBy.php index 5e62ad2..318690a 100644 --- a/src/Query/OrderBy.php +++ b/src/Query/OrderBy.php @@ -3,7 +3,7 @@ namespace Ulmus\Query; class OrderBy extends Fragment { - public int $order = 80; + public int $order = 85; public array $orderBy = []; diff --git a/src/Query/Select.php b/src/Query/Select.php index ceecf77..aa2f3d8 100644 --- a/src/Query/Select.php +++ b/src/Query/Select.php @@ -4,6 +4,8 @@ namespace Ulmus\Query; class Select extends Fragment { + const FIELD_AS = "%s as %s"; + public int $order = -100; public bool $distinct = false; @@ -36,6 +38,13 @@ class Select extends Fragment { public function render() : string { + foreach($this->fields as $key => &$value) { + if ( ! is_numeric($key) ) { + $value = sprintf(static::FIELD_AS, $value, $key); + } + + } + return $this->renderSegments([ ( $this->union ? 'UNION' : false ), static::SQL_TOKEN, diff --git a/src/Query/Truncate.php b/src/Query/Truncate.php new file mode 100644 index 0000000..da26663 --- /dev/null +++ b/src/Query/Truncate.php @@ -0,0 +1,26 @@ +table = $tableName; + + return $this; + } + + public function render() : string + { + return $this->renderSegments([ + static::SQL_TOKEN, $this->table, + ]); + } +} diff --git a/src/Query/Where.php b/src/Query/Where.php index 736d5ec..255dffe 100644 --- a/src/Query/Where.php +++ b/src/Query/Where.php @@ -50,14 +50,14 @@ class Where extends Fragment { return $this; } - public function render() : string + public function render(bool $skipToken = false) : string { $stack = []; foreach ($this->conditionList ?? [] as $key => $item) { if ( $item instanceof Where ) { if ( $item->conditionList ?? false ) { - $stack[] = ( $key !== 0 ? "{$item->condition} " : "" ) . "(" . $item->render() . ")"; + $stack[] = ( $key !== 0 ? "{$item->condition} " : "" ) . "(" . $item->render($skipToken) . ")"; } } else { @@ -67,7 +67,7 @@ class Where extends Fragment { } return $this->renderSegments([ - ! $this->parent ? static::SQL_TOKEN : "", + ! $this->parent && ! $skipToken ? static::SQL_TOKEN : "", implode(" ", $stack) ]); } diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index c597c79..6bc73e6 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -8,6 +8,8 @@ class QueryBuilder public Query\Having $having; + public QueryBuilder $parent; + /** * Those are the parameters we are going to bind to PDO. */ @@ -18,10 +20,10 @@ class QueryBuilder * Those values are to be inserted or updated */ public array $values = []; - + public string $whereConditionOperator = Query\Where::CONDITION_AND; - public string $havingConditionOperator = Query\Having::CONDITION_AND; + public string $havingConditionOperator = Query\Where::CONDITION_AND; protected int $parameterIndex = 0; @@ -194,7 +196,12 @@ class QueryBuilder return $this; } - public function having($field, $value, string $operator = Query\Having::OPERATOR_EQUAL, string $condition = Query\Having::CONDITION_AND, bool $not = false) : self + public function notWhere($field, $value, string $operator = Query\Where::CONDITION_AND) : self + { + return $this->where($field, $value, $operator, true); + } + + public function having($field, $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self { if ( $this->having ?? false ) { $having = $this->having; @@ -209,15 +216,16 @@ class QueryBuilder return $this; } - - public function notWhere($field, $value, string $operator = Query\Where::CONDITION_AND) : self + public function groupBy(string $field, ? string $direction = null) : self { - return $this->where($field, $value, $operator, true); - } + if ( null === $groupBy = $this->getFragment(Query\GroupBy::class) ) { + $groupBy = new Query\GroupBy(); + $this->push($groupBy); + } - public function groupBy() : self - { + $groupBy->add($field, $direction); + return $this; } @@ -258,6 +266,13 @@ class QueryBuilder } public function join(string $type, /*string | QueryBuilder*/ $table, $field, $value, bool $outer = false, ? string $alias = null) : self + { + $this->withJoin(...func_get_args()); + + return $this; + } + + public function withJoin(string $type, $table, $field, $value, bool $outer = false, ? string $alias = null) : Query\Join { $join = new Query\Join($this); @@ -269,17 +284,41 @@ class QueryBuilder $join->alias = $alias; + return $join; + } + + public function truncate(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self + { + if ( $schema ) { + $table = "$schema.$table"; + } + + if ( $database ) { + $table = "$database.$table"; + } + + $truncate = new Query\Truncate($this); + + $this->push($truncate); + + $truncate->set($table); + return $this; } - + public function push(Query\Fragment $queryFragment) : self { $this->queryStack[] = $queryFragment; return $this; } + + public function pull(Query\Fragment $queryFragment) : self + { + return array_shift($this->queryStack); + } - public function render() : string + public function render(bool $skipToken = false) : string { $sql = []; @@ -288,7 +327,7 @@ class QueryBuilder }); foreach($this->queryStack as $fragment) { - $sql[] = $fragment->render(); + $sql[] = $fragment->render($skipToken); } return implode(" ", $sql); @@ -298,10 +337,10 @@ class QueryBuilder { $this->parameters = $this->values = $this->queryStack = []; $this->whereConditionOperator = Query\Where::CONDITION_AND; - $this->havingConditionOperator = Query\Having::CONDITION_AND; + $this->havingConditionOperator = Query\Where::CONDITION_AND; $this->parameterIndex = 0; - unset($this->where); + unset($this->where, $this->having); } public function getFragment(string $class) : ? Query\Fragment @@ -331,12 +370,16 @@ class QueryBuilder public function addParameter($value, string $key = null) : string { + if ( $this->parent ?? false ) { + return $this->parent->addParameter($value, $key); + } + if ( $key === null ) { $key = ":p" . $this->parameterIndex++; } - $this->parameters[$key] = $value; - + $this->parameters[$key] = $value; + return $key; } diff --git a/src/Repository.php b/src/Repository.php index d856bf8..b093bbb 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -6,7 +6,7 @@ use Ulmus\Common\EntityResolver; class Repository { - use EventTrait; + use EventTrait, Repository\ConditionTrait; const DEFAULT_ALIAS = "this"; @@ -27,7 +27,7 @@ class Repository $this->alias = $alias; $this->entityResolver = Ulmus::resolveEntity($entity); $this->adapter = $adapter ?? $this->entityResolver->databaseAdapter() ?? Ulmus::$defaultAdapter; - $this->queryBuilder = new QueryBuilder( $this->adapter->adapter() ); + $this->queryBuilder = new QueryBuilder(); } public function loadOne() : ? object @@ -61,7 +61,14 @@ class Repository $this->queryBuilder->removeFragment($select); } - $this->select("COUNT(*)")->selectSqlQuery(); + if ( $this->queryBuilder->getFragment(Query\GroupBy::class) ) { + $this->select( "DISTINCT COUNT(*) OVER ()" ); + } + else { + $this->select(Common\Sql::function("COUNT", "*")); + } + + $this->selectSqlQuery(); $this->finalizeQuery(); @@ -161,14 +168,35 @@ class Repository } } + public function truncate(? string $table = null, ? string $alias = null, ? string $schema = null) : self + { + $schema = $schema ?: $this->entityResolver->schemaName(); + + $this->queryBuilder->truncate($this->escapeTable($table ?: $this->entityResolver->tableName()), $alias ?: $this->alias, $this->escapeDatabase($this->adapter->adapter()->database), $schema ? $this->escapeSchema($schema) : null); + + $this->finalizeQuery(); + + $result = Ulmus::runSelectQuery($this->queryBuilder, $this->adapter); + + return $this; + } + public function generateDatasetDiff(object $entity) : array { return array_diff_assoc( array_change_key_case($entity->toArray()), array_change_key_case($entity->entityGetDataset(false, true)) ); } - - public function yieldAll() : \Generator + + public function yield() : \Generator { + $class = $this->entityClass; + + $this->selectSqlQuery(); + + $this->finalizeQuery(); + foreach(Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) { + yield ( new $class() )->entityFillFromDataset($entityData); + } } public function select($fields) : self @@ -220,194 +248,23 @@ class Repository return $this; } - public function join(string $type, $table, $field, $value, ? string $alias = null) : self + public function join(string $type, $table, $field, $value, ? string $alias = null, ? callable $callback = null) : self { - $this->queryBuilder->join($type, $this->escapeTable($table), $field, $value, false, $alias); + $join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, false, $alias); - return $this; - } - - public function outerJoin(string $type, $table, $field, $value, ? string $alias = null) : self - { - $this->queryBuilder->join($type, $this->escapeTable($table), $field, $value, true, $alias); - - return $this; - } - - public function open(string $condition = Query\Where::CONDITION_AND) : self - { - $this->queryBuilder->open($condition); - - return $this; - } - - public function orOpen() : self - { - return $this->open(Query\Where::CONDITION_OR); - } - - public function close() : self - { - $this->queryBuilder->close(); - - return $this; - } - - public function where($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self - { - $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_AND); - - return $this; - } - - public function wheres(array $fieldValues, string $operator = Query\Where::OPERATOR_EQUAL) : self - { - foreach($fieldValues as $field => $value) { - if ( is_array($value) ) { - switch ($value[1]) { - case Query\Where::CONDITION_AND: - $this->where($field, $value[0], $operator); - break; - - case Query\Where::CONDITION_OR: - $this->or($field, $value[0], $operator); - break; - - case Query\Where::CONDITION_NOT: - $this->notWhere($field, $value[0], $operator); - break; - } - } - else { - $this->where($field, $value, $operator); - } + if ( $callback ) { + $callback($join); } return $this; } - - public function and($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self - { - return $this->where($field, $value, $operator); - } - public function or($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self + public function outerJoin(string $type, $table, $field, $value, ? string $alias = null, ? callable $callback = null) : self { - $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_OR); + $join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, true, $alias); - return $this; - } - - public function notWhere($field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self - { - $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_AND, true); - - return $this; - } - - public function orNot($field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self - { - $this->queryBuilder->notWhere($field, $value, $operator, Query\Where::CONDITION_OR, true); - - return $this; - } - - public function having($field, $value, string $operator = Query\Having::OPERATOR_EQUAL) : self - { - $this->queryBuilder->having($field, $value, $operator, Query\Having::CONDITION_AND); - - return $this; - } - - public function orHaving($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self - { - $this->queryBuilder->having($field, $value, $operator, Query\Having::CONDITION_OR); - - return $this; - } - - public function notHaving($field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self - { - $this->queryBuilder->having($field, $value, $operator, Query\Having::CONDITION_AND, true); - - return $this; - } - - public function orNotHaving($field, $value) : self - { - $this->queryBuilder->having($field, $value, Query\Where::OPERATOR_NOT_EQUAL, Query\Having::CONDITION_OR, true); - - return $this; - } - - public function in($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self - { - $this->queryBuilder->where($field, $value, $operator); - - return $this; - } - - public function orIn($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self - { - $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_OR); - - return $this; - } - - public function notIn($field, $value) : self - { - $this->queryBuilder->where($field, $value, Query\Where::OPERATOR_NOT_EQUAL); - - return $this; - } - - public function orNotIn($field, $value) : self - { - return $this->orNot($field, $value, Query\Where::OPERATOR_NOT_EQUAL, Query\Where::CONDITION_OR, true); - } - - public function like($field, $value) : self - { - $this->where($field, $value, Query\Where::OPERATOR_LIKE); - - return $this; - } - - public function orLike($field, $value) : self - { - $this->or($field, $value, Query\Where::OPERATOR_LIKE); - - return $this; - } - - public function notLike($field, $value) : self - { - $this->notWhere($field, $value, Query\Where::OPERATOR_LIKE); - - return $this; - } - - public function likes(array $fieldValues, string $condition = Query\Where::CONDITION_AND) : self - { - foreach($fieldValues as $field => $value) { - if ( is_array($value) ) { - switch ($value[1]) { - case Query\Where::CONDITION_AND: - $this->like($field, $value[0]); - break; - - case Query\Where::CONDITION_OR: - $this->orLike($field, $value[0]); - break; - - case Query\Where::CONDITION_NOT: - $this->notLike($field, $value[0]); - break; - } - } - else { - $this->like($field, $value); - } + if ( $callback ) { + $callback($join); } return $this; @@ -432,15 +289,20 @@ class Repository { } - - public function groupBy() : self + + public function groupBy($field) : self { + $this->queryBuilder->groupBy($field); + return $this; } public function groups(array $groups) : self { - # foreach($this->groups) + foreach($groups as $field ) { + $this->groupBy($field); + } + return $this; } @@ -454,7 +316,7 @@ class Repository public function orders(array $orderList) : self { foreach($orderList as $field => $direction) { - $this->queryBuilder->orderBy($field, $direction); + $this->orderBy($field, $direction); } return $this; @@ -502,7 +364,10 @@ class Repository public function withJoin(/*string|array*/ $fields) : self { $resolvedEntity = Ulmus::resolveEntity($this->entityClass); - $this->select("{$this->alias}.*"); + + if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) { + $this->select("{$this->alias}.*"); + } foreach((array) $fields as $item) { if ( null !== $join = $resolvedEntity->searchFieldAnnotation($item, new Annotation\Property\Join) ) { @@ -528,17 +393,21 @@ class Repository public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest) : self { - $searchRequest->count = $searchRequest->filter( clone $this ) - ->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) - ->likes($searchRequest->likes(), Query\Where::CONDITION_OR) - ->groups($searchRequest->groups()) + $likes = $searchRequest->likes(); + $wheres = $searchRequest->wheres(); + $groups = $searchRequest->groups(); + + $searchRequest->count = $searchRequest->skipCount ? 0 : $searchRequest->filter( clone $this ) + ->wheres($wheres, Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) + ->likes($likes, Query\Where::CONDITION_OR) + ->groups($groups) ->count(); return $searchRequest->filter($this) - ->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) - ->likes($searchRequest->likes(), Query\Where::CONDITION_OR) + ->wheres($wheres, Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) + ->likes($likes, Query\Where::CONDITION_OR) ->orders($searchRequest->orders()) - ->groups($searchRequest->groups()) + ->groups($groups) ->offset($searchRequest->offset()) ->limit($searchRequest->limit()); } @@ -546,8 +415,8 @@ class Repository public function collectionFromQuery(? string $entityClass = null) : EntityCollection { $class = $entityClass ?: $this->entityClass; - - $entityCollection = $this->instanciateEntityCollection(); + + $entityCollection = $class::entityCollection(); $this->selectSqlQuery(); @@ -557,7 +426,7 @@ class Repository $entityCollection->append( ( new $class() )->entityFillFromDataset($entityData) ); } - $this->eventExecute(Event\RepositoryCollectionFromQueryInterface::class, $entityCollection); + $this->eventExecute(Event\Repository\CollectionFromQueryInterface::class, $entityCollection); return $entityCollection; } diff --git a/src/Repository/ConditionTrait.php b/src/Repository/ConditionTrait.php new file mode 100644 index 0000000..3c681e0 --- /dev/null +++ b/src/Repository/ConditionTrait.php @@ -0,0 +1,153 @@ +queryBuilder->open($condition); + + return $this; + } + + public function orOpen() : self + { + return $this->open(Query\Where::CONDITION_OR); + } + + public function close() : self + { + $this->queryBuilder->close(); + + return $this; + } + + public function where($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self + { + $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_AND); + + return $this; + } + + public function wheres(array $fieldValues, string $operator = Query\Where::OPERATOR_EQUAL) : self + { + foreach($fieldValues as $field => $value) { + $this->where($field, $value, $operator); + } + + return $this; + } + + public function and($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self + { + return $this->where($field, $value, $operator); + } + + public function or($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self + { + $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_OR); + + return $this; + } + + public function notWhere($field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self + { + $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_AND, true); + + return $this; + } + + public function orNot($field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self + { + $this->queryBuilder->notWhere($field, $value, $operator, Query\Where::CONDITION_OR, true); + + return $this; + } + + public function having($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self + { + $this->queryBuilder->having($field, $value, $operator, Query\Where::CONDITION_AND); + + return $this; + } + + public function orHaving($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self + { + $this->queryBuilder->having($field, $value, $operator, Query\Where::CONDITION_OR); + + return $this; + } + + public function notHaving($field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self + { + $this->queryBuilder->having($field, $value, $operator, Query\Where::CONDITION_AND, true); + + return $this; + } + + public function orNotHaving($field, $value) : self + { + $this->queryBuilder->having($field, $value, Query\Where::OPERATOR_NOT_EQUAL, Query\Where::CONDITION_OR, true); + + return $this; + } + + public function in($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self + { + $this->queryBuilder->where($field, $value, $operator); + + return $this; + } + + public function orIn($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self + { + $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_OR); + + return $this; + } + + public function notIn($field, $value) : self + { + $this->queryBuilder->where($field, $value, Query\Where::OPERATOR_NOT_EQUAL); + + return $this; + } + + public function orNotIn($field, $value) : self + { + return $this->orNot($field, $value, Query\Where::OPERATOR_NOT_EQUAL, Query\Where::CONDITION_OR, true); + } + + public function like($field, $value) : self + { + $this->where($field, $value, Query\Where::OPERATOR_LIKE); + + return $this; + } + + public function orLike($field, $value) : self + { + $this->or($field, $value, Query\Where::OPERATOR_LIKE); + + return $this; + } + + public function notLike($field, $value) : self + { + $this->notWhere($field, $value, Query\Where::OPERATOR_LIKE); + + return $this; + } + + public function likes(array $fieldValues, string $condition = Query\Where::CONDITION_AND) : self + { + foreach($fieldValues as $field => $value) { + $this->like($field, $value); + } + + return $this; + } +} \ No newline at end of file diff --git a/src/SearchRequest/SearchRequestPaginationTrait.php b/src/SearchRequest/SearchRequestPaginationTrait.php index c4d0de9..0494c34 100644 --- a/src/SearchRequest/SearchRequestPaginationTrait.php +++ b/src/SearchRequest/SearchRequestPaginationTrait.php @@ -12,6 +12,8 @@ trait SearchRequestPaginationTrait { public int $limit = 25; + public bool $skipCount = false; + public function limit(): int { return $this->limit;