vendor/alterphp/easyadmin-extension-bundle/src/EventListener/PostQueryBuilderSubscriber.php line 74

Open in your IDE?
  1. <?php
  2. namespace AlterPHP\EasyAdminExtensionBundle\EventListener;
  3. use AlterPHP\EasyAdminExtensionBundle\Model\ListFilter;
  4. use Doctrine\ORM\Query\QueryException;
  5. use Doctrine\ORM\QueryBuilder;
  6. use EasyCorp\Bundle\EasyAdminBundle\Event\EasyAdminEvents;
  7. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  8. use Symfony\Component\EventDispatcher\GenericEvent;
  9. /**
  10.  * Apply filters on list/search queryBuilder.
  11.  */
  12. class PostQueryBuilderSubscriber implements EventSubscriberInterface
  13. {
  14.     /**
  15.      * @var \AlterPHP\EasyAdminExtensionBundle\Helper\ListFormFiltersHelper
  16.      */
  17.     protected $listFormFiltersHelper;
  18.     /**
  19.      * ListFormFiltersExtension constructor.
  20.      *
  21.      * @param \AlterPHP\EasyAdminExtensionBundle\Helper\ListFormFiltersHelper $listFormFiltersHelper
  22.      */
  23.     public function __construct($listFormFiltersHelper)
  24.     {
  25.         $this->listFormFiltersHelper $listFormFiltersHelper;
  26.     }
  27.     /**
  28.      * {@inheritdoc}
  29.      */
  30.     public static function getSubscribedEvents()
  31.     {
  32.         return [
  33.             EasyAdminEvents::POST_LIST_QUERY_BUILDER => ['onPostListQueryBuilder'],
  34.             EasyAdminEvents::POST_SEARCH_QUERY_BUILDER => ['onPostSearchQueryBuilder'],
  35.         ];
  36.     }
  37.     /**
  38.      * Called on POST_LIST_QUERY_BUILDER event.
  39.      *
  40.      * @param GenericEvent $event
  41.      */
  42.     public function onPostListQueryBuilder(GenericEvent $event)
  43.     {
  44.         $queryBuilder $event->getArgument('query_builder');
  45.         // Request filters
  46.         if ($event->hasArgument('request')) {
  47.             $this->applyRequestFilters($queryBuilder$event->getArgument('request')->get('filters', []));
  48.         }
  49.         // List form filters
  50.         if ($event->hasArgument('entity')) {
  51.             $entityConfig $event->getArgument('entity');
  52.             if (isset($entityConfig['list']['form_filters'])) {
  53.                 $listFormFiltersForm $this->listFormFiltersHelper->getListFormFilters($entityConfig['list']['form_filters']);
  54.                 if ($listFormFiltersForm->isSubmitted() && $listFormFiltersForm->isValid()) {
  55.                     $this->applyFormFilters($queryBuilder$listFormFiltersForm->getData());
  56.                 }
  57.             }
  58.         }
  59.     }
  60.     /**
  61.      * Called on POST_SEARCH_QUERY_BUILDER event.
  62.      *
  63.      * @param GenericEvent $event
  64.      */
  65.     public function onPostSearchQueryBuilder(GenericEvent $event)
  66.     {
  67.         $queryBuilder $event->getArgument('query_builder');
  68.         if ($event->hasArgument('request')) {
  69.             $this->applyRequestFilters($queryBuilder$event->getArgument('request')->get('filters', []));
  70.         }
  71.     }
  72.     /**
  73.      * Applies request filters on queryBuilder.
  74.      *
  75.      * @param QueryBuilder $queryBuilder
  76.      * @param array        $filters
  77.      */
  78.     protected function applyRequestFilters(QueryBuilder $queryBuilder, array $filters = [])
  79.     {
  80.         foreach ($filters as $field => $value) {
  81.             // Empty string and numeric keys is considered as "not applied filter"
  82.             if ('' === $value || \is_int($field)) {
  83.                 continue;
  84.             }
  85.             $operator = \is_array($value) ? ListFilter::OPERATOR_IN ListFilter::OPERATOR_EQUALS;
  86.             $listFilter ListFilter::createFromRequest($field$operator$value);
  87.             $this->filterQueryBuilder($queryBuilder$field$listFilter);
  88.         }
  89.     }
  90.     /**
  91.      * Applies form filters on queryBuilder.
  92.      *
  93.      * @param QueryBuilder $queryBuilder
  94.      * @param array        $filters
  95.      */
  96.     protected function applyFormFilters(QueryBuilder $queryBuilder, array $filters = [])
  97.     {
  98.         foreach ($filters as $field => $listFilter) {
  99.             if (null === $listFilter) {
  100.                 continue;
  101.             }
  102.             $this->filterQueryBuilder($queryBuilder$field$listFilter);
  103.         }
  104.     }
  105.     /**
  106.      * Filters queryBuilder.
  107.      *
  108.      * @param QueryBuilder $queryBuilder
  109.      * @param string       $field
  110.      * @param ListFilter   $listFilter
  111.      */
  112.     protected function filterQueryBuilder(QueryBuilder $queryBuilderstring $fieldListFilter $listFilter)
  113.     {
  114.         $value $this->filterEasyadminAutocompleteValue($listFilter->getValue());
  115.         // Empty string and numeric keys is considered as "not applied filter"
  116.         if (null === $value || '' === $value || \is_int($field)) {
  117.             return;
  118.         }
  119.         // Add root entity alias if none provided
  120.         $queryField $listFilter->getProperty();
  121.         if (false === \strpos($queryField'.')) {
  122.             $queryField $queryBuilder->getRootAlias().'.'.$queryField;
  123.         }
  124.         // Checks if filter is directly appliable on queryBuilder
  125.         if (!$this->isFilterAppliable($queryBuilder$queryField)) {
  126.             return;
  127.         }
  128.         $operator $listFilter->getOperator();
  129.         // Sanitize parameter name
  130.         $parameter 'form_filter_'.\str_replace('.''_'$field);
  131.         switch ($operator) {
  132.             case ListFilter::OPERATOR_EQUALS:
  133.                 if ('_NULL' === $value) {
  134.                     $queryBuilder->andWhere(\sprintf('%s IS NULL'$queryField));
  135.                 } elseif ('_NOT_NULL' === $value) {
  136.                     $queryBuilder->andWhere(\sprintf('%s IS NOT NULL'$queryField));
  137.                 } else {
  138.                     $queryBuilder
  139.                         ->andWhere(\sprintf('%s %s :%s'$queryField'='$parameter))
  140.                         ->setParameter($parameter$value)
  141.                     ;
  142.                 }
  143.                 break;
  144.             case ListFilter::OPERATOR_NOT:
  145.                 $queryBuilder
  146.                     ->andWhere(\sprintf('%s %s :%s'$queryField'!='$parameter))
  147.                     ->setParameter($parameter$value)
  148.                 ;
  149.                 break;
  150.             case ListFilter::OPERATOR_IN:
  151.                 // Checks that $value is not an empty Traversable
  152.                 if (< \count($value)) {
  153.                     $queryBuilder
  154.                         ->andWhere(\sprintf('%s %s (:%s)'$queryField'IN'$parameter))
  155.                         ->setParameter($parameter$value)
  156.                     ;
  157.                 }
  158.                 break;
  159.             case ListFilter::OPERATOR_NOTIN:
  160.                 // Checks that $value is not an empty Traversable
  161.                 if (< \count($value)) {
  162.                     $queryBuilder
  163.                         ->andWhere(\sprintf('%s %s (:%s)'$queryField'NOT IN'$parameter))
  164.                         ->setParameter($parameter$value)
  165.                     ;
  166.                 }
  167.                 break;
  168.             case ListFilter::OPERATOR_GT:
  169.                 $queryBuilder
  170.                     ->andWhere(\sprintf('%s %s :%s'$queryField'>'$parameter))
  171.                     ->setParameter($parameter$value)
  172.                 ;
  173.                 break;
  174.             case ListFilter::OPERATOR_GTE:
  175.                 $queryBuilder
  176.                     ->andWhere(\sprintf('%s %s :%s'$queryField'>='$parameter))
  177.                     ->setParameter($parameter$value)
  178.                 ;
  179.                 break;
  180.             case ListFilter::OPERATOR_LT:
  181.                 $queryBuilder
  182.                     ->andWhere(\sprintf('%s %s :%s'$queryField'<'$parameter))
  183.                     ->setParameter($parameter$value)
  184.                 ;
  185.                 break;
  186.             case ListFilter::OPERATOR_LTE:
  187.                 $queryBuilder
  188.                     ->andWhere(\sprintf('%s %s :%s'$queryField'<='$parameter))
  189.                     ->setParameter($parameter$value)
  190.                 ;
  191.                 break;
  192.             case ListFilter::OPERATOR_LIKE:
  193.                 $queryBuilder
  194.                     ->andWhere(\sprintf('%s %s :%s'$queryField'LIKE'$parameter))
  195.                     ->setParameter($parameter'%'.$value.'%')
  196.                 ;
  197.                 break;
  198.             default:
  199.                 throw new \RuntimeException(\sprintf('Operator "%s" is not supported !'$operator));
  200.         }
  201.     }
  202.     protected function filterEasyadminAutocompleteValue($value)
  203.     {
  204.         if (!\is_array($value) || !isset($value['autocomplete']) || !== \count($value)) {
  205.             return $value;
  206.         }
  207.         return $value['autocomplete'];
  208.     }
  209.     /**
  210.      * Checks if filter is directly appliable on queryBuilder.
  211.      *
  212.      * @param QueryBuilder $queryBuilder
  213.      * @param string       $field
  214.      *
  215.      * @return bool
  216.      */
  217.     protected function isFilterAppliable(QueryBuilder $queryBuilderstring $field): bool
  218.     {
  219.         $qbClone = clone $queryBuilder;
  220.         try {
  221.             $qbClone->andWhere($field.' IS NULL');
  222.             // Generating SQL throws a QueryException if using wrong field/association
  223.             $qbClone->getQuery()->getSQL();
  224.         } catch (QueryException $e) {
  225.             return false;
  226.         }
  227.         return true;
  228.     }
  229. }