vendor/pagerfanta/pagerfanta/lib/Core/Pagerfanta.php line 81

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Pagerfanta;
  3. use Pagerfanta\Adapter\AdapterInterface;
  4. use Pagerfanta\Exception\LessThan1CurrentPageException;
  5. use Pagerfanta\Exception\LessThan1MaxPagesException;
  6. use Pagerfanta\Exception\LessThan1MaxPerPageException;
  7. use Pagerfanta\Exception\LogicException;
  8. use Pagerfanta\Exception\OutOfBoundsException;
  9. use Pagerfanta\Exception\OutOfRangeCurrentPageException;
  10. /**
  11.  * @template T
  12.  * @implements PagerfantaInterface<T>
  13.  */
  14. class Pagerfanta implements PagerfantaInterface\JsonSerializable
  15. {
  16.     /**
  17.      * @var AdapterInterface<T>
  18.      */
  19.     private AdapterInterface $adapter;
  20.     private bool $allowOutOfRangePages false;
  21.     private bool $normalizeOutOfRangePages false;
  22.     /**
  23.      * @phpstan-var positive-int
  24.      */
  25.     private int $maxPerPage 10;
  26.     /**
  27.      * @phpstan-var positive-int
  28.      */
  29.     private int $currentPage 1;
  30.     /**
  31.      * @phpstan-var int<0, max>|null
  32.      */
  33.     private ?int $nbResults null;
  34.     /**
  35.      * @phpstan-var positive-int|null
  36.      */
  37.     private ?int $maxNbPages null;
  38.     /**
  39.      * @phpstan-var iterable<array-key, T>|null
  40.      */
  41.     private ?iterable $currentPageResults null;
  42.     /**
  43.      * @param AdapterInterface<T> $adapter
  44.      */
  45.     public function __construct(AdapterInterface $adapter)
  46.     {
  47.         $this->adapter $adapter;
  48.     }
  49.     /**
  50.      * @param AdapterInterface<T> $adapter
  51.      *
  52.      * @return self<T>
  53.      */
  54.     public static function createForCurrentPageWithMaxPerPage(AdapterInterface $adapterint $currentPageint $maxPerPage): self
  55.     {
  56.         $pagerfanta = new self($adapter);
  57.         $pagerfanta->setMaxPerPage($maxPerPage);
  58.         $pagerfanta->setCurrentPage($currentPage);
  59.         return $pagerfanta;
  60.     }
  61.     /**
  62.      * @return AdapterInterface<T>
  63.      *
  64.      * @deprecated to be removed in 4.0
  65.      */
  66.     public function getAdapter(): AdapterInterface
  67.     {
  68.         trigger_deprecation('pagerfanta/pagerfanta''3.2''Retrieving the %s from "%s" implementations is deprecated and will be removed in 4.0.'AdapterInterface::class, PagerfantaInterface::class);
  69.         return $this->adapter;
  70.     }
  71.     /**
  72.      * @return $this<T>
  73.      */
  74.     public function setAllowOutOfRangePages(bool $allowOutOfRangePages): PagerfantaInterface
  75.     {
  76.         $this->allowOutOfRangePages $allowOutOfRangePages;
  77.         return $this;
  78.     }
  79.     public function getAllowOutOfRangePages(): bool
  80.     {
  81.         return $this->allowOutOfRangePages;
  82.     }
  83.     public function setNormalizeOutOfRangePages(bool $normalizeOutOfRangePages): PagerfantaInterface
  84.     {
  85.         $this->normalizeOutOfRangePages $normalizeOutOfRangePages;
  86.         return $this;
  87.     }
  88.     public function getNormalizeOutOfRangePages(): bool
  89.     {
  90.         return $this->normalizeOutOfRangePages;
  91.     }
  92.     /**
  93.      * @return $this<T>
  94.      *
  95.      * @throws LessThan1MaxPerPageException if the page is less than 1
  96.      */
  97.     public function setMaxPerPage(int $maxPerPage): PagerfantaInterface
  98.     {
  99.         $this->filterMaxPerPage($maxPerPage);
  100.         $this->maxPerPage $maxPerPage;
  101.         $this->resetForMaxPerPageChange();
  102.         $this->filterOutOfRangeCurrentPage($this->currentPage);
  103.         return $this;
  104.     }
  105.     private function filterMaxPerPage(int $maxPerPage): void
  106.     {
  107.         $this->checkMaxPerPage($maxPerPage);
  108.     }
  109.     /**
  110.      * @throws LessThan1MaxPerPageException if the page is less than 1
  111.      */
  112.     private function checkMaxPerPage(int $maxPerPage): void
  113.     {
  114.         if ($maxPerPage 1) {
  115.             throw new LessThan1MaxPerPageException();
  116.         }
  117.     }
  118.     private function resetForMaxPerPageChange(): void
  119.     {
  120.         $this->currentPageResults null;
  121.     }
  122.     /**
  123.      * @phpstan-return positive-int
  124.      */
  125.     public function getMaxPerPage(): int
  126.     {
  127.         return $this->maxPerPage;
  128.     }
  129.     /**
  130.      * @return $this<T>
  131.      *
  132.      * @throws LessThan1CurrentPageException  if the current page is less than 1
  133.      * @throws OutOfRangeCurrentPageException if It is not allowed out of range pages and they are not normalized
  134.      */
  135.     public function setCurrentPage(int $currentPage): PagerfantaInterface
  136.     {
  137.         $this->currentPage $this->filterCurrentPage($currentPage);
  138.         $this->resetForCurrentPageChange();
  139.         return $this;
  140.     }
  141.     /**
  142.      * @phpstan-return positive-int
  143.      */
  144.     private function filterCurrentPage(int $currentPage): int
  145.     {
  146.         $this->checkCurrentPage($currentPage);
  147.         return $this->filterOutOfRangeCurrentPage($currentPage);
  148.     }
  149.     /**
  150.      * @throws LessThan1CurrentPageException if the current page is less than 1
  151.      */
  152.     private function checkCurrentPage(int $currentPage): void
  153.     {
  154.         if ($currentPage 1) {
  155.             throw new LessThan1CurrentPageException();
  156.         }
  157.     }
  158.     /**
  159.      * @phpstan-return positive-int
  160.      */
  161.     private function filterOutOfRangeCurrentPage(int $currentPage): int
  162.     {
  163.         if ($this->notAllowedCurrentPageOutOfRange($currentPage)) {
  164.             return $this->normalizeOutOfRangeCurrentPage($currentPage);
  165.         }
  166.         return $currentPage;
  167.     }
  168.     private function notAllowedCurrentPageOutOfRange(int $currentPage): bool
  169.     {
  170.         return !$this->getAllowOutOfRangePages() && $this->currentPageOutOfRange($currentPage);
  171.     }
  172.     private function currentPageOutOfRange(int $currentPage): bool
  173.     {
  174.         return $currentPage && $currentPage $this->getNbPages();
  175.     }
  176.     /**
  177.      * @phpstan-return positive-int
  178.      *
  179.      * @throws OutOfRangeCurrentPageException if the page should not be normalized
  180.      */
  181.     private function normalizeOutOfRangeCurrentPage(int $currentPage): int
  182.     {
  183.         if ($this->getNormalizeOutOfRangePages()) {
  184.             return $this->getNbPages();
  185.         }
  186.         throw new OutOfRangeCurrentPageException(sprintf('Page "%d" does not exist. The currentPage must be inferior to "%d"'$currentPage$this->getNbPages()));
  187.     }
  188.     private function resetForCurrentPageChange(): void
  189.     {
  190.         $this->currentPageResults null;
  191.     }
  192.     /**
  193.      * @phpstan-return positive-int
  194.      */
  195.     public function getCurrentPage(): int
  196.     {
  197.         return $this->currentPage;
  198.     }
  199.     /**
  200.      * @phpstan-return iterable<array-key, T>
  201.      */
  202.     public function getCurrentPageResults(): iterable
  203.     {
  204.         if (null === $this->currentPageResults) {
  205.             $this->currentPageResults $this->getCurrentPageResultsFromAdapter();
  206.         }
  207.         return $this->currentPageResults;
  208.     }
  209.     /**
  210.      * @return iterable<array-key, T>
  211.      */
  212.     private function getCurrentPageResultsFromAdapter(): iterable
  213.     {
  214.         $offset $this->calculateOffsetForCurrentPageResults();
  215.         $length $this->getMaxPerPage();
  216.         return $this->adapter->getSlice($offset$length);
  217.     }
  218.     /**
  219.      * @phpstan-return int<0, max>
  220.      */
  221.     private function calculateOffsetForCurrentPageResults(): int
  222.     {
  223.         return ($this->getCurrentPage() - 1) * $this->getMaxPerPage();
  224.     }
  225.     /**
  226.      * @phpstan-return int<0, max>
  227.      */
  228.     public function getCurrentPageOffsetStart(): int
  229.     {
  230.         return $this->getNbResults() ? $this->calculateOffsetForCurrentPageResults() + 0;
  231.     }
  232.     /**
  233.      * @phpstan-return int<0, max>
  234.      */
  235.     public function getCurrentPageOffsetEnd(): int
  236.     {
  237.         return $this->hasNextPage() ? $this->getCurrentPage() * $this->getMaxPerPage() : $this->getNbResults();
  238.     }
  239.     /**
  240.      * @phpstan-return int<0, max>
  241.      */
  242.     public function getNbResults(): int
  243.     {
  244.         if (null === $this->nbResults) {
  245.             $this->nbResults $this->adapter->getNbResults();
  246.         }
  247.         return $this->nbResults;
  248.     }
  249.     /**
  250.      * @phpstan-return positive-int
  251.      */
  252.     public function getNbPages(): int
  253.     {
  254.         $nbPages $this->calculateNbPages();
  255.         if (=== $nbPages) {
  256.             return $this->minimumNbPages();
  257.         }
  258.         if (null !== $this->maxNbPages && $this->maxNbPages $nbPages) {
  259.             return $this->maxNbPages;
  260.         }
  261.         return $nbPages;
  262.     }
  263.     /**
  264.      * @phpstan-return int<0, max>
  265.      */
  266.     private function calculateNbPages(): int
  267.     {
  268.         return (int) ceil($this->getNbResults() / $this->getMaxPerPage());
  269.     }
  270.     /**
  271.      * @phpstan-return positive-int
  272.      */
  273.     private function minimumNbPages(): int
  274.     {
  275.         return 1;
  276.     }
  277.     /**
  278.      * @return $this<T>
  279.      *
  280.      * @throws LessThan1MaxPagesException if the max number of pages is less than 1
  281.      */
  282.     public function setMaxNbPages(int $maxNbPages): PagerfantaInterface
  283.     {
  284.         if ($maxNbPages 1) {
  285.             throw new LessThan1MaxPagesException();
  286.         }
  287.         $this->maxNbPages $maxNbPages;
  288.         return $this;
  289.     }
  290.     /**
  291.      * @return $this<T>
  292.      */
  293.     public function resetMaxNbPages(): PagerfantaInterface
  294.     {
  295.         $this->maxNbPages null;
  296.         return $this;
  297.     }
  298.     public function haveToPaginate(): bool
  299.     {
  300.         return $this->getNbResults() > $this->maxPerPage;
  301.     }
  302.     public function hasPreviousPage(): bool
  303.     {
  304.         return $this->currentPage 1;
  305.     }
  306.     /**
  307.      * @phpstan-return positive-int
  308.      *
  309.      * @throws LogicException if there is no previous page
  310.      */
  311.     public function getPreviousPage(): int
  312.     {
  313.         if (!$this->hasPreviousPage()) {
  314.             throw new LogicException('There is no previous page.');
  315.         }
  316.         return $this->currentPage 1;
  317.     }
  318.     public function hasNextPage(): bool
  319.     {
  320.         return $this->currentPage $this->getNbPages();
  321.     }
  322.     /**
  323.      * @phpstan-return positive-int
  324.      *
  325.      * @throws LogicException if there is no next page
  326.      */
  327.     public function getNextPage(): int
  328.     {
  329.         if (!$this->hasNextPage()) {
  330.             throw new LogicException('There is no next page.');
  331.         }
  332.         return $this->currentPage 1;
  333.     }
  334.     /**
  335.      * @phpstan-return int<0, max>
  336.      */
  337.     public function count(): int
  338.     {
  339.         return $this->getNbResults();
  340.     }
  341.     /**
  342.      * @return \Traversable<array-key, T>
  343.      */
  344.     public function getIterator(): \Traversable
  345.     {
  346.         $results $this->getCurrentPageResults();
  347.         if ($results instanceof \Iterator) {
  348.             return $results;
  349.         }
  350.         if ($results instanceof \IteratorAggregate) {
  351.             return $results->getIterator();
  352.         }
  353.         if (\is_array($results)) {
  354.             return new \ArrayIterator($results);
  355.         }
  356.         throw new \InvalidArgumentException(sprintf('Cannot create iterator with page results of type "%s".'get_debug_type($results)));
  357.     }
  358.     public function jsonSerialize(): array
  359.     {
  360.         $results $this->getCurrentPageResults();
  361.         if ($results instanceof \Traversable) {
  362.             return iterator_to_array($results);
  363.         }
  364.         return $results;
  365.     }
  366.     /**
  367.      * Get page number of the item at specified position (1-based index).
  368.      *
  369.      * @phpstan-param positive-int $position
  370.      *
  371.      * @phpstan-return positive-int
  372.      *
  373.      * @throws OutOfBoundsException if the item is outside the result set
  374.      */
  375.     public function getPageNumberForItemAtPosition(int $position): int
  376.     {
  377.         if ($this->getNbResults() < $position) {
  378.             throw new OutOfBoundsException(sprintf('Item requested at position %d, but there are only %d items.'$position$this->getNbResults()));
  379.         }
  380.         return (int) ceil($position $this->getMaxPerPage());
  381.     }
  382. }