vendor/knplabs/knp-components/src/Knp/Component/Pager/Paginator.php line 129

Open in your IDE?
  1. <?php
  2. namespace Knp\Component\Pager;
  3. use Knp\Component\Pager\Event;
  4. use Knp\Component\Pager\Event\Subscriber\Paginate\PaginationSubscriber;
  5. use Knp\Component\Pager\Event\Subscriber\Sortable\SortableSubscriber;
  6. use Knp\Component\Pager\Exception\PageNumberOutOfRangeException;
  7. use Knp\Component\Pager\Pagination\PaginationInterface;
  8. use Symfony\Component\EventDispatcher\Event as BaseEvent;
  9. use Symfony\Component\EventDispatcher\EventDispatcher;
  10. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  11. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  12. use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
  13. use Symfony\Component\HttpFoundation\Request;
  14. use Symfony\Component\HttpFoundation\RequestStack;
  15. /**
  16.  * Paginator uses event dispatcher to trigger pagination
  17.  * lifecycle events. Subscribers are expected to paginate
  18.  * wanted target and finally it generates pagination view
  19.  * which is only the result of paginator
  20.  */
  21. class Paginator implements PaginatorInterface
  22. {
  23.     /**
  24.      * @var EventDispatcherInterface
  25.      */
  26.     protected $eventDispatcher;
  27.     /**
  28.      * Default options of paginator
  29.      *
  30.      * @var array
  31.      */
  32.     protected $defaultOptions = [
  33.         self::PAGE_PARAMETER_NAME => 'page',
  34.         self::SORT_FIELD_PARAMETER_NAME => 'sort',
  35.         self::SORT_DIRECTION_PARAMETER_NAME => 'direction',
  36.         self::FILTER_FIELD_PARAMETER_NAME => 'filterParam',
  37.         self::FILTER_VALUE_PARAMETER_NAME => 'filterValue',
  38.         self::DISTINCT => true,
  39.         self::PAGE_OUT_OF_RANGE => self::PAGE_OUT_OF_RANGE_IGNORE,
  40.         self::DEFAULT_LIMIT => self::DEFAULT_LIMIT_VALUE,
  41.     ];
  42.     /**
  43.      * @var RequestStack|null
  44.      */
  45.     protected $requestStack;
  46.     /**
  47.      * Initialize paginator with event dispatcher
  48.      * Can be a service in concept. By default it
  49.      * hooks standard pagination subscriber
  50.      *
  51.      * @param EventDispatcherInterface|null $eventDispatcher
  52.      * @param RequestStack|null             $requestStack
  53.      */
  54.     public function __construct(EventDispatcherInterface $eventDispatcher nullRequestStack $requestStack null)
  55.     {
  56.         $this->eventDispatcher = \class_exists(BaseEvent::class) && \class_exists(LegacyEventDispatcherProxy::class) ? LegacyEventDispatcherProxy::decorate($eventDispatcher) : $eventDispatcher;
  57.         if (is_null($this->eventDispatcher)) {
  58.             trigger_deprecation('knplabs/knp-components''2.5.0''Not passing EventDispatcher is deprecated and will no longer be supported in v3.');
  59.             $this->eventDispatcher = new EventDispatcher();
  60.             $this->eventDispatcher->addSubscriber(new PaginationSubscriber);
  61.             $this->eventDispatcher->addSubscriber(new SortableSubscriber);
  62.         }
  63.         $this->requestStack $requestStack;
  64.     }
  65.     /**
  66.      * Override the default paginator options
  67.      * to be reused for paginations
  68.      *
  69.      * @param array $options
  70.      */
  71.     public function setDefaultPaginatorOptions(array $options): void
  72.     {
  73.         $this->defaultOptions array_merge($this->defaultOptions$options);
  74.     }
  75.     /**
  76.      * Paginates anything (depending on event listeners)
  77.      * into Pagination object, which is a view targeted
  78.      * pagination object (might be aggregated helper object)
  79.      * responsible for the pagination result representation
  80.      *
  81.      * @param mixed $target - anything what needs to be paginated
  82.      * @param int $page - page number, starting from 1
  83.      * @param int $limit - number of items per page
  84.      * @param array $options - less used options:
  85.      *     boolean $distinct - default true for distinction of results
  86.      *     string $alias - pagination alias, default none
  87.      *     array $allowList - sortable allow list for target fields being paginated
  88.      *     string $pageOutOfRange - see PaginatorInterface::PAGE_OUT_OF_RANGE_*
  89.      * @throws \LogicException
  90.      * @throws PageNumberOutOfRangeException
  91.      * @return PaginationInterface
  92.      */
  93.     public function paginate($targetint $page 1int $limit null, array $options = []): PaginationInterface
  94.     {
  95.         $limit $limit ?? $this->defaultOptions[self::DEFAULT_LIMIT];
  96.         if ($limit <= or $page <= 0) {
  97.             throw new \LogicException("Invalid item per page number. Limit: $limit and Page: $page, must be positive non-zero integers");
  98.         }
  99.         $offset = ($page 1) * $limit;
  100.         $options array_merge($this->defaultOptions$options);
  101.         // normalize default sort field
  102.         if (isset($options[self::DEFAULT_SORT_FIELD_NAME]) && is_array($options[self::DEFAULT_SORT_FIELD_NAME])) {
  103.             $options[self::DEFAULT_SORT_FIELD_NAME] = implode('+'$options[self::DEFAULT_SORT_FIELD_NAME]);
  104.         }
  105.         $request null === $this->requestStack Request::createFromGlobals() : $this->requestStack->getCurrentRequest();
  106.         // default sort field and direction are set based on options (if available)
  107.         if (isset($options[self::DEFAULT_SORT_FIELD_NAME]) && !$request->query->has($options[self::SORT_FIELD_PARAMETER_NAME])) {
  108.            $request->query->set($options[self::SORT_FIELD_PARAMETER_NAME], $options[self::DEFAULT_SORT_FIELD_NAME]);
  109.             if (!$request->query->has($options[self::SORT_DIRECTION_PARAMETER_NAME])) {
  110.                 $request->query->set($options[self::SORT_DIRECTION_PARAMETER_NAME], $options[self::DEFAULT_SORT_DIRECTION] ?? 'asc');
  111.             }
  112.         }
  113.         // before pagination start
  114.         $beforeEvent = new Event\BeforeEvent($this->eventDispatcher$request);
  115.         $this->dispatch('knp_pager.before'$beforeEvent);
  116.         // items
  117.         $itemsEvent = new Event\ItemsEvent($offset$limit);
  118.         $itemsEvent->options = &$options;
  119.         $itemsEvent->target = &$target;
  120.         $this->dispatch('knp_pager.items'$itemsEvent);
  121.         if (!$itemsEvent->isPropagationStopped()) {
  122.             throw new \RuntimeException('One of listeners must count and slice given target');
  123.         }
  124.         if ($page ceil($itemsEvent->count $limit)) {
  125.             $pageOutOfRangeOption $options[self::PAGE_OUT_OF_RANGE] ?? $this->defaultOptions[self::PAGE_OUT_OF_RANGE];
  126.             if ($pageOutOfRangeOption === self::PAGE_OUT_OF_RANGE_FIX && $itemsEvent->count 0) {
  127.                 // replace page number out of range with max page
  128.                 return $this->paginate($targetceil($itemsEvent->count $limit), $limit$options);
  129.             }
  130.             if ($pageOutOfRangeOption === self::PAGE_OUT_OF_RANGE_THROW_EXCEPTION && $page 1) {
  131.                 throw new PageNumberOutOfRangeException(
  132.                     sprintf('Page number: %d is out of range.'$page),
  133.                     ceil($itemsEvent->count $limit)
  134.                 );
  135.             }
  136.         }
  137.         // pagination initialization event
  138.         $paginationEvent = new Event\PaginationEvent;
  139.         $paginationEvent->target = &$target;
  140.         $paginationEvent->options = &$options;
  141.         $this->dispatch('knp_pager.pagination'$paginationEvent);
  142.         if (!$paginationEvent->isPropagationStopped()) {
  143.             throw new \RuntimeException('One of listeners must create pagination view');
  144.         }
  145.         // pagination class can be different, with different rendering methods
  146.         $paginationView $paginationEvent->getPagination();
  147.         $paginationView->setCustomParameters($itemsEvent->getCustomPaginationParameters());
  148.         $paginationView->setCurrentPageNumber($page);
  149.         $paginationView->setItemNumberPerPage($limit);
  150.         $paginationView->setTotalItemCount($itemsEvent->count);
  151.         $paginationView->setPaginatorOptions($options);
  152.         $paginationView->setItems($itemsEvent->items);
  153.         // after
  154.         $afterEvent = new Event\AfterEvent($paginationView);
  155.         $this->dispatch('knp_pager.after'$afterEvent);
  156.         return $paginationView;
  157.     }
  158.     /**
  159.      * Hooks in the given event subscriber
  160.      *
  161.      * @param \Symfony\Component\EventDispatcher\EventSubscriberInterface $subscriber
  162.      */
  163.     public function subscribe(EventSubscriberInterface $subscriber): void
  164.     {
  165.         $this->eventDispatcher->addSubscriber($subscriber);
  166.     }
  167.     /**
  168.      * Hooks the listener to the given event name
  169.      *
  170.      * @param string $eventName
  171.      * @param object $listener
  172.      * @param integer $priority
  173.      */
  174.     public function connect(string $eventName$listenerint $priority 0): void
  175.     {
  176.         $this->eventDispatcher->addListener($eventName$listener$priority);
  177.     }
  178.     /**
  179.      * Provide a BC way to dispatch events.
  180.      *
  181.      * @param string $eventName
  182.      * @param Event\Event $event
  183.      */
  184.     protected function dispatch(string $eventNameEvent\Event $event): void
  185.     {
  186.         if (!\class_exists(LegacyEventDispatcherProxy::class)) {
  187.             $this->eventDispatcher->dispatch($eventName$event);
  188.         } else {
  189.             $this->eventDispatcher->dispatch($event$eventName);
  190.         }
  191.     }
  192. }