Skip to content

Commit

Permalink
Restore DB filters
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerych1984 committed Feb 23, 2024
1 parent e4db023 commit 5053f7d
Show file tree
Hide file tree
Showing 53 changed files with 1,845 additions and 339 deletions.
37 changes: 25 additions & 12 deletions src/AbstractQueryDataReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ abstract class AbstractQueryDataReader implements QueryDataReaderInterface
* @psalm-var array<TKey, TValue>|null
*/
private ?array $data = null;

/**
* @psalm-var non-negative-int|null
*/
private ?int $batchSize = 100;
private ?string $countParam = null;

Expand All @@ -62,9 +66,7 @@ public function __construct(
*/
public function getIterator(): Generator
{
if (is_array($this->data)) {
yield from $this->data;
} elseif ($this->batchSize === null) {
if ($this->batchSize === null) {
yield from $this->read();
} else {
$iterator = $this->getPreparedQuery()->each($this->batchSize);
Expand Down Expand Up @@ -104,7 +106,8 @@ public function count(): int

public function getPreparedQuery(): QueryInterface
{
$query = $this->applyFilter(clone $this->query);
$query = clone $this->query;

Check warning on line 109 in src/AbstractQueryDataReader.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "CloneRemoval": --- Original +++ New @@ @@ } public function getPreparedQuery() : QueryInterface { - $query = clone $this->query; + $query = $this->query; $query = $this->applyFilter($query); $query = $this->applyHaving($query); if ($this->limit) {
$query = $this->applyFilter($query);
$query = $this->applyHaving($query);

if ($this->limit) {
Expand All @@ -125,10 +128,9 @@ public function getPreparedQuery(): QueryInterface
protected function applyFilter(QueryInterface $query): QueryInterface
{
if ($this->filter !== null) {
$condition = $this->criteriaHandler->handle($this->filter->toCriteriaArray());
if ($condition !== null) {
$query = $query->andWhere($condition);
}
return $this->criteriaHandler
->getHandlerByOperator($this->filter)
->applyFilter($this->query, $this->filter, $this->criteriaHandler);
}

return $query;
Expand All @@ -137,10 +139,9 @@ protected function applyFilter(QueryInterface $query): QueryInterface
protected function applyHaving(QueryInterface $query): QueryInterface
{
if ($this->having !== null) {
$condition = $this->criteriaHandler->handle($this->having->toCriteriaArray());
if ($condition !== null) {
$query = $query->andHaving($condition);
}
return $this->criteriaHandler
->getHandlerByOperator($this->having)
->applyHaving($this->query, $this->having, $this->criteriaHandler);
}

return $query;
Expand All @@ -152,6 +153,10 @@ protected function applyHaving(QueryInterface $query): QueryInterface
*/
public function withOffset(int $offset): static
{
if ($this->offset === $offset) {
return $this;
}

$new = clone $this;
$new->data = null;
$new->offset = $offset;
Expand All @@ -169,6 +174,10 @@ public function withLimit(int $limit): static
throw new InvalidArgumentException('$limit must not be less than 0.');
}

if ($this->limit === $limit) {
return $this;
}

$new = clone $this;
$new->data = null;
$new->limit = $limit;
Expand Down Expand Up @@ -242,6 +251,10 @@ public function withBatchSize(?int $batchSize): static
throw new InvalidArgumentException('$batchSize cannot be less than 1.');
}

if ($this->batchSize === $batchSize) {
return $this;
}

$new = clone $this;
$new->batchSize = $batchSize;

Expand Down
68 changes: 29 additions & 39 deletions src/CriteriaHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,45 @@
use Yiisoft\Data\Db\FilterHandler\AllHandler;
use Yiisoft\Data\Db\FilterHandler\AnyHandler;
use Yiisoft\Data\Db\FilterHandler\BetweenHandler;
use Yiisoft\Data\Db\FilterHandler\Context;
use Yiisoft\Data\Db\FilterHandler\EqualsEmptyHandler;
use Yiisoft\Data\Db\FilterHandler\EqualsHandler;
use Yiisoft\Data\Db\FilterHandler\EqualsNullHandler;
use Yiisoft\Data\Db\FilterHandler\ExistsHandler;
use Yiisoft\Data\Db\FilterHandler\GreaterThanHandler;
use Yiisoft\Data\Db\FilterHandler\GreaterThanOrEqualHandler;
use Yiisoft\Data\Db\FilterHandler\ILikeHandler;
use Yiisoft\Data\Db\FilterHandler\InHandler;
use Yiisoft\Data\Db\FilterHandler\LessThanHandler;
use Yiisoft\Data\Db\FilterHandler\LessThanOrEqualHandler;
use Yiisoft\Data\Db\FilterHandler\LikeHandler;
use Yiisoft\Data\Db\FilterHandler\NotHandler;
use Yiisoft\Data\Db\FilterHandler\OrILikeHandler;
use Yiisoft\Data\Db\FilterHandler\OrLikeHandler;
use Yiisoft\Data\Db\FilterHandler\QueryHandlerInterface;
use Yiisoft\Data\Reader\FilterHandlerInterface;
use Yiisoft\Data\Reader\FilterInterface;
use Yiisoft\Db\Query\QueryPartsInterface;

use function array_merge;
use function array_unshift;

/**
* `CriteriaHandler` processes filter criteria array from {@see FilterInterface::toCriteriaArray()} into condition array
* that is used in {@see QueryPartsInterface::andWhere()} and {@see QueryPartsInterface::andHaving()}.
*/
final class CriteriaHandler
{
private Context $context;

/**
* @psalm-var array<string, QueryHandlerInterface>
*/
private array $handlers;

/**
* @param QueryHandlerInterface[]|null $handlers
* @param ValueNormalizerInterface|null $valueNormalizer
* @param QueryHandlerInterface ...$handlers
*/
public function __construct(
?array $handlers = null,
ValueNormalizerInterface $valueNormalizer = null,
QueryHandlerInterface ...$handlers
) {
if (empty($handlers)) {
if ($handlers === []) {
$handlers = [
new AllHandler(),
new AnyHandler(),
Expand All @@ -55,6 +56,9 @@ public function __construct(
new LessThanHandler(),
new LessThanOrEqualHandler(),
new LikeHandler(),
new ILikeHandler(),
new OrLikeHandler(),
new OrILikeHandler(),
new InHandler(),
new ExistsHandler(),
new NotHandler(),
Expand All @@ -64,64 +68,50 @@ public function __construct(
];
}

$this->handlers = $this->prepareHandlers($handlers);
$this->context = new Context($this, $valueNormalizer ?? new ValueNormalizer());
$this->handlers = $this->prepareHandlers(...$handlers);
}

public function withFilterHandlers(FilterHandlerInterface ...$handlers): self
public function withFilterHandlers(QueryHandlerInterface $handler, QueryHandlerInterface ...$handlers): self
{
foreach ($handlers as $handler) {
if (!$handler instanceof QueryHandlerInterface) {
throw new LogicException(
sprintf(
'Filter handler must implement "%s".',
QueryHandlerInterface::class,
)
);
}
}
/** @var QueryHandlerInterface[] $handlers */
array_unshift($handlers, $handler);

$new = clone $this;
$new->handlers = array_merge(
$this->handlers,
$this->prepareHandlers($handlers),
$this->prepareHandlers(...$handlers),
);
return $new;
}

public function handle(array $criteria): ?array
public function hasHandler(string|FilterInterface $operator): bool
{
if (!isset($criteria[0])) {
throw new LogicException('Incorrect criteria array.');
if ($operator instanceof FilterInterface) {
$operator = $operator::getOperator();
}

$operator = $criteria[0];
if (!is_string($operator)) {
throw new LogicException('Criteria operator must be a string.');
}

$operands = array_slice($criteria, 1);

return $this->getHandlerByOperator($operator)->getCondition($operands, $this->context);
return isset($this->handlers[$operator]);
}

private function getHandlerByOperator(string $operator): QueryHandlerInterface
public function getHandlerByOperator(string|FilterInterface $operator): QueryHandlerInterface
{
if (!isset($this->handlers[$operator])) {
if ($operator instanceof FilterInterface) {
$operator = $operator::getOperator();
}

if (!$this->hasHandler($operator)) {
throw new LogicException(sprintf('Operator "%s" is not supported', $operator));
}

return $this->handlers[$operator];
}

/**
* @param QueryHandlerInterface[] $handlers
* @param QueryHandlerInterface ...$handlers
*
* @return QueryHandlerInterface[]
* @psalm-return array<string, QueryHandlerInterface>
*/
private function prepareHandlers(array $handlers): array
private function prepareHandlers(QueryHandlerInterface ...$handlers): array
{
$result = [];
foreach ($handlers as $handler) {
Expand Down
15 changes: 15 additions & 0 deletions src/Filter/All.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Data\Db\Filter;

use Yiisoft\Data\Reader\Filter\All as FilterAll;

final class All extends GroupFilter
{
public static function getOperator(): string
{
return FilterAll::getOperator();
}
}
15 changes: 15 additions & 0 deletions src/Filter/Any.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Data\Db\Filter;

use Yiisoft\Data\Reader\Filter\Any as FilterAny;

final class Any extends GroupFilter
{
public static function getOperator(): string
{
return FilterAny::getOperator();
}
}
57 changes: 57 additions & 0 deletions src/Filter/Between.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Data\Db\Filter;

use Yiisoft\Data\Reader\Filter\Between as BetweenFilter;
use Yiisoft\Db\Expression\ExpressionInterface;

final class Between implements QueryFilterInterface
{
use ParamsTrait;

public function __construct(
private readonly string|ExpressionInterface $column,
private readonly mixed $min,
private readonly mixed $max,
array $params = []
) {
$this->params = $params;
}

public static function getOperator(): string
{
return BetweenFilter::getOperator();
}

private static function isEmpty(mixed $value): bool
{
return $value === null || $value === '';
}

public function toCriteriaArray(): array
{
$isMinEmpty = self::isEmpty($this->min);
$isMaxEmpty = self::isEmpty($this->max);

if (!$isMinEmpty && !$isMaxEmpty) {
return [
self::getOperator(),
$this->column,
$this->min,
$this->max,
];
}

if (!$isMinEmpty) {
return (new GreaterThanOrEqual($this->column, $this->min))->toCriteriaArray();
}

if (!$isMaxEmpty) {
return (new LessThanOrEqual($this->column, $this->max))->toCriteriaArray();
}

return [];
}
}
48 changes: 48 additions & 0 deletions src/Filter/CompareFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Data\Db\Filter;

use Yiisoft\Db\Expression\ExpressionInterface;

abstract class CompareFilter implements QueryFilterInterface
{
use ParamsTrait;

protected bool $ignoreNull = false;

/**
* @param ExpressionInterface|string $column
* @param mixed $value
* @param array $params
*/
public function __construct(
protected readonly string|ExpressionInterface $column,
protected mixed $value,
array $params = []
) {
$this->params = $params;
}

public function withIgnoreNull(bool $ignoreNull = true): static
{
if ($this->ignoreNull === $ignoreNull) {
return $this;
}

$new = clone $this;
$new->ignoreNull = $ignoreNull;

return $new;
}

public function toCriteriaArray(): array
{
if ($this->value === null) {
return $this->ignoreNull ? [] : (new EqualsNull($this->column))->toCriteriaArray();
}

return [static::getOperator(), $this->column , $this->value];
}
}
Loading

0 comments on commit 5053f7d

Please sign in to comment.