mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-24 18:58:46 +02:00
Added an basic TextConstraint for part filtering.
This commit is contained in:
parent
f6239dfd50
commit
f8562f9622
9 changed files with 234 additions and 7 deletions
|
@ -5,7 +5,7 @@ namespace App\DataTables\Filters\Constraints;
|
|||
use App\DataTables\Filters\FilterInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
abstract class AbstractSimpleConstraint implements FilterInterface
|
||||
abstract class AbstractConstraint implements FilterInterface
|
||||
{
|
||||
use FilterTrait;
|
||||
|
||||
|
@ -20,6 +20,12 @@ abstract class AbstractSimpleConstraint implements FilterInterface
|
|||
protected $identifier;
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether this constraint is active or not. This should be decided accordingly to the value of the constraint
|
||||
* @return bool True if the constraint is active, false otherwise
|
||||
*/
|
||||
abstract public function isEnabled(): bool;
|
||||
|
||||
public function __construct(string $property, string $identifier = null)
|
||||
{
|
||||
$this->property = $property;
|
|
@ -5,7 +5,7 @@ namespace App\DataTables\Filters\Constraints;
|
|||
use App\DataTables\Filters\FilterInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
class BooleanConstraint extends AbstractSimpleConstraint
|
||||
class BooleanConstraint extends AbstractConstraint
|
||||
{
|
||||
/** @var bool|null The value of our constraint */
|
||||
protected $value;
|
||||
|
@ -34,12 +34,16 @@ class BooleanConstraint extends AbstractSimpleConstraint
|
|||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->value !== null;
|
||||
}
|
||||
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
//Do not apply a filter if value is null (filter is set to ignore)
|
||||
if($this->value === null) {
|
||||
if(!$this->isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace App\DataTables\Filters\Constraints;
|
|||
use Doctrine\ORM\QueryBuilder;
|
||||
use \RuntimeException;
|
||||
|
||||
class NumberConstraint extends AbstractSimpleConstraint
|
||||
class NumberConstraint extends AbstractConstraint
|
||||
{
|
||||
public const ALLOWED_OPERATOR_VALUES = ['=', '!=', '<', '>', '<=', '>=', 'BETWEEN'];
|
||||
|
||||
|
@ -62,7 +62,7 @@ class NumberConstraint extends AbstractSimpleConstraint
|
|||
/**
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function getOperator()
|
||||
public function getOperator(): string
|
||||
{
|
||||
return $this->operator;
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ class NumberConstraint extends AbstractSimpleConstraint
|
|||
/**
|
||||
* @param mixed|string $operator
|
||||
*/
|
||||
public function setOperator($operator): void
|
||||
public function setOperator(?string $operator): void
|
||||
{
|
||||
$this->operator = $operator;
|
||||
}
|
||||
|
@ -84,6 +84,12 @@ class NumberConstraint extends AbstractSimpleConstraint
|
|||
$this->operator = $operator;
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->value1 !== null
|
||||
&& !empty($this->operator);
|
||||
}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
//If no value is provided then we do not apply a filter
|
||||
|
@ -93,7 +99,7 @@ class NumberConstraint extends AbstractSimpleConstraint
|
|||
|
||||
//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));
|
||||
throw new \RuntimeException('Invalid operator '. $this->operator . ' provided. Valid operators are '. implode(', ', self::ALLOWED_OPERATOR_VALUES));
|
||||
}
|
||||
|
||||
if ($this->operator !== 'BETWEEN') {
|
||||
|
|
109
src/DataTables/Filters/Constraints/TextConstraint.php
Normal file
109
src/DataTables/Filters/Constraints/TextConstraint.php
Normal file
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
namespace App\DataTables\Filters\Constraints;
|
||||
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
class TextConstraint extends AbstractConstraint
|
||||
{
|
||||
|
||||
public const ALLOWED_OPERATOR_VALUES = ['=', '!=', 'STARTS', 'ENDS', 'CONTAINS', 'LIKE', 'REGEX'];
|
||||
|
||||
/**
|
||||
* @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, $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);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
//Equal and not equal can be handled easily
|
||||
if($this->operator === '=' || $this->operator === '!=') {
|
||||
|
||||
$this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, $this->operator, $this->value);
|
||||
return;
|
||||
}
|
||||
|
||||
//The CONTAINS, LIKE, STARTS and ENDS operators use the LIKE operator but we have to build the value string differently
|
||||
$like_value = null;
|
||||
if ($this->operator === 'LIKE') {
|
||||
$like_value = $this->value;
|
||||
} else if ($this->operator === 'STARTS') {
|
||||
$like_value = $this->value . '%';
|
||||
} else if ($this->operator === 'ENDS') {
|
||||
$like_value = '%' . $this->value;
|
||||
} else if ($this->operator === 'CONTAINS') {
|
||||
$like_value = '%' . $this->value . '%';
|
||||
}
|
||||
|
||||
if ($like_value !== null) {
|
||||
$this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, 'LIKE', $like_value);
|
||||
return;
|
||||
}
|
||||
|
||||
//Regex is only supported on MySQL and needs a special function
|
||||
if ($this->operator === 'REGEX') {
|
||||
$queryBuilder->andWhere(sprintf('REGEXP(%s, :%s) = 1', $this->property, $this->identifier));
|
||||
$queryBuilder->setParameter($this->identifier, $this->value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,10 +4,17 @@ namespace App\DataTables\Filters;
|
|||
|
||||
use App\DataTables\Filters\Constraints\BooleanConstraint;
|
||||
use App\DataTables\Filters\Constraints\NumberConstraint;
|
||||
use App\DataTables\Filters\Constraints\TextConstraint;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
class PartFilter implements FilterInterface
|
||||
{
|
||||
/** @var TextConstraint */
|
||||
protected $name;
|
||||
|
||||
/** @var TextConstraint */
|
||||
protected $description;
|
||||
|
||||
/** @var BooleanConstraint */
|
||||
protected $favorite;
|
||||
|
||||
|
@ -38,11 +45,23 @@ class PartFilter implements FilterInterface
|
|||
return $this->mass;
|
||||
}
|
||||
|
||||
public function getName(): TextConstraint
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getDescription(): TextConstraint
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->favorite = new BooleanConstraint('part.favorite');
|
||||
$this->needsReview = new BooleanConstraint('part.needs_review');
|
||||
$this->mass = new NumberConstraint('part.mass');
|
||||
$this->name = new TextConstraint('part.name');
|
||||
$this->description = new TextConstraint('part.description');
|
||||
}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
|
@ -50,5 +69,7 @@ class PartFilter implements FilterInterface
|
|||
$this->favorite->apply($queryBuilder);
|
||||
$this->needsReview->apply($queryBuilder);
|
||||
$this->mass->apply($queryBuilder);
|
||||
$this->name->apply($queryBuilder);
|
||||
$this->description->apply($queryBuilder);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ class NumberConstraintType extends AbstractType
|
|||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$choices = [
|
||||
'' => '',
|
||||
'=' => '=',
|
||||
'!=' => '!=',
|
||||
'<' => '<',
|
||||
|
@ -68,6 +69,7 @@ class NumberConstraintType extends AbstractType
|
|||
$builder->add('operator', ChoiceType::class, [
|
||||
'label' => 'filter.number_constraint.operator',
|
||||
'choices' => $choices,
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
60
src/Form/Filters/Constraints/TextConstraintType.php
Normal file
60
src/Form/Filters/Constraints/TextConstraintType.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form\Filters\Constraints;
|
||||
|
||||
use App\DataTables\Filters\Constraints\TextConstraint;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SearchType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TextConstraintType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'compound' => true,
|
||||
'data_class' => TextConstraint::class,
|
||||
'text_suffix' => '', // An suffix which is attached as text-append to the input group. This can for example be used for units
|
||||
]);
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$choices = [
|
||||
'' => '',
|
||||
'=' => '=',
|
||||
'!=' => '!=',
|
||||
'STARTS' => 'STARTS',
|
||||
'ENDS' => 'ENDS',
|
||||
'CONTAINS' => 'CONTAINS',
|
||||
'LIKE' => 'LIKE',
|
||||
'REGEX' => 'REGEX',
|
||||
];
|
||||
|
||||
$builder->add('value', SearchType::class, [
|
||||
'attr' => [
|
||||
'placeholder' => 'filter.text_constraint.value',
|
||||
],
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
|
||||
$builder->add('operator', ChoiceType::class, [
|
||||
'label' => 'filter.text_constraint.operator',
|
||||
'choices' => $choices,
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
parent::buildView($view, $form, $options);
|
||||
|
||||
$view->vars['text_suffix'] = $options['text_suffix'];
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ namespace App\Form\Filters;
|
|||
use App\DataTables\Filters\PartFilter;
|
||||
use App\Form\Filters\Constraints\BooleanConstraintType;
|
||||
use App\Form\Filters\Constraints\NumberConstraintType;
|
||||
use App\Form\Filters\Constraints\TextConstraintType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
@ -38,6 +39,14 @@ class PartFilterType extends AbstractType
|
|||
'min' => 0,
|
||||
]);
|
||||
|
||||
$builder->add('name', TextConstraintType::class, [
|
||||
'label' => 'part.edit.name',
|
||||
]);
|
||||
|
||||
$builder->add('description', TextConstraintType::class, [
|
||||
'label' => 'part.edit.description',
|
||||
]);
|
||||
|
||||
$builder->add('submit', SubmitType::class, [
|
||||
'label' => 'Update',
|
||||
]);
|
||||
|
|
|
@ -8,4 +8,14 @@
|
|||
<span class="input-group-text">{{ form.vars["text_suffix"] }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block text_constraint_widget %}
|
||||
<div class="input-group">
|
||||
{{ form_widget(form.operator, {"attr": {"class": "form-select"}}) }}
|
||||
{{ form_widget(form.value) }}
|
||||
{% if form.vars["text_suffix"] %}
|
||||
<span class="input-group-text">{{ form.vars["text_suffix"] }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Add table
Add a link
Reference in a new issue