mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-29 21:14:29 +02:00
Added the very basic foundations for a filter system
This commit is contained in:
parent
ef389dcc15
commit
f9d945c4c7
15 changed files with 460 additions and 5 deletions
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\DataTables\Filters\Constraints;
|
||||
|
||||
use App\DataTables\Filters\FilterInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
abstract class AbstractSimpleConstraint implements FilterInterface
|
||||
{
|
||||
use FilterTrait;
|
||||
|
||||
/**
|
||||
* @var string The property where this BooleanConstraint should apply to
|
||||
*/
|
||||
protected $property;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $identifier;
|
||||
|
||||
|
||||
public function __construct(string $property, string $identifier = null)
|
||||
{
|
||||
$this->property = $property;
|
||||
$this->identifier = $identifier ?? $this->generateParameterIdentifier($property);
|
||||
}
|
||||
}
|
48
src/DataTables/Filters/Constraints/BooleanConstraint.php
Normal file
48
src/DataTables/Filters/Constraints/BooleanConstraint.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\DataTables\Filters\Constraints;
|
||||
|
||||
use App\DataTables\Filters\FilterInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
class BooleanConstraint extends AbstractSimpleConstraint
|
||||
{
|
||||
/** @var bool|null The value of our constraint */
|
||||
protected $value;
|
||||
|
||||
|
||||
public function __construct(string $property, string $identifier = null, ?bool $default_value = null)
|
||||
{
|
||||
parent::__construct($property, $identifier, $default_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of this constraint. Null means "don't filter", true means "filter for true", false means "filter for false".
|
||||
* @return bool|null
|
||||
*/
|
||||
public function getValue(): ?bool
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of this constraint. Null means "don't filter", true means "filter for true", false means "filter for false".
|
||||
* @param bool|null $value
|
||||
*/
|
||||
public function setValue(?bool $value): void
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
//Do not apply a filter if value is null (filter is set to ignore)
|
||||
if($this->value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, '=', $this->value);
|
||||
}
|
||||
}
|
33
src/DataTables/Filters/Constraints/FilterTrait.php
Normal file
33
src/DataTables/Filters/Constraints/FilterTrait.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\DataTables\Filters\Constraints;
|
||||
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
trait FilterTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* Generates a parameter identifier that can be used for the given property. It gives random results, to be unique, so you have to cache it.
|
||||
* @param string $property
|
||||
* @return string
|
||||
*/
|
||||
protected function generateParameterIdentifier(string $property): string
|
||||
{
|
||||
return str_replace('.', '_', $property) . '_' . uniqid("", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a simple constraint in the form of (property OPERATOR value) (e.g. "part.name = :name") to the given query builder.
|
||||
* @param QueryBuilder $queryBuilder
|
||||
* @param string $property
|
||||
* @param string $comparison_operator
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
protected function addSimpleAndConstraint(QueryBuilder $queryBuilder, string $property, string $parameterIdentifier, string $comparison_operator, $value): void
|
||||
{
|
||||
$queryBuilder->andWhere(sprintf("%s %s :%s", $property, $comparison_operator, $parameterIdentifier));
|
||||
$queryBuilder->setParameter($parameterIdentifier, $value);
|
||||
}
|
||||
}
|
110
src/DataTables/Filters/Constraints/NumberConstraint.php
Normal file
110
src/DataTables/Filters/Constraints/NumberConstraint.php
Normal file
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
namespace App\DataTables\Filters\Constraints;
|
||||
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use \RuntimeException;
|
||||
|
||||
class NumberConstraint extends AbstractSimpleConstraint
|
||||
{
|
||||
public const ALLOWED_OPERATOR_VALUES = ['=', '!=', '<', '>', '<=', '>=', 'BETWEEN'];
|
||||
|
||||
|
||||
/**
|
||||
* The value1 used for comparison (this is the main one used for all mono-value comparisons)
|
||||
* @var float|null
|
||||
*/
|
||||
protected $value1;
|
||||
|
||||
/**
|
||||
* The second value used when operator is RANGE; this is the upper bound of the range
|
||||
* @var float|null
|
||||
*/
|
||||
protected $value2;
|
||||
|
||||
/**
|
||||
* @var string The operator to use
|
||||
*/
|
||||
protected $operator;
|
||||
|
||||
/**
|
||||
* @return float|mixed|null
|
||||
*/
|
||||
public function getValue1()
|
||||
{
|
||||
return $this->value1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float|mixed|null $value1
|
||||
*/
|
||||
public function setValue1($value1): void
|
||||
{
|
||||
$this->value1 = $value1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float|mixed|null
|
||||
*/
|
||||
public function getValue2()
|
||||
{
|
||||
return $this->value2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float|mixed|null $value2
|
||||
*/
|
||||
public function setValue2($value2): void
|
||||
{
|
||||
$this->value2 = $value2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function getOperator()
|
||||
{
|
||||
return $this->operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed|string $operator
|
||||
*/
|
||||
public function setOperator($operator): void
|
||||
{
|
||||
$this->operator = $operator;
|
||||
}
|
||||
|
||||
|
||||
public function __construct(string $property, string $identifier = null, $value1 = null, $operator = '>', $value2 = null)
|
||||
{
|
||||
parent::__construct($property, $identifier);
|
||||
$this->value1 = $value1;
|
||||
$this->value2 = $value2;
|
||||
$this->operator = $operator;
|
||||
}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
//If no value is provided then we do not apply a filter
|
||||
if ($this->value1 === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Ensure we have an valid operator
|
||||
if(!in_array($this->operator, self::ALLOWED_OPERATOR_VALUES, true)) {
|
||||
throw new \InvalidArgumentException('Invalid operator '. $this->operator . ' provided. Valid operators are '. implode(', ', self::ALLOWED_OPERATOR_VALUES));
|
||||
}
|
||||
|
||||
if ($this->operator !== 'BETWEEN') {
|
||||
$this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, $this->operator, $this->value1);
|
||||
} else {
|
||||
if ($this->value2 === null) {
|
||||
throw new RuntimeException("Cannot use operator BETWEEN without value2!");
|
||||
}
|
||||
|
||||
$this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier . '1', '>=', $this->value1);
|
||||
$this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier . '2', '<=', $this->value2);
|
||||
}
|
||||
}
|
||||
}
|
16
src/DataTables/Filters/FilterInterface.php
Normal file
16
src/DataTables/Filters/FilterInterface.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\DataTables\Filters;
|
||||
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
interface FilterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Apply the given filter to the given query builder on the given property
|
||||
* @param QueryBuilder $queryBuilder
|
||||
* @return void
|
||||
*/
|
||||
public function apply(QueryBuilder $queryBuilder): void;
|
||||
}
|
54
src/DataTables/Filters/PartFilter.php
Normal file
54
src/DataTables/Filters/PartFilter.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace App\DataTables\Filters;
|
||||
|
||||
use App\DataTables\Filters\Constraints\BooleanConstraint;
|
||||
use App\DataTables\Filters\Constraints\NumberConstraint;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
class PartFilter implements FilterInterface
|
||||
{
|
||||
/** @var BooleanConstraint */
|
||||
protected $favorite;
|
||||
|
||||
/** @var BooleanConstraint */
|
||||
protected $needsReview;
|
||||
|
||||
/** @var NumberConstraint */
|
||||
protected $mass;
|
||||
|
||||
/**
|
||||
* @return BooleanConstraint|false
|
||||
*/
|
||||
public function getFavorite()
|
||||
{
|
||||
return $this->favorite;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BooleanConstraint
|
||||
*/
|
||||
public function getNeedsReview(): BooleanConstraint
|
||||
{
|
||||
return $this->needsReview;
|
||||
}
|
||||
|
||||
public function getMass(): NumberConstraint
|
||||
{
|
||||
return $this->mass;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->favorite = new BooleanConstraint('part.favorite');
|
||||
$this->needsReview = new BooleanConstraint('part.needs_review');
|
||||
$this->mass = new NumberConstraint('part.mass');
|
||||
}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$this->favorite->apply($queryBuilder);
|
||||
$this->needsReview->apply($queryBuilder);
|
||||
$this->mass->apply($queryBuilder);
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@ use App\DataTables\Column\LocaleDateTimeColumn;
|
|||
use App\DataTables\Column\MarkdownColumn;
|
||||
use App\DataTables\Column\PartAttachmentsColumn;
|
||||
use App\DataTables\Column\TagsColumn;
|
||||
use App\DataTables\Filters\PartFilter;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
|
@ -108,6 +109,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
'supplier' => null,
|
||||
'tag' => null,
|
||||
'search' => null,
|
||||
'filter' => null
|
||||
]);
|
||||
|
||||
$optionsResolver->setAllowedTypes('category', ['null', Category::class]);
|
||||
|
@ -351,6 +353,13 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
|
||||
private function buildCriteria(QueryBuilder $builder, array $options): void
|
||||
{
|
||||
|
||||
if (isset($options['filter']) && $options['filter'] instanceof PartFilter) {
|
||||
$filter = $options['filter'];
|
||||
|
||||
$filter->apply($builder);
|
||||
}
|
||||
|
||||
if (isset($options['category'])) {
|
||||
$category = $options['category'];
|
||||
$list = $this->treeBuilder->typeToNodesList(Category::class, $category);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue