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

Open in your IDE?
  1. <?php
  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\NotBooleanException;
  9. use Pagerfanta\Exception\NotIntegerCurrentPageException;
  10. use Pagerfanta\Exception\NotIntegerException;
  11. use Pagerfanta\Exception\NotIntegerMaxPerPageException;
  12. use Pagerfanta\Exception\OutOfBoundsException;
  13. use Pagerfanta\Exception\OutOfRangeCurrentPageException;
  14. /**
  15.  * @template T
  16.  * @implements \IteratorAggregate<T>
  17.  */
  18. class Pagerfanta implements \Countable\IteratorAggregate\JsonSerializablePagerfantaInterface
  19. {
  20.     /**
  21.      * @var AdapterInterface
  22.      */
  23.     private $adapter;
  24.     /**
  25.      * @var bool
  26.      */
  27.     private $allowOutOfRangePages false;
  28.     /**
  29.      * @var bool
  30.      */
  31.     private $normalizeOutOfRangePages false;
  32.     /**
  33.      * @var int
  34.      */
  35.     private $maxPerPage 10;
  36.     /**
  37.      * @var int
  38.      */
  39.     private $currentPage 1;
  40.     /**
  41.      * @var int|null
  42.      */
  43.     private $nbResults;
  44.     /**
  45.      * @var int|null
  46.      */
  47.     private $maxNbPages;
  48.     /**
  49.      * @var iterable<array-key, T>|null
  50.      */
  51.     private $currentPageResults;
  52.     public function __construct(AdapterInterface $adapter)
  53.     {
  54.         $this->adapter $adapter;
  55.     }
  56.     /**
  57.      * @return AdapterInterface
  58.      */
  59.     public function getAdapter()
  60.     {
  61.         return $this->adapter;
  62.     }
  63.     /**
  64.      * @param bool $allowOutOfRangePages
  65.      *
  66.      * @return $this
  67.      *
  68.      * @throws NotBooleanException if the value is not boolean
  69.      */
  70.     public function setAllowOutOfRangePages($allowOutOfRangePages)
  71.     {
  72.         $this->allowOutOfRangePages $this->filterBoolean($allowOutOfRangePages);
  73.         return $this;
  74.     }
  75.     /**
  76.      * @return bool
  77.      */
  78.     public function getAllowOutOfRangePages()
  79.     {
  80.         return $this->allowOutOfRangePages;
  81.     }
  82.     /**
  83.      * @param bool $normalizeOutOfRangePages
  84.      *
  85.      * @return $this
  86.      *
  87.      * @throws NotBooleanException if the value is not boolean
  88.      */
  89.     public function setNormalizeOutOfRangePages($normalizeOutOfRangePages)
  90.     {
  91.         $this->normalizeOutOfRangePages $this->filterBoolean($normalizeOutOfRangePages);
  92.         return $this;
  93.     }
  94.     /**
  95.      * @return bool
  96.      */
  97.     public function getNormalizeOutOfRangePages()
  98.     {
  99.         return $this->normalizeOutOfRangePages;
  100.     }
  101.     /**
  102.      * Sets the maximum number of items per page.
  103.      *
  104.      * Tries to convert from string and float.
  105.      *
  106.      * @param int $maxPerPage
  107.      *
  108.      * @return $this
  109.      *
  110.      * @throws NotIntegerMaxPerPageException if the max per page is not an integer even converting
  111.      * @throws LessThan1MaxPerPageException  if the max per page is less than 1
  112.      */
  113.     public function setMaxPerPage($maxPerPage)
  114.     {
  115.         $this->maxPerPage $this->filterMaxPerPage($maxPerPage);
  116.         $this->resetForMaxPerPageChange();
  117.         return $this;
  118.     }
  119.     /**
  120.      * @param int $maxPerPage
  121.      */
  122.     private function filterMaxPerPage($maxPerPage): int
  123.     {
  124.         $maxPerPage $this->toInteger($maxPerPage);
  125.         $this->checkMaxPerPage($maxPerPage);
  126.         return $maxPerPage;
  127.     }
  128.     /**
  129.      * @param int $maxPerPage
  130.      *
  131.      * @throws NotIntegerMaxPerPageException if the max per page is not an integer even converting
  132.      * @throws LessThan1MaxPerPageException  if the max per page is less than 1
  133.      */
  134.     private function checkMaxPerPage($maxPerPage): void
  135.     {
  136.         if (!\is_int($maxPerPage)) {
  137.             throw new NotIntegerMaxPerPageException();
  138.         }
  139.         if ($maxPerPage 1) {
  140.             throw new LessThan1MaxPerPageException();
  141.         }
  142.     }
  143.     private function resetForMaxPerPageChange(): void
  144.     {
  145.         $this->currentPageResults null;
  146.     }
  147.     /**
  148.      * @return int
  149.      */
  150.     public function getMaxPerPage()
  151.     {
  152.         return $this->maxPerPage;
  153.     }
  154.     /**
  155.      * Sets the current page.
  156.      *
  157.      * Tries to convert from string and float.
  158.      *
  159.      * @param int $currentPage
  160.      *
  161.      * @return $this
  162.      *
  163.      * @throws NotIntegerCurrentPageException if the current page is not an integer even converting
  164.      * @throws LessThan1CurrentPageException  if the current page is less than 1
  165.      * @throws OutOfRangeCurrentPageException if It is not allowed out of range pages and they are not normalized
  166.      */
  167.     public function setCurrentPage($currentPage)
  168.     {
  169.         if (\count(\func_get_args()) > 1) {
  170.             $this->useDeprecatedCurrentPageBooleanArguments(\func_get_args());
  171.         }
  172.         $this->currentPage $this->filterCurrentPage($currentPage);
  173.         $this->resetForCurrentPageChange();
  174.         return $this;
  175.     }
  176.     private function useDeprecatedCurrentPageBooleanArguments(array $arguments): void
  177.     {
  178.         $this->useDeprecatedCurrentPageAllowOutOfRangePagesBooleanArgument($arguments);
  179.         $this->useDeprecatedCurrentPageNormalizeOutOfRangePagesBooleanArgument($arguments);
  180.     }
  181.     private function useDeprecatedCurrentPageAllowOutOfRangePagesBooleanArgument(array $arguments): void
  182.     {
  183.         $this->useDeprecatedBooleanArgument($arguments1'setAllowOutOfRangePages''$allowOutOfRangePages');
  184.     }
  185.     private function useDeprecatedCurrentPageNormalizeOutOfRangePagesBooleanArgument(array $arguments): void
  186.     {
  187.         $this->useDeprecatedBooleanArgument($arguments2'setNormalizeOutOfRangePages''$normalizeOutOfRangePages');
  188.     }
  189.     private function useDeprecatedBooleanArgument(array $argumentsint $indexstring $methodstring $oldArgument): void
  190.     {
  191.         if (isset($arguments[$index])) {
  192.             trigger_deprecation(
  193.                 'pagerfanta/pagerfanta',
  194.                 '2.2',
  195.                 'The %1$s argument of %2$s::setCurrentPage() is deprecated and will no longer be supported in 3.0. Use the %2$s::%3$s() method instead.',
  196.                 $oldArgument,
  197.                 self::class,
  198.                 self::class,
  199.                 $method
  200.             );
  201.             $this->$method($arguments[$index]);
  202.         }
  203.     }
  204.     /**
  205.      * @param int $currentPage
  206.      */
  207.     private function filterCurrentPage($currentPage): int
  208.     {
  209.         $currentPage $this->toInteger($currentPage);
  210.         $this->checkCurrentPage($currentPage);
  211.         $currentPage $this->filterOutOfRangeCurrentPage($currentPage);
  212.         return $currentPage;
  213.     }
  214.     /**
  215.      * @param int $currentPage
  216.      *
  217.      * @throws NotIntegerCurrentPageException if the current page is not an integer even converting
  218.      * @throws LessThan1CurrentPageException  if the current page is less than 1
  219.      */
  220.     private function checkCurrentPage($currentPage): void
  221.     {
  222.         if (!\is_int($currentPage)) {
  223.             throw new NotIntegerCurrentPageException();
  224.         }
  225.         if ($currentPage 1) {
  226.             throw new LessThan1CurrentPageException();
  227.         }
  228.     }
  229.     /**
  230.      * @param int $currentPage
  231.      */
  232.     private function filterOutOfRangeCurrentPage($currentPage): int
  233.     {
  234.         if ($this->notAllowedCurrentPageOutOfRange($currentPage)) {
  235.             return $this->normalizeOutOfRangeCurrentPage($currentPage);
  236.         }
  237.         return $currentPage;
  238.     }
  239.     private function notAllowedCurrentPageOutOfRange(int $currentPage): bool
  240.     {
  241.         return !$this->getAllowOutOfRangePages() && $this->currentPageOutOfRange($currentPage);
  242.     }
  243.     private function currentPageOutOfRange(int $currentPage): bool
  244.     {
  245.         return $currentPage && $currentPage $this->getNbPages();
  246.     }
  247.     /**
  248.      * @param int $currentPage
  249.      *
  250.      * @throws OutOfRangeCurrentPageException if the page should not be normalized
  251.      */
  252.     private function normalizeOutOfRangeCurrentPage($currentPage): int
  253.     {
  254.         if ($this->getNormalizeOutOfRangePages()) {
  255.             return $this->getNbPages();
  256.         }
  257.         throw new OutOfRangeCurrentPageException(sprintf('Page "%d" does not exist. The currentPage must be inferior to "%d"'$currentPage$this->getNbPages()));
  258.     }
  259.     private function resetForCurrentPageChange(): void
  260.     {
  261.         $this->currentPageResults null;
  262.     }
  263.     /**
  264.      * @return int
  265.      */
  266.     public function getCurrentPage()
  267.     {
  268.         return $this->currentPage;
  269.     }
  270.     /**
  271.      * @return iterable<array-key, T>
  272.      */
  273.     public function getCurrentPageResults()
  274.     {
  275.         if (null === $this->currentPageResults) {
  276.             $this->currentPageResults $this->getCurrentPageResultsFromAdapter();
  277.         }
  278.         return $this->currentPageResults;
  279.     }
  280.     /**
  281.      * @return iterable<array-key, T>
  282.      */
  283.     private function getCurrentPageResultsFromAdapter(): iterable
  284.     {
  285.         $offset $this->calculateOffsetForCurrentPageResults();
  286.         $length $this->getMaxPerPage();
  287.         return $this->adapter->getSlice($offset$length);
  288.     }
  289.     private function calculateOffsetForCurrentPageResults(): int
  290.     {
  291.         return ($this->getCurrentPage() - 1) * $this->getMaxPerPage();
  292.     }
  293.     /**
  294.      * Calculates the current page offset start.
  295.      *
  296.      * @return int
  297.      */
  298.     public function getCurrentPageOffsetStart()
  299.     {
  300.         return $this->getNbResults() ? $this->calculateOffsetForCurrentPageResults() + 0;
  301.     }
  302.     /**
  303.      * Calculates the current page offset end.
  304.      *
  305.      * @return int
  306.      */
  307.     public function getCurrentPageOffsetEnd()
  308.     {
  309.         return $this->hasNextPage() ? $this->getCurrentPage() * $this->getMaxPerPage() : $this->getNbResults();
  310.     }
  311.     /**
  312.      * @return int
  313.      */
  314.     public function getNbResults()
  315.     {
  316.         if (null === $this->nbResults) {
  317.             $this->nbResults $this->getAdapter()->getNbResults();
  318.         }
  319.         return $this->nbResults;
  320.     }
  321.     /**
  322.      * @return int
  323.      */
  324.     public function getNbPages()
  325.     {
  326.         $nbPages $this->calculateNbPages();
  327.         if (=== $nbPages) {
  328.             return $this->minimumNbPages();
  329.         }
  330.         if (null !== $this->maxNbPages && $this->maxNbPages $nbPages) {
  331.             return $this->maxNbPages;
  332.         }
  333.         return $nbPages;
  334.     }
  335.     private function calculateNbPages(): int
  336.     {
  337.         return (int) ceil($this->getNbResults() / $this->getMaxPerPage());
  338.     }
  339.     private function minimumNbPages(): int
  340.     {
  341.         return 1;
  342.     }
  343.     /**
  344.      * @return $this<T>
  345.      *
  346.      * @throws LessThan1MaxPagesException if the max number of pages is less than 1
  347.      */
  348.     public function setMaxNbPages(int $maxNbPages): self
  349.     {
  350.         if ($maxNbPages 1) {
  351.             throw new LessThan1MaxPagesException();
  352.         }
  353.         $this->maxNbPages $maxNbPages;
  354.         return $this;
  355.     }
  356.     /**
  357.      * @return $this<T>
  358.      */
  359.     public function resetMaxNbPages(): self
  360.     {
  361.         $this->maxNbPages null;
  362.         return $this;
  363.     }
  364.     /**
  365.      * @return bool
  366.      */
  367.     public function haveToPaginate()
  368.     {
  369.         return $this->getNbResults() > $this->maxPerPage;
  370.     }
  371.     /**
  372.      * @return bool
  373.      */
  374.     public function hasPreviousPage()
  375.     {
  376.         return $this->currentPage 1;
  377.     }
  378.     /**
  379.      * @return int
  380.      *
  381.      * @throws LogicException if there is no previous page
  382.      */
  383.     public function getPreviousPage()
  384.     {
  385.         if (!$this->hasPreviousPage()) {
  386.             throw new LogicException('There is no previous page.');
  387.         }
  388.         return $this->currentPage 1;
  389.     }
  390.     /**
  391.      * @return bool
  392.      */
  393.     public function hasNextPage()
  394.     {
  395.         return $this->currentPage $this->getNbPages();
  396.     }
  397.     /**
  398.      * @return int
  399.      *
  400.      * @throws LogicException if there is no next page
  401.      */
  402.     public function getNextPage()
  403.     {
  404.         if (!$this->hasNextPage()) {
  405.             throw new LogicException('There is no next page.');
  406.         }
  407.         return $this->currentPage 1;
  408.     }
  409.     /**
  410.      * @return int
  411.      */
  412.     #[\ReturnTypeWillChange]
  413.     public function count()
  414.     {
  415.         return $this->getNbResults();
  416.     }
  417.     /**
  418.      * @return \Traversable<array-key, T>
  419.      */
  420.     #[\ReturnTypeWillChange]
  421.     public function getIterator()
  422.     {
  423.         $results $this->getCurrentPageResults();
  424.         if ($results instanceof \Iterator) {
  425.             return $results;
  426.         }
  427.         if ($results instanceof \IteratorAggregate) {
  428.             return $results->getIterator();
  429.         }
  430.         if (\is_array($results)) {
  431.             return new \ArrayIterator($results);
  432.         }
  433.         throw new \InvalidArgumentException(sprintf('Cannot create iterator with page results of type "%s".'get_debug_type($results)));
  434.     }
  435.     /**
  436.      * @return iterable
  437.      */
  438.     #[\ReturnTypeWillChange]
  439.     public function jsonSerialize()
  440.     {
  441.         $results $this->getCurrentPageResults();
  442.         if ($results instanceof \Traversable) {
  443.             return iterator_to_array($results);
  444.         }
  445.         return $results;
  446.     }
  447.     /**
  448.      * Get page number of the item at specified position (1-based index).
  449.      *
  450.      * @param int $position
  451.      *
  452.      * @return int
  453.      *
  454.      * @throws NotIntegerException  if the position is not an integer
  455.      * @throws OutOfBoundsException if the item is outside the result set
  456.      */
  457.     public function getPageNumberForItemAtPosition($position)
  458.     {
  459.         if (!\is_int($position)) {
  460.             throw new NotIntegerException();
  461.         }
  462.         if ($this->getNbResults() < $position) {
  463.             throw new OutOfBoundsException(sprintf('Item requested at position %d, but there are only %d items.'$position$this->getNbResults()));
  464.         }
  465.         return (int) ceil($position $this->getMaxPerPage());
  466.     }
  467.     /**
  468.      * @param bool $value
  469.      *
  470.      * @throws NotBooleanException if the value is not boolean
  471.      */
  472.     private function filterBoolean($value): bool
  473.     {
  474.         if (!\is_bool($value)) {
  475.             throw new NotBooleanException();
  476.         }
  477.         return $value;
  478.     }
  479.     /**
  480.      * @param int|float|string $value
  481.      *
  482.      * @return int
  483.      */
  484.     private function toInteger($value)
  485.     {
  486.         if ($this->needsToIntegerConversion($value)) {
  487.             return (int) $value;
  488.         }
  489.         return $value;
  490.     }
  491.     /**
  492.      * @param int|float|string $value
  493.      */
  494.     private function needsToIntegerConversion($value): bool
  495.     {
  496.         return (\is_string($value) || \is_float($value)) && (int) $value == $value;
  497.     }
  498. }