vendor/pagerfanta/pagerfanta/src/Pagerfanta.php line 16

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