Added an filter constraint based on part tags.

This commit is contained in:
Jan Böhmer 2022-08-21 23:01:10 +02:00
parent 4d3ff7d7b5
commit 4ba58cc621
7 changed files with 234 additions and 4 deletions

View file

@ -0,0 +1,137 @@
<?php
namespace App\DataTables\Filters\Constraints\Part;
use App\DataTables\Filters\Constraints\AbstractConstraint;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
class TagsConstraint extends AbstractConstraint
{
public const ALLOWED_OPERATOR_VALUES = ['ANY', 'ALL', 'NONE'];
/**
* @var string|null The operator to use
*/
protected $operator;
/**
* @var string The value to compare to
*/
protected $value;
public function __construct(string $property, string $identifier = null, $value = null, string $operator = '')
{
parent::__construct($property, $identifier);
$this->value = $value;
$this->operator = $operator;
}
/**
* @return string
*/
public function getOperator(): ?string
{
return $this->operator;
}
/**
* @param string $operator
*/
public function setOperator(?string $operator): self
{
$this->operator = $operator;
return $this;
}
/**
* @return string
*/
public function getValue(): string
{
return $this->value;
}
/**
* @param string $value
*/
public function setValue(string $value): self
{
$this->value = $value;
return $this;
}
public function isEnabled(): bool
{
return $this->value !== null
&& !empty($this->operator);
}
/**
* Returns a list of tags based on the comma separated tags list
* @return string[]
*/
public function getTags(): array
{
return explode(',', trim($this->value, ','));
}
/**
* Builds an expression to query for a single tag
* @param QueryBuilder $queryBuilder
* @param string $tag
* @return Expr\Orx
*/
protected function getExpressionForTag(QueryBuilder $queryBuilder, string $tag): Expr\Orx
{
$tag_identifier_prefix = uniqid($this->identifier . '_', false);
$expr = $queryBuilder->expr();
$tmp = $expr->orX(
$expr->like($this->property, ':' . $tag_identifier_prefix . '_1'),
$expr->like($this->property, ':' . $tag_identifier_prefix . '_2'),
$expr->like($this->property, ':' . $tag_identifier_prefix . '_3'),
$expr->eq($this->property, ':' . $tag_identifier_prefix . '_4'),
);
//Set the parameters for the LIKE expression, in each variation of the tag (so with a comma, at the end, at the beginning, and on both ends, and equaling the tag)
$queryBuilder->setParameter($tag_identifier_prefix . '_1', '%,' . $tag . ',%');
$queryBuilder->setParameter($tag_identifier_prefix . '_2', '%,' . $tag);
$queryBuilder->setParameter($tag_identifier_prefix . '_3', $tag . ',%');
$queryBuilder->setParameter($tag_identifier_prefix . '_4', $tag);
return $tmp;
}
public function apply(QueryBuilder $queryBuilder): void
{
if(!$this->isEnabled()) {
return;
}
if(!in_array($this->operator, self::ALLOWED_OPERATOR_VALUES, true)) {
throw new \RuntimeException('Invalid operator '. $this->operator . ' provided. Valid operators are '. implode(', ', self::ALLOWED_OPERATOR_VALUES));
}
$tagsExpressions = [];
foreach ($this->getTags() as $tag) {
$tagsExpressions[] = $this->getExpressionForTag($queryBuilder, $tag);
}
if ($this->operator === 'ANY') {
$queryBuilder->andWhere($queryBuilder->expr()->orX(...$tagsExpressions));
return;
}
if ($this->operator === 'ALL') {
$queryBuilder->andWhere($queryBuilder->expr()->andX(...$tagsExpressions));
return;
}
if ($this->operator === 'NONE') {
$queryBuilder->andWhere($queryBuilder->expr()->not($queryBuilder->expr()->orX(...$tagsExpressions)));
return;
}
}
}

View file

@ -6,6 +6,7 @@ use App\DataTables\Filters\Constraints\BooleanConstraint;
use App\DataTables\Filters\Constraints\DateTimeConstraint;
use App\DataTables\Filters\Constraints\EntityConstraint;
use App\DataTables\Filters\Constraints\NumberConstraint;
use App\DataTables\Filters\Constraints\Part\TagsConstraint;
use App\DataTables\Filters\Constraints\TextConstraint;
use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
@ -33,6 +34,9 @@ class PartFilter implements FilterInterface
/** @var TextConstraint */
protected $comment;
/** @var TagsConstraint */
protected $tags;
/** @var NumberConstraint */
protected $minAmount;
@ -82,6 +86,7 @@ class PartFilter implements FilterInterface
$this->comment = new TextConstraint('part.comment');
$this->category = new EntityConstraint($nodesListBuilder, Category::class, 'part.category');
$this->footprint = new EntityConstraint($nodesListBuilder, Footprint::class, 'part.footprint');
$this->tags = new TagsConstraint('part.tags');
$this->favorite = new BooleanConstraint('part.favorite');
$this->needsReview = new BooleanConstraint('part.needs_review');
@ -239,7 +244,11 @@ class PartFilter implements FilterInterface
return $this->manufacturer_product_number;
}
/**
* @return TagsConstraint
*/
public function getTags(): TagsConstraint
{
return $this->tags;
}
}