From 852624ae7e95a3faa8c318a728107cadf04792a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 3 Oct 2023 21:59:33 +0200 Subject: [PATCH] Added filter to filter parts by storage location --- src/ApiPlatform/Filter/EntityFilter.php | 66 +---------- src/ApiPlatform/Filter/EntityFilterHelper.php | 104 ++++++++++++++++++ .../Filter/PartStoragelocationFilter.php | 83 ++++++++++++++ src/Entity/Parts/Part.php | 2 + 4 files changed, 192 insertions(+), 63 deletions(-) create mode 100644 src/ApiPlatform/Filter/EntityFilterHelper.php create mode 100644 src/ApiPlatform/Filter/PartStoragelocationFilter.php diff --git a/src/ApiPlatform/Filter/EntityFilter.php b/src/ApiPlatform/Filter/EntityFilter.php index a8b250ad..e3972732 100644 --- a/src/ApiPlatform/Filter/EntityFilter.php +++ b/src/ApiPlatform/Filter/EntityFilter.php @@ -40,8 +40,7 @@ class EntityFilter extends AbstractFilter public function __construct( ManagerRegistry $managerRegistry, - private NodesListBuilder $nodesListBuilder, - private EntityManagerInterface $entityManager, + private readonly EntityFilterHelper $filter_helper, LoggerInterface $logger = null, ?array $properties = null, ?NameConverterInterface $nameConverter = null @@ -72,7 +71,7 @@ class EntityFilter extends AbstractFilter return; } - $elements = $this->valueToEntityArray($value, $target_class); + $elements = $this->filter_helper->valueToEntityArray($value, $target_class); $parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters $queryBuilder @@ -80,69 +79,10 @@ class EntityFilter extends AbstractFilter ->setParameter($parameterName, $elements); } - private function valueToEntityArray(string $value, string $target_class): array - { - //Convert value to IDs: - $elements = []; - //Split the given value by comm - foreach (explode(',', $value) as $id) { - if (trim($id) === '') { - continue; - } - - //Check if the given value ends with a plus, then we want to include all direct children - $include_children = false; - $include_recursive = false; - if (str_ends_with($id, '++')) { //Plus Plus means include all children recursively - $id = substr($id, 0, -2); - $include_recursive = true; - } elseif (str_ends_with($id, '+')) { - $id = substr($id, 0, -1); - $include_children = true; - } - - //Get a (shallow) reference to the entitity - $element = $this->entityManager->getReference($target_class, (int) $id); - $elements[] = $element; - - //If $element is not structural we are done - if (!is_a($element, AbstractStructuralDBElement::class)) { - continue; - } - - //Get the recursive list of children - if ($include_recursive) { - $elements = array_merge($elements, $this->nodesListBuilder->getChildrenFlatList($element)); - } elseif ($include_children) { - $elements = array_merge($elements, $element->getChildren()->toArray()); - } - } - - return $elements; - } public function getDescription(string $resourceClass): array { - if (!$this->properties) { - return []; - } - - $description = []; - foreach ($this->properties as $property => $strategy) { - $description["$property"] = [ - 'property' => $property, - 'type' => Type::BUILTIN_TYPE_STRING, - 'required' => false, - 'description' => 'Filter using a comma seperated list of element IDs. Use + to include all direct children and ++ to include all children recursively.', - 'openapi' => [ - 'example' => '', - 'allowReserved' => false,// if true, query parameters will be not percent-encoded - 'allowEmptyValue' => true, - 'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green - ], - ]; - } - return $description; + return $this->filter_helper->getDescription($this->properties); } } \ No newline at end of file diff --git a/src/ApiPlatform/Filter/EntityFilterHelper.php b/src/ApiPlatform/Filter/EntityFilterHelper.php new file mode 100644 index 00000000..6c729e1e --- /dev/null +++ b/src/ApiPlatform/Filter/EntityFilterHelper.php @@ -0,0 +1,104 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform\Filter; + +use App\Entity\Base\AbstractStructuralDBElement; +use App\Services\Trees\NodesListBuilder; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\PropertyInfo\Type; + +class EntityFilterHelper +{ + public function __construct(private NodesListBuilder $nodesListBuilder, + private EntityManagerInterface $entityManager) + { + + } + + public function valueToEntityArray(string $value, string $target_class): array + { + //Convert value to IDs: + $elements = []; + + //Split the given value by comm + foreach (explode(',', $value) as $id) { + if (trim($id) === '') { + continue; + } + + //Check if the given value ends with a plus, then we want to include all direct children + $include_children = false; + $include_recursive = false; + if (str_ends_with($id, '++')) { //Plus Plus means include all children recursively + $id = substr($id, 0, -2); + $include_recursive = true; + } elseif (str_ends_with($id, '+')) { + $id = substr($id, 0, -1); + $include_children = true; + } + + //Get a (shallow) reference to the entitity + $element = $this->entityManager->getReference($target_class, (int) $id); + $elements[] = $element; + + //If $element is not structural we are done + if (!is_a($element, AbstractStructuralDBElement::class)) { + continue; + } + + //Get the recursive list of children + if ($include_recursive) { + $elements = array_merge($elements, $this->nodesListBuilder->getChildrenFlatList($element)); + } elseif ($include_children) { + $elements = array_merge($elements, $element->getChildren()->toArray()); + } + } + + return $elements; + } + + public function getDescription(array $properties): array + { + if (!$properties) { + return []; + } + + $description = []; + foreach ($properties as $property => $strategy) { + $description["$property"] = [ + 'property' => $property, + 'type' => Type::BUILTIN_TYPE_STRING, + 'required' => false, + 'description' => 'Filter using a comma seperated list of element IDs. Use + to include all direct children and ++ to include all children recursively.', + 'openapi' => [ + 'example' => '', + 'allowReserved' => false,// if true, query parameters will be not percent-encoded + 'allowEmptyValue' => true, + 'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green + ], + ]; + } + return $description; + } +} \ No newline at end of file diff --git a/src/ApiPlatform/Filter/PartStoragelocationFilter.php b/src/ApiPlatform/Filter/PartStoragelocationFilter.php new file mode 100644 index 00000000..0e942208 --- /dev/null +++ b/src/ApiPlatform/Filter/PartStoragelocationFilter.php @@ -0,0 +1,83 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform\Filter; + +use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; +use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\Operation; +use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\Parts\StorageLocation; +use App\Services\Trees\NodesListBuilder; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\QueryBuilder; +use Doctrine\Persistence\ManagerRegistry; +use Psr\Log\LoggerInterface; +use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; + +class PartStoragelocationFilter extends AbstractFilter +{ + + public function __construct( + ManagerRegistry $managerRegistry, + private readonly EntityFilterHelper $filter_helper, + LoggerInterface $logger = null, + ?array $properties = null, + ?NameConverterInterface $nameConverter = null + ) { + parent::__construct($managerRegistry, $logger, $properties, $nameConverter); + } + + protected function filterProperty( + string $property, + $value, + QueryBuilder $queryBuilder, + QueryNameGeneratorInterface $queryNameGenerator, + string $resourceClass, + Operation $operation = null, + array $context = [] + ): void { + //Do not check for mapping here, as we are using a virtual property + if ( + !$this->isPropertyEnabled($property, $resourceClass) + ) { + return; + } + + $elements = $this->filter_helper->valueToEntityArray($value, StorageLocation::class); + + $parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters + $queryBuilder + ->leftJoin('o.partLots', 'partLots') + ->andWhere(sprintf('partLots.storage_location IN (:%s)', $parameterName)) + ->setParameter($parameterName, $elements); + } + + + + public function getDescription(string $resourceClass): array + { + return $this->filter_helper->getDescription($this->properties); + } +} \ No newline at end of file diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index aa4a9a17..d53ac8f6 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -39,6 +39,7 @@ use ApiPlatform\Serializer\Filter\PropertyFilter; use App\ApiPlatform\DocumentedAPIProperty; use App\ApiPlatform\Filter\EntityFilter; use App\ApiPlatform\Filter\LikeFilter; +use App\ApiPlatform\Filter\PartStoragelocationFilter; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\PartRepository; use Doctrine\DBAL\Types\Types; @@ -93,6 +94,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; )] #[ApiFilter(PropertyFilter::class)] #[ApiFilter(EntityFilter::class, properties: ["category", "footprint", "manufacturer", "partUnit"])] +#[ApiFilter(PartStoragelocationFilter::class, properties: ["storage_location"])] #[ApiFilter(LikeFilter::class, properties: ["name", "comment", "description", "ipn", "tags", "manufacturer_product_number"])] #[ApiFilter(BooleanFilter::class, properties: ["favorite" , "needs_review"])] #[ApiFilter(RangeFilter::class, properties: ["mass", "minamount"])]