Allow to filter parameters based on their text value

This commit is contained in:
Jan Böhmer 2022-09-07 21:43:01 +02:00
parent 9ed953d1b2
commit bc0365fe16
4 changed files with 78 additions and 10 deletions

View file

@ -3,8 +3,10 @@
namespace App\DataTables\Filters\Constraints\Part; namespace App\DataTables\Filters\Constraints\Part;
use App\DataTables\Filters\Constraints\AbstractConstraint; use App\DataTables\Filters\Constraints\AbstractConstraint;
use App\DataTables\Filters\Constraints\TextConstraint;
use App\Entity\Parameters\PartParameter; use App\Entity\Parameters\PartParameter;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Svg\Tag\Text;
class ParameterConstraint extends AbstractConstraint class ParameterConstraint extends AbstractConstraint
{ {
@ -17,9 +19,20 @@ class ParameterConstraint extends AbstractConstraint
/** @var string */ /** @var string */
protected $unit; protected $unit;
/** @var TextConstraint */
protected $value_text;
/** @var string The alias to use for the subquery */
protected $alias;
public function __construct() public function __construct()
{ {
parent::__construct("parts.parameters"); parent::__construct("parts.parameters");
//The alias has to be uniq for each subquery, so generate a random one
$this->alias = uniqid('param_', false);
$this->value_text = new TextConstraint($this->alias . '.value_text');
} }
public function isEnabled(): bool public function isEnabled(): bool
@ -32,31 +45,39 @@ class ParameterConstraint extends AbstractConstraint
//Create a new qb to build the subquery //Create a new qb to build the subquery
$subqb = new QueryBuilder($queryBuilder->getEntityManager()); $subqb = new QueryBuilder($queryBuilder->getEntityManager());
//The alias has to be uniq for each subquery, so generate a random one
$alias = uniqid('param_', false);
$subqb->select('COUNT(' . $alias . ')')
->from(PartParameter::class, $alias) $subqb->select('COUNT(' . $this->alias . ')')
->where($alias . '.element = part'); ->from(PartParameter::class, $this->alias)
->where($this->alias . '.element = part');
if (!empty($this->name)) { if (!empty($this->name)) {
$paramName = $this->generateParameterIdentifier('params.name'); $paramName = $this->generateParameterIdentifier('params.name');
$subqb->andWhere($alias . '.name = :' . $paramName); $subqb->andWhere($this->alias . '.name = :' . $paramName);
$queryBuilder->setParameter($paramName, $this->name); $queryBuilder->setParameter($paramName, $this->name);
} }
if (!empty($this->symbol)) { if (!empty($this->symbol)) {
$paramName = $this->generateParameterIdentifier('params.symbol'); $paramName = $this->generateParameterIdentifier('params.symbol');
$subqb->andWhere($alias . '.symbol = :' . $paramName); $subqb->andWhere($this->alias . '.symbol = :' . $paramName);
$queryBuilder->setParameter($paramName, $this->symbol); $queryBuilder->setParameter($paramName, $this->symbol);
} }
if (!empty($this->unit)) { if (!empty($this->unit)) {
$paramName = $this->generateParameterIdentifier('params.unit'); $paramName = $this->generateParameterIdentifier('params.unit');
$subqb->andWhere($alias . '.unit = :' . $paramName); $subqb->andWhere($this->alias . '.unit = :' . $paramName);
$queryBuilder->setParameter($paramName, $this->unit); $queryBuilder->setParameter($paramName, $this->unit);
} }
//Apply all subfilters
$this->value_text->apply($subqb);
//Copy all parameters from the subquery to the main query
//We can not use setParameters here, as this would override the exiting paramaters in queryBuilder
foreach ($subqb->getParameters() as $parameter) {
$queryBuilder->setParameter($parameter->getName(), $parameter->getValue());
}
$queryBuilder->andWhere('(' . $subqb->getDQL() . ') > 0'); $queryBuilder->andWhere('(' . $subqb->getDQL() . ') > 0');
} }
@ -113,4 +134,24 @@ class ParameterConstraint extends AbstractConstraint
$this->unit = $unit; $this->unit = $unit;
return $this; return $this;
} }
/**
* @return TextConstraint
*/
public function getValueText(): TextConstraint
{
return $this->value_text;
}
/**
* DO NOT USE THIS SETTER!
* This is just a workaround for collectionType behavior
* @param $value
* @return $this
*/
/*public function setValueText($value): self
{
//Do not really set the value here, as we dont want to override the constraint created in the constructor
return $this;
}*/
} }

View file

@ -8,6 +8,8 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SearchType; use Symfony\Component\Form\Extension\Core\Type\SearchType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
class ParameterConstraintType extends AbstractType class ParameterConstraintType extends AbstractType
@ -17,6 +19,7 @@ class ParameterConstraintType extends AbstractType
$resolver->setDefaults([ $resolver->setDefaults([
'compound' => true, 'compound' => true,
'data_class' => ParameterConstraint::class, 'data_class' => ParameterConstraint::class,
'empty_data' => new ParameterConstraint(),
]); ]);
} }
@ -31,7 +34,26 @@ class ParameterConstraintType extends AbstractType
]); ]);
$builder->add('symbol', SearchType::class, [ $builder->add('symbol', SearchType::class, [
'required' => false 'required' => false
]); ]);
$builder->add('value_text', TextConstraintType::class, [
//'required' => false,
] );
/*
* I am not quite sure why this is needed, but somehow symfony tries to create a new instance of TextConstraint
* instead of using the existing one for the prototype (or the one from empty data). This fails as the constructor of TextConstraint requires
* arguments.
* Ensure that the data is never null, but use an empty ParameterConstraint instead
*/
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
if ($data === null) {
$event->setData(new ParameterConstraint());
}
});
} }
} }

View file

@ -2,6 +2,7 @@
namespace App\Form\Filters; namespace App\Form\Filters;
use App\DataTables\Filters\Constraints\Part\ParameterConstraint;
use App\DataTables\Filters\PartFilter; use App\DataTables\Filters\PartFilter;
use App\Entity\Attachments\AttachmentType; use App\Entity\Attachments\AttachmentType;
use App\Entity\Parts\Category; use App\Entity\Parts\Category;
@ -211,12 +212,16 @@ class PartFilterType extends AbstractType
'label' => 'part.filter.attachmentName', 'label' => 'part.filter.attachmentName',
]); ]);
$constraint_prototype = new ParameterConstraint();
$builder->add('parameters', CollectionType::class, [ $builder->add('parameters', CollectionType::class, [
'label' => 'parameter.label', 'label' => 'parameter.label',
'entry_type' => ParameterConstraintType::class, 'entry_type' => ParameterConstraintType::class,
'allow_delete' => true, 'allow_delete' => true,
'allow_add' => true, 'allow_add' => true,
'reindex_enable' => false, 'reindex_enable' => false,
'prototype_data' => $constraint_prototype,
'empty_data' => $constraint_prototype,
]); ]);
$builder->add('submit', SubmitType::class, [ $builder->add('submit', SubmitType::class, [

View file

@ -47,7 +47,7 @@
<td {{ stimulus_controller('pages/latex_preview') }}>{{ form_widget(form.symbol, {"attr": {"data-pages--parameters-autocomplete-target": "symbol", "data-pages--latex-preview-target": "input"}}) }}<span {{ stimulus_target('pages/latex_preview', 'preview') }}></span></td> <td {{ stimulus_controller('pages/latex_preview') }}>{{ form_widget(form.symbol, {"attr": {"data-pages--parameters-autocomplete-target": "symbol", "data-pages--latex-preview-target": "input"}}) }}<span {{ stimulus_target('pages/latex_preview', 'preview') }}></span></td>
<td></td> <td></td>
<td {{ stimulus_controller('pages/latex_preview') }}>{{ form_widget(form.unit, {"attr": {"data-pages--parameters-autocomplete-target": "unit", "data-pages--latex-preview-target": "input"}}) }}<span {{ stimulus_target('pages/latex_preview', 'preview') }}></span></td> <td {{ stimulus_controller('pages/latex_preview') }}>{{ form_widget(form.unit, {"attr": {"data-pages--parameters-autocomplete-target": "unit", "data-pages--latex-preview-target": "input"}}) }}<span {{ stimulus_target('pages/latex_preview', 'preview') }}></span></td>
<td></td> <td>{{ form_widget(form.value_text) }}</td>
<td> <td>
<button type="button" class="btn btn-danger btn-sm" {{ collection.delete_btn() }} title="{% trans %}orderdetail.delete{% endtrans %}"> <button type="button" class="btn btn-danger btn-sm" {{ collection.delete_btn() }} title="{% trans %}orderdetail.delete{% endtrans %}">
<i class="fas fa-trash-alt fa-fw"></i> <i class="fas fa-trash-alt fa-fw"></i>