diff --git a/src/Entity/Field/Date.php b/src/Entity/Field/Date.php index c2c5e7e..8724cd3 100644 --- a/src/Entity/Field/Date.php +++ b/src/Entity/Field/Date.php @@ -5,9 +5,4 @@ namespace Ulmus\Entity\Field; class Date extends Datetime { public string $format = "Y-m-d"; - - public function formatLocale(string $format) : string - { - return strftime($format, $this->getTimestamp()); - } } diff --git a/src/EntityCollection.php b/src/EntityCollection.php index 478058b..cb17ff8 100644 --- a/src/EntityCollection.php +++ b/src/EntityCollection.php @@ -44,6 +44,15 @@ class EntityCollection extends \ArrayObject { } } + public function filtersOne(Callable $callback) : ? object + { + foreach($this->filters($callback, true) as $item) { + return $item; + } + + return null; + } + public function iterate(Callable $callback) : self { foreach($this as $item) { @@ -290,6 +299,20 @@ class EntityCollection extends \ArrayObject { return $this; } + public function batch(int $size, $fill = null, $preserveKeys = true) : array + { + $batch = []; + $split = ceil( $this->count() / $size ); + + for($i = 0; $i < $split; $i++) { + $slice = array_slice($this->getArrayCopy(), $i * $size, $size, $preserveKeys); + $pad = ( count($slice) !== $size ) && ( $fill !== null ); + $batch[] = $pad ? array_pad($slice, $size, $fill) : $slice; + } + + return $batch; + } + public function randomize() : self { $arr = $this->getArrayCopy(); @@ -313,6 +336,16 @@ class EntityCollection extends \ArrayObject { return $this->reverse(); } + public function sortField(? string $field = null) : self + { + return $this->sort(fn($e1, $e2) => $field ? $e1->$field <=> $e2->$field : $e1 <=> $e2); + } + + public function rsortField(string $field) : self + { + return $this->sort(fn($e1, $e2) => $field ? $e2->$field <=> $e1->$field : $e2 <=> $e1); + } + public function reverse() : self { return $this->replaceWith(array_reverse($this->getArrayCopy()));; diff --git a/src/Exception/InvalidFieldType.php b/src/Exception/InvalidFieldType.php new file mode 100644 index 0000000..6888726 --- /dev/null +++ b/src/Exception/InvalidFieldType.php @@ -0,0 +1,5 @@ +validateFieldType($field); + $this->groupBy[] = $field; return $this; diff --git a/src/Query/Having.php b/src/Query/Having.php index a106e99..96552bd 100644 --- a/src/Query/Having.php +++ b/src/Query/Having.php @@ -29,6 +29,9 @@ class Having extends Fragment { public function add($field, $value, string $operator, string $condition, bool $not = false) : self { + $this->validateFieldType($field); + # $this->validateValueType($value); + $this->conditionList[] = [ $field, $value, @@ -102,7 +105,8 @@ class Having extends Fragment { return (in_array($this->operator, [ '!=', '<>' ]) ? Where::CONDITION_NOT . " " : "") . Where::COMPARISON_IN; } - return $this->operator; + # whitelisting operators + return in_array(strtoupper($this->operator), [ '=', '!=', '<>', 'LIKE' ]) ? $this->operator : "="; } protected function value() @@ -124,6 +128,7 @@ class Having extends Fragment { { 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 ) ) { diff --git a/src/Query/Like.php b/src/Query/Like.php deleted file mode 100644 index 917d780..0000000 --- a/src/Query/Like.php +++ /dev/null @@ -1,7 +0,0 @@ -validateFieldType($field); + $this->orderBy[] = [ $field, $direction ]; return $this; diff --git a/src/Query/Where.php b/src/Query/Where.php index d1db936..72e7b17 100644 --- a/src/Query/Where.php +++ b/src/Query/Where.php @@ -37,6 +37,9 @@ class Where extends Fragment { public function add($field, $value, string $operator, string $condition, bool $not = false) : self { + $this->validateFieldType($field); + # $this->validateValueType($value); + $this->conditionList[] = [ $field, $value, @@ -110,7 +113,8 @@ class Where extends Fragment { return (in_array($this->operator, [ '!=', '<>' ]) ? Where::CONDITION_NOT . " " : "") . Where::COMPARISON_IN; } - return $this->operator; + # whitelisting operators + return in_array(strtoupper($this->operator), [ '=', '!=', '<>', 'LIKE' ]) ? $this->operator : "="; } protected function value() diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index ec78463..7d30901 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -48,7 +48,7 @@ class QueryBuilder implements Query\QueryBuilderInterface } } - public function select($field) : self + public function select($field, bool $distinct = false) : self { if ( null !== ( $select = $this->getFragment(Query\Select::class) ) ) { $select->add($field); @@ -59,6 +59,8 @@ class QueryBuilder implements Query\QueryBuilderInterface $this->push($select); } + $select->distinct = $distinct; + return $this; } @@ -401,7 +403,7 @@ class QueryBuilder implements Query\QueryBuilderInterface public function getFragment(string $class, int $index = 0) : ? Query\Fragment { foreach($this->queryStack as $item) { - if ( get_class($item) === $class ) { + if ( is_a($item, $class, true) ) { if ( $index-- === 0 ) { return $item; } @@ -419,6 +421,11 @@ class QueryBuilder implements Query\QueryBuilderInterface } } } + + public function getFragments() : array + { + return $this->queryStack; + } public function __toString() : string { diff --git a/src/Repository.php b/src/Repository.php index 05c0f31..00ab211 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -31,7 +31,7 @@ class Repository $this->queryBuilder = new QueryBuilder(); } - public function __clone() + public function __clone() { #$this->queryBuilder = clone $this->queryBuilder; } @@ -285,6 +285,8 @@ class Repository $this->select("$alias.$key as {$prependField}{$field['name']}"); } } + + return $this; } public function selectJsonEntity(string $entity, string $alias, bool $skipFrom = false) : self @@ -307,9 +309,9 @@ class Repository ); } - public function select(/*array|Stringable*/ $fields) : self + public function select(/*array|Stringable*/ $fields, bool $distinct = false) : self { - $this->queryBuilder->select($fields); + $this->queryBuilder->select($fields, $distinct); return $this; } @@ -616,7 +618,7 @@ class Repository public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest, bool $count = true) : self { if ($count) { - $searchRequest->count = $searchRequest->filter(new Repository\ServerRequestCountRepository($this->entityClass, $this->alias, $this->adapter)) + $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()) @@ -632,6 +634,11 @@ class Repository ->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; @@ -646,7 +653,7 @@ class Repository $entityCollection->append( ( new $class() )->resetVirtualProperties()->entityFillFromDataset($entityData) ); } - $this->eventExecute(Event\RepositoryCollectionFromQueryInterface::class, $entityCollection); + $this->eventExecute(Event\Repository\CollectionFromQueryInterface::class, $entityCollection); return $entityCollection; } @@ -793,6 +800,11 @@ class Repository return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_SCHEMA); } + public function hasFilters() : bool + { + return isset($this->queryBuilder->where); + } + protected function matchEntity(object $entity) { return get_class($entity) === $this->entityClass; } diff --git a/src/Repository/MssqlRepository.php b/src/Repository/MssqlRepository.php index e1d1741..590fb47 100644 --- a/src/Repository/MssqlRepository.php +++ b/src/Repository/MssqlRepository.php @@ -7,7 +7,7 @@ use Ulmus\{ Repository, Query }; class MssqlRepository extends Repository { protected function finalizeQuery() : void - { + { if ( null !== $offset = $this->queryBuilder->getFragment(Query\Offset::class) ) { if ( null === $limit = $this->queryBuilder->getFragment(Query\Limit::class) ) { throw new \Exception("Your offset query fragment is missing a LIMIT value."); @@ -28,6 +28,7 @@ class MssqlRepository extends Repository { $this->queryBuilder->push($mssqlOffset); } elseif ( null !== $limit = $this->queryBuilder->getFragment(Query\Limit::class) ) { + if ( null !== $select = $this->queryBuilder->getFragment(Query\Select::class) ) { $select->top = $limit->limit; } diff --git a/src/SearchRequest/SearchRequestConstructorTrait.php b/src/SearchRequest/SearchRequestConstructorTrait.php new file mode 100644 index 0000000..db02cbf --- /dev/null +++ b/src/SearchRequest/SearchRequestConstructorTrait.php @@ -0,0 +1,22 @@ +fromArray($parameters); + } + + public function fromArray(array $data): self + { + foreach(array_filter($data, fn($e) => $e !== "") as $key => $value) { + $this->$key = $value; + } + + return $this; + } +} \ No newline at end of file