diff --git a/composer.json b/composer.json index 5a8b404..3ff283e 100644 --- a/composer.json +++ b/composer.json @@ -13,8 +13,7 @@ } }, "require": { - "php": ">=5.5", - "zendframework/zend-stdlib": "dev-develop as 2.8.0" + "php": ">=5.5" }, "require-dev": { "zendframework/zend-cache": "dev-develop as 2.6.0", @@ -24,6 +23,7 @@ "zendframework/zend-json": "~2.5", "zendframework/zend-mvc": "dev-develop as 2.7.0", "zendframework/zend-servicemanager": "dev-develop as 2.7.0", + "zendframework/zend-stdlib": "dev-develop as 2.8.0", "zendframework/zend-view": "dev-develop as 2.6.0", "fabpot/php-cs-fixer": "1.7.*", "phpunit/PHPUnit": "~4.0" @@ -34,6 +34,7 @@ "zendframework/zend-filter": "Zend\\Filter component", "zendframework/zend-json": "Zend\\Json component", "zendframework/zend-servicemanager": "Zend\\ServiceManager component", + "zendframework/zend-stdlib": "Zend\\Stdlib component", "zendframework/zend-view": "Zend\\View component" }, "minimum-stability": "dev", diff --git a/src/CachedTrait.php b/src/CachedTrait.php new file mode 100644 index 0000000..e4e32be --- /dev/null +++ b/src/CachedTrait.php @@ -0,0 +1,147 @@ +cacheEnabled = (bool) $enable; + return $this; + } + + /** + * Returns the page item cache. + * + * @return array + */ + public function getPageItemCache() + { + $data = []; + if ($this->cacheEnabled()) { + $prefixLength = strlen(self::CACHE_TAG_PREFIX); + $cacheIterator = static::$cache->getIterator(); + $cacheIterator->setMode(CacheIterator::CURRENT_AS_VALUE); + foreach ($cacheIterator as $key => $value) { + if (substr($key, 0, $prefixLength) == self::CACHE_TAG_PREFIX) { + $data[(int) substr($key, $prefixLength)] = $value; + } + } + } + return $data; + } + + /** + * Clear the page item cache. + * + * @param int $pageNumber + * @return Paginator + */ + public function clearPageItemCache($pageNumber = null) + { + if (!$this->cacheEnabled()) { + return $this; + } + + if (null === $pageNumber) { + $prefixLength = strlen(self::CACHE_TAG_PREFIX); + $cacheIterator = static::$cache->getIterator(); + $cacheIterator->setMode(CacheIterator::CURRENT_AS_KEY); + foreach ($cacheIterator as $key) { + if (substr($key, 0, $prefixLength) == self::CACHE_TAG_PREFIX) { + static::$cache->removeItem($this->_getCacheId((int)substr($key, $prefixLength))); + } + } + } else { + $cleanId = $this->_getCacheId($pageNumber); + static::$cache->removeItem($cleanId); + } + return $this; + } + + /** + * Tells if there is an active cache object + * and if the cache has not been disabled + * + * @return bool + */ + protected function cacheEnabled() + { + return ((static::$cache !== null) && $this->cacheEnabled); + } + + /** + * Makes an Id for the cache + * Depends on the adapter object and the page number + * + * Used to store item in cache from that Paginator instance + * and that current page + * + * @param int $page + * @return string + */ + protected function _getCacheId($page = null) + { + if ($page === null) { + $page = $this->getCurrentPageNumber(); + } + return self::CACHE_TAG_PREFIX . $page . '_' . $this->_getCacheInternalId(); + } + + /** + * Get the internal cache id + * Depends on the adapter and the item count per page + * + * Used to tag that unique Paginator instance in cache + * + * @return string + */ + protected function _getCacheInternalId() + { + return md5(serialize([ + spl_object_hash($this->getAdapter()), + $this->getItemCountPerPage() + ])); + } +} diff --git a/src/FilterTrait.php b/src/FilterTrait.php new file mode 100644 index 0000000..2f6b468 --- /dev/null +++ b/src/FilterTrait.php @@ -0,0 +1,46 @@ +filter; + } + + /** + * Set a filter chain + * + * @param FilterInterface $filter + * @return Paginator + */ + public function setFilter(FilterInterface $filter) + { + $this->filter = $filter; + + return $this; + } +} diff --git a/src/GlobalPaginator.php b/src/GlobalPaginator.php new file mode 100644 index 0000000..a2306b1 --- /dev/null +++ b/src/GlobalPaginator.php @@ -0,0 +1,246 @@ +$setupMethod($value); + } + } + } + } + + /** + * Returns the number of items per page. + * + * @return int + */ + public function getItemCountPerPage() + { + if (empty($this->itemCountPerPage)) { + $this->itemCountPerPage = static::getDefaultItemCountPerPage(); + } + + return $this->itemCountPerPage; + } + + /** + * Loads a scrolling style. + * + * @param string $scrollingStyle + * @return ScrollingStyleInterface + * @throws Exception\InvalidArgumentException + */ + protected function _loadScrollingStyle($scrollingStyle = null) + { + if ($scrollingStyle === null) { + $scrollingStyle = static::$defaultScrollingStyle; + } + + switch (strtolower(gettype($scrollingStyle))) { + case 'object': + if (!$scrollingStyle instanceof ScrollingStyleInterface) { + throw new Exception\InvalidArgumentException( + 'Scrolling style must implement Zend\Paginator\ScrollingStyle\ScrollingStyleInterface' + ); + } + + return $scrollingStyle; + + case 'string': + return static::getScrollingStylePluginManager()->get($scrollingStyle); + + case 'null': + // Fall through to default case + + default: + throw new Exception\InvalidArgumentException( + 'Scrolling style must be a class ' . + 'name or object implementing Zend\Paginator\ScrollingStyle\ScrollingStyleInterface' + ); + } + } +} diff --git a/src/GlobalSetupInterface.php b/src/GlobalSetupInterface.php new file mode 100644 index 0000000..5f5fae9 --- /dev/null +++ b/src/GlobalSetupInterface.php @@ -0,0 +1,59 @@ +getCurrentItems(); + + if ($currentItems instanceof AbstractResultSet) { + return Json::encode($currentItems->toArray()); + } + return Json::encode($currentItems); + } +} diff --git a/src/Paginator.php b/src/Paginator.php index bec3691..898ee80 100644 --- a/src/Paginator.php +++ b/src/Paginator.php @@ -10,582 +10,24 @@ namespace Zend\Paginator; use ArrayIterator; -use Countable; -use IteratorAggregate; use Traversable; -use Zend\Cache\Storage\IteratorInterface as CacheIterator; -use Zend\Cache\Storage\StorageInterface as CacheStorage; -use Zend\Db\ResultSet\AbstractResultSet; -use Zend\Filter\FilterInterface; -use Zend\Json\Json; -use Zend\Paginator\Adapter\AdapterInterface; -use Zend\Paginator\ScrollingStyle\ScrollingStyleInterface; -use Zend\Stdlib\ArrayUtils; -use Zend\View; -use Zend\ServiceManager\ServiceManager; -class Paginator implements Countable, IteratorAggregate +class Paginator extends GlobalPaginator { + use JsonSerializeTrait, + RenderTrait, + FilterTrait, + CachedTrait; + /** * The cache tag prefix used to namespace Paginator results in the cache * */ const CACHE_TAG_PREFIX = 'Zend_Paginator_'; - /** - * Adapter plugin manager - * - * @var AdapterPluginManager - */ - protected static $adapters = null; - - /** - * Configuration file - * - * @var array|null - */ - protected static $config = null; - - /** - * Default scrolling style - * - * @var string - */ - protected static $defaultScrollingStyle = 'Sliding'; - - /** - * Default item count per page - * - * @var int - */ - protected static $defaultItemCountPerPage = 10; - - /** - * Scrolling style plugin manager - * - * @var ScrollingStylePluginManager - */ - protected static $scrollingStyles = null; - - /** - * Cache object - * - * @var CacheStorage - */ - protected static $cache; - - /** - * Enable or disable the cache by Zend\Paginator\Paginator instance - * - * @var bool - */ - protected $cacheEnabled = true; - - /** - * Adapter - * - * @var AdapterInterface - */ - protected $adapter = null; - - /** - * Number of items in the current page - * - * @var int - */ - protected $currentItemCount = null; - - /** - * Current page items - * - * @var Traversable - */ - protected $currentItems = null; - - /** - * Current page number (starting from 1) - * - * @var int - */ - protected $currentPageNumber = 1; - - /** - * Result filter - * - * @var FilterInterface - */ - protected $filter = null; - - /** - * Number of items per page - * - * @var int - */ - protected $itemCountPerPage = null; - - /** - * Number of pages - * - * @var int - */ - protected $pageCount = null; - - /** - * Number of local pages (i.e., the number of discrete page numbers - * that will be displayed, including the current page number) - * - * @var int - */ - protected $pageRange = 10; - - /** - * Pages - * - * @var \stdClass - */ - protected $pages = null; - - /** - * View instance used for self rendering - * - * @var \Zend\View\Renderer\RendererInterface - */ - protected $view = null; - - /** - * Set a global config - * - * @param array|Traversable $config - * @throws Exception\InvalidArgumentException - */ - public static function setGlobalConfig($config) - { - if ($config instanceof Traversable) { - $config = ArrayUtils::iteratorToArray($config); - } - if (!is_array($config)) { - throw new Exception\InvalidArgumentException(__METHOD__ . ' expects an array or Traversable'); - } - - static::$config = $config; - - if (isset($config['scrolling_style_plugins']) - && null !== ($adapters = $config['scrolling_style_plugins']) - ) { - static::setScrollingStylePluginManager($adapters); - } - - $scrollingStyle = isset($config['scrolling_style']) ? $config['scrolling_style'] : null; - - if ($scrollingStyle !== null) { - static::setDefaultScrollingStyle($scrollingStyle); - } - } - - /** - * Returns the default scrolling style. - * - * @return string - */ - public static function getDefaultScrollingStyle() - { - return static::$defaultScrollingStyle; - } - - /** - * Get the default item count per page - * - * @return int - */ - public static function getDefaultItemCountPerPage() - { - return static::$defaultItemCountPerPage; - } - - /** - * Set the default item count per page - * - * @param int $count - */ - public static function setDefaultItemCountPerPage($count) - { - static::$defaultItemCountPerPage = (int) $count; - } - - /** - * Sets a cache object - * - * @param CacheStorage $cache - */ - public static function setCache(CacheStorage $cache) - { - static::$cache = $cache; - } - - /** - * Sets the default scrolling style. - * - * @param string $scrollingStyle - */ - public static function setDefaultScrollingStyle($scrollingStyle = 'Sliding') - { - static::$defaultScrollingStyle = $scrollingStyle; - } - - public static function setScrollingStylePluginManager($scrollingAdapters) - { - if (is_string($scrollingAdapters)) { - if (!class_exists($scrollingAdapters)) { - throw new Exception\InvalidArgumentException(sprintf( - 'Unable to locate scrolling style plugin manager with class "%s"; class not found', - $scrollingAdapters - )); - } - $scrollingAdapters = new $scrollingAdapters(new ServiceManager); - } - if (!$scrollingAdapters instanceof ScrollingStylePluginManager) { - throw new Exception\InvalidArgumentException(sprintf( - 'Pagination scrolling-style manager must extend ScrollingStylePluginManager; received "%s"', - (is_object($scrollingAdapters) ? get_class($scrollingAdapters) : gettype($scrollingAdapters)) - )); - } - static::$scrollingStyles = $scrollingAdapters; - } - - /** - * Returns the scrolling style manager. If it doesn't exist it's - * created. - * - * @return ScrollingStylePluginManager - */ - public static function getScrollingStylePluginManager() - { - if (static::$scrollingStyles === null) { - static::$scrollingStyles = new ScrollingStylePluginManager(new ServiceManager); - } - - return static::$scrollingStyles; - } - - /** - * Constructor. - * - * @param AdapterInterface|AdapterAggregateInterface $adapter - * @throws Exception\InvalidArgumentException - */ - public function __construct($adapter) - { - if ($adapter instanceof AdapterInterface) { - $this->adapter = $adapter; - } elseif ($adapter instanceof AdapterAggregateInterface) { - $this->adapter = $adapter->getPaginatorAdapter(); - } else { - throw new Exception\InvalidArgumentException( - 'Zend\Paginator only accepts instances of the type ' . - 'Zend\Paginator\Adapter\AdapterInterface or Zend\Paginator\AdapterAggregateInterface.' - ); - } - - $config = static::$config; - - if (!empty($config)) { - $setupMethods = ['ItemCountPerPage', 'PageRange']; - - foreach ($setupMethods as $setupMethod) { - $key = strtolower($setupMethod); - $value = isset($config[$key]) ? $config[$key] : null; - - if ($value !== null) { - $setupMethod = 'set' . $setupMethod; - $this->$setupMethod($value); - } - } - } - } - - /** - * Serializes the object as a string. Proxies to {@link render()}. - * - * @return string - */ - public function __toString() - { - try { - $return = $this->render(); - return $return; - } catch (\Exception $e) { - trigger_error($e->getMessage(), E_USER_WARNING); - } - - return ''; - } - - /** - * Enables/Disables the cache for this instance - * - * @param bool $enable - * @return Paginator - */ - public function setCacheEnabled($enable) - { - $this->cacheEnabled = (bool) $enable; - return $this; - } - - /** - * Returns the number of pages. - * - * @return int - */ - public function count() - { - if (!$this->pageCount) { - $this->pageCount = $this->_calculatePageCount(); - } - - return $this->pageCount; - } - - /** - * Returns the total number of items available. - * - * @return int - */ - public function getTotalItemCount() - { - return count($this->getAdapter()); - } - - /** - * Clear the page item cache. - * - * @param int $pageNumber - * @return Paginator - */ - public function clearPageItemCache($pageNumber = null) - { - if (!$this->cacheEnabled()) { - return $this; - } - - if (null === $pageNumber) { - $prefixLength = strlen(self::CACHE_TAG_PREFIX); - $cacheIterator = static::$cache->getIterator(); - $cacheIterator->setMode(CacheIterator::CURRENT_AS_KEY); - foreach ($cacheIterator as $key) { - if (substr($key, 0, $prefixLength) == self::CACHE_TAG_PREFIX) { - static::$cache->removeItem($this->_getCacheId((int)substr($key, $prefixLength))); - } - } - } else { - $cleanId = $this->_getCacheId($pageNumber); - static::$cache->removeItem($cleanId); - } - return $this; - } - - /** - * Returns the absolute item number for the specified item. - * - * @param int $relativeItemNumber Relative item number - * @param int $pageNumber Page number - * @return int - */ - public function getAbsoluteItemNumber($relativeItemNumber, $pageNumber = null) - { - $relativeItemNumber = $this->normalizeItemNumber($relativeItemNumber); - - if ($pageNumber === null) { - $pageNumber = $this->getCurrentPageNumber(); - } - - $pageNumber = $this->normalizePageNumber($pageNumber); - - return (($pageNumber - 1) * $this->getItemCountPerPage()) + $relativeItemNumber; - } - - /** - * Returns the adapter. - * - * @return AdapterInterface - */ - public function getAdapter() - { - return $this->adapter; - } - - /** - * Returns the number of items for the current page. - * - * @return int - */ - public function getCurrentItemCount() - { - if ($this->currentItemCount === null) { - $this->currentItemCount = $this->getItemCount($this->getCurrentItems()); - } - - return $this->currentItemCount; - } - - /** - * Returns the items for the current page. - * - * @return Traversable - */ - public function getCurrentItems() - { - if ($this->currentItems === null) { - $this->currentItems = $this->getItemsByPage($this->getCurrentPageNumber()); - } - - return $this->currentItems; - } - - /** - * Returns the current page number. - * - * @return int - */ - public function getCurrentPageNumber() - { - return $this->normalizePageNumber($this->currentPageNumber); - } - - /** - * Sets the current page number. - * - * @param int $pageNumber Page number - * @return Paginator $this - */ - public function setCurrentPageNumber($pageNumber) - { - $this->currentPageNumber = (int) $pageNumber; - $this->currentItems = null; - $this->currentItemCount = null; - - return $this; - } - - /** - * Get the filter - * - * @return FilterInterface - */ - public function getFilter() - { - return $this->filter; - } - - /** - * Set a filter chain - * - * @param FilterInterface $filter - * @return Paginator - */ - public function setFilter(FilterInterface $filter) - { - $this->filter = $filter; - - return $this; - } /** - * Returns an item from a page. The current page is used if there's no - * page specified. - * - * @param int $itemNumber Item number (1 to itemCountPerPage) - * @param int $pageNumber - * @throws Exception\InvalidArgumentException - * @return mixed - */ - public function getItem($itemNumber, $pageNumber = null) - { - if ($pageNumber === null) { - $pageNumber = $this->getCurrentPageNumber(); - } elseif ($pageNumber < 0) { - $pageNumber = ($this->count() + 1) + $pageNumber; - } - - $page = $this->getItemsByPage($pageNumber); - $itemCount = $this->getItemCount($page); - - if ($itemCount == 0) { - throw new Exception\InvalidArgumentException('Page ' . $pageNumber . ' does not exist'); - } - - if ($itemNumber < 0) { - $itemNumber = ($itemCount + 1) + $itemNumber; - } - - $itemNumber = $this->normalizeItemNumber($itemNumber); - - if ($itemNumber > $itemCount) { - throw new Exception\InvalidArgumentException( - "Page {$pageNumber} does not contain item number {$itemNumber}" - ); - } - - return $page[$itemNumber - 1]; - } - - /** - * Returns the number of items per page. - * - * @return int - */ - public function getItemCountPerPage() - { - if (empty($this->itemCountPerPage)) { - $this->itemCountPerPage = static::getDefaultItemCountPerPage(); - } - - return $this->itemCountPerPage; - } - - /** - * Sets the number of items per page. - * - * @param int $itemCountPerPage - * @return Paginator $this - */ - public function setItemCountPerPage($itemCountPerPage = -1) - { - $this->itemCountPerPage = (int) $itemCountPerPage; - if ($this->itemCountPerPage < 1) { - $this->itemCountPerPage = $this->getTotalItemCount(); - } - $this->pageCount = $this->_calculatePageCount(); - $this->currentItems = null; - $this->currentItemCount = null; - - return $this; - } - - /** - * Returns the number of items in a collection. - * - * @param mixed $items Items - * @return int - */ - public function getItemCount($items) - { - $itemCount = 0; - - if (is_array($items) || $items instanceof Countable) { - $itemCount = count($items); - } elseif ($items instanceof Traversable) { // $items is something like LimitIterator - $itemCount = iterator_count($items); - } - - return $itemCount; - } - - /** - * Returns the items for a given page. - * - * @param int $pageNumber - * @return mixed + * @inheritdoc */ public function getItemsByPage($pageNumber) { @@ -619,344 +61,4 @@ public function getItemsByPage($pageNumber) return $items; } - - /** - * Returns a foreach-compatible iterator. - * - * @throws Exception\RuntimeException - * @return Traversable - */ - public function getIterator() - { - try { - return $this->getCurrentItems(); - } catch (\Exception $e) { - throw new Exception\RuntimeException('Error producing an iterator', null, $e); - } - } - - /** - * Returns the page range (see property declaration above). - * - * @return int - */ - public function getPageRange() - { - return $this->pageRange; - } - - /** - * Sets the page range (see property declaration above). - * - * @param int $pageRange - * @return Paginator $this - */ - public function setPageRange($pageRange) - { - $this->pageRange = (int) $pageRange; - - return $this; - } - - /** - * Returns the page collection. - * - * @param string $scrollingStyle Scrolling style - * @return \stdClass - */ - public function getPages($scrollingStyle = null) - { - if ($this->pages === null) { - $this->pages = $this->_createPages($scrollingStyle); - } - - return $this->pages; - } - - /** - * Returns a subset of pages within a given range. - * - * @param int $lowerBound Lower bound of the range - * @param int $upperBound Upper bound of the range - * @return array - */ - public function getPagesInRange($lowerBound, $upperBound) - { - $lowerBound = $this->normalizePageNumber($lowerBound); - $upperBound = $this->normalizePageNumber($upperBound); - - $pages = []; - - for ($pageNumber = $lowerBound; $pageNumber <= $upperBound; $pageNumber++) { - $pages[$pageNumber] = $pageNumber; - } - - return $pages; - } - - /** - * Returns the page item cache. - * - * @return array - */ - public function getPageItemCache() - { - $data = []; - if ($this->cacheEnabled()) { - $prefixLength = strlen(self::CACHE_TAG_PREFIX); - $cacheIterator = static::$cache->getIterator(); - $cacheIterator->setMode(CacheIterator::CURRENT_AS_VALUE); - foreach ($cacheIterator as $key => $value) { - if (substr($key, 0, $prefixLength) == self::CACHE_TAG_PREFIX) { - $data[(int) substr($key, $prefixLength)] = $value; - } - } - } - return $data; - } - - /** - * Retrieves the view instance. - * - * If none registered, instantiates a PhpRenderer instance. - * - * @return \Zend\View\Renderer\RendererInterface|null - */ - public function getView() - { - if ($this->view === null) { - $this->setView(new View\Renderer\PhpRenderer()); - } - - return $this->view; - } - - /** - * Sets the view object. - * - * @param \Zend\View\Renderer\RendererInterface $view - * @return Paginator - */ - public function setView(View\Renderer\RendererInterface $view = null) - { - $this->view = $view; - - return $this; - } - - /** - * Brings the item number in range of the page. - * - * @param int $itemNumber - * @return int - */ - public function normalizeItemNumber($itemNumber) - { - $itemNumber = (int) $itemNumber; - - if ($itemNumber < 1) { - $itemNumber = 1; - } - - if ($itemNumber > $this->getItemCountPerPage()) { - $itemNumber = $this->getItemCountPerPage(); - } - - return $itemNumber; - } - - /** - * Brings the page number in range of the paginator. - * - * @param int $pageNumber - * @return int - */ - public function normalizePageNumber($pageNumber) - { - $pageNumber = (int) $pageNumber; - - if ($pageNumber < 1) { - $pageNumber = 1; - } - - $pageCount = $this->count(); - - if ($pageCount > 0 && $pageNumber > $pageCount) { - $pageNumber = $pageCount; - } - - return $pageNumber; - } - - /** - * Renders the paginator. - * - * @param \Zend\View\Renderer\RendererInterface $view - * @return string - */ - public function render(View\Renderer\RendererInterface $view = null) - { - if (null !== $view) { - $this->setView($view); - } - - $view = $this->getView(); - - return $view->paginationControl($this); - } - - /** - * Returns the items of the current page as JSON. - * - * @return string - */ - public function toJson() - { - $currentItems = $this->getCurrentItems(); - - if ($currentItems instanceof AbstractResultSet) { - return Json::encode($currentItems->toArray()); - } - return Json::encode($currentItems); - } - - /** - * Tells if there is an active cache object - * and if the cache has not been disabled - * - * @return bool - */ - protected function cacheEnabled() - { - return ((static::$cache !== null) && $this->cacheEnabled); - } - - /** - * Makes an Id for the cache - * Depends on the adapter object and the page number - * - * Used to store item in cache from that Paginator instance - * and that current page - * - * @param int $page - * @return string - */ - protected function _getCacheId($page = null) - { - if ($page === null) { - $page = $this->getCurrentPageNumber(); - } - return self::CACHE_TAG_PREFIX . $page . '_' . $this->_getCacheInternalId(); - } - - /** - * Get the internal cache id - * Depends on the adapter and the item count per page - * - * Used to tag that unique Paginator instance in cache - * - * @return string - */ - protected function _getCacheInternalId() - { - return md5(serialize([ - spl_object_hash($this->getAdapter()), - $this->getItemCountPerPage() - ])); - } - - /** - * Calculates the page count. - * - * @return int - */ - protected function _calculatePageCount() - { - return (int) ceil($this->getAdapter()->count() / $this->getItemCountPerPage()); - } - - /** - * Creates the page collection. - * - * @param string $scrollingStyle Scrolling style - * @return \stdClass - */ - protected function _createPages($scrollingStyle = null) - { - $pageCount = $this->count(); - $currentPageNumber = $this->getCurrentPageNumber(); - - $pages = new \stdClass(); - $pages->pageCount = $pageCount; - $pages->itemCountPerPage = $this->getItemCountPerPage(); - $pages->first = 1; - $pages->current = $currentPageNumber; - $pages->last = $pageCount; - - // Previous and next - if ($currentPageNumber - 1 > 0) { - $pages->previous = $currentPageNumber - 1; - } - - if ($currentPageNumber + 1 <= $pageCount) { - $pages->next = $currentPageNumber + 1; - } - - // Pages in range - $scrollingStyle = $this->_loadScrollingStyle($scrollingStyle); - $pages->pagesInRange = $scrollingStyle->getPages($this); - $pages->firstPageInRange = min($pages->pagesInRange); - $pages->lastPageInRange = max($pages->pagesInRange); - - // Item numbers - if ($this->getCurrentItems() !== null) { - $pages->currentItemCount = $this->getCurrentItemCount(); - $pages->totalItemCount = $this->getTotalItemCount(); - $pages->firstItemNumber = $pages->totalItemCount - ? (($currentPageNumber - 1) * $pages->itemCountPerPage) + 1 - : 0; - $pages->lastItemNumber = $pages->totalItemCount - ? $pages->firstItemNumber + $pages->currentItemCount - 1 - : 0; - } - - return $pages; - } - - /** - * Loads a scrolling style. - * - * @param string $scrollingStyle - * @return ScrollingStyleInterface - * @throws Exception\InvalidArgumentException - */ - protected function _loadScrollingStyle($scrollingStyle = null) - { - if ($scrollingStyle === null) { - $scrollingStyle = static::$defaultScrollingStyle; - } - - switch (strtolower(gettype($scrollingStyle))) { - case 'object': - if (!$scrollingStyle instanceof ScrollingStyleInterface) { - throw new Exception\InvalidArgumentException( - 'Scrolling style must implement Zend\Paginator\ScrollingStyle\ScrollingStyleInterface' - ); - } - - return $scrollingStyle; - - case 'string': - return static::getScrollingStylePluginManager()->get($scrollingStyle); - - case 'null': - // Fall through to default case - - default: - throw new Exception\InvalidArgumentException( - 'Scrolling style must be a class ' . - 'name or object implementing Zend\Paginator\ScrollingStyle\ScrollingStyleInterface' - ); - } - } } diff --git a/src/PaginatorInterface.php b/src/PaginatorInterface.php new file mode 100644 index 0000000..c29da98 --- /dev/null +++ b/src/PaginatorInterface.php @@ -0,0 +1,176 @@ +view === null) { + $this->setView(new View\Renderer\PhpRenderer()); + } + + return $this->view; + } + + /** + * Sets the view object. + * + * @param \Zend\View\Renderer\RendererInterface $view + * @return Paginator + */ + public function setView(View\Renderer\RendererInterface $view = null) + { + $this->view = $view; + + return $this; + } + + /** + * Renders the paginator. + * + * @param \Zend\View\Renderer\RendererInterface $view + * @return string + */ + public function render(View\Renderer\RendererInterface $view = null) + { + if (null !== $view) { + $this->setView($view); + } + + $view = $this->getView(); + + return $view->paginationControl($this); + } + + /** + * Serializes the object as a string. Proxies to {@link render()}. + * + * @return string + */ + public function __toString() + { + try { + $return = $this->render(); + return $return; + } catch (\Exception $e) { + trigger_error($e->getMessage(), E_USER_WARNING); + } + + return ''; + } +} diff --git a/src/ScrollingStyle/All.php b/src/ScrollingStyle/All.php index c7f44b8..4417524 100644 --- a/src/ScrollingStyle/All.php +++ b/src/ScrollingStyle/All.php @@ -9,7 +9,7 @@ namespace Zend\Paginator\ScrollingStyle; -use Zend\Paginator\Paginator; +use Zend\Paginator\PaginatorInterface; /** * A scrolling style that returns every page in the collection. @@ -25,7 +25,7 @@ class All implements ScrollingStyleInterface * @param int $pageRange Unused * @return array */ - public function getPages(Paginator $paginator, $pageRange = null) + public function getPages(PaginatorInterface $paginator, $pageRange = null) { return $paginator->getPagesInRange(1, $paginator->count()); } diff --git a/src/ScrollingStyle/Elastic.php b/src/ScrollingStyle/Elastic.php index 8183ae2..486cfbc 100644 --- a/src/ScrollingStyle/Elastic.php +++ b/src/ScrollingStyle/Elastic.php @@ -9,7 +9,7 @@ namespace Zend\Paginator\ScrollingStyle; -use Zend\Paginator\Paginator; +use Zend\Paginator\PaginatorInterface; /** * A Google-like scrolling style. Incrementally expands the range to about @@ -27,7 +27,7 @@ class Elastic extends Sliding * @param int $pageRange Unused * @return array */ - public function getPages(Paginator $paginator, $pageRange = null) + public function getPages(PaginatorInterface $paginator, $pageRange = null) { $pageRange = $paginator->getPageRange(); $pageNumber = $paginator->getCurrentPageNumber(); diff --git a/src/ScrollingStyle/Jumping.php b/src/ScrollingStyle/Jumping.php index f560c9b..547c7be 100644 --- a/src/ScrollingStyle/Jumping.php +++ b/src/ScrollingStyle/Jumping.php @@ -9,7 +9,7 @@ namespace Zend\Paginator\ScrollingStyle; -use Zend\Paginator\Paginator; +use Zend\Paginator\PaginatorInterface; /** * A scrolling style in which the cursor advances to the upper bound @@ -25,7 +25,7 @@ class Jumping implements ScrollingStyleInterface * @param int $pageRange Unused * @return array */ - public function getPages(Paginator $paginator, $pageRange = null) + public function getPages(PaginatorInterface $paginator, $pageRange = null) { $pageRange = $paginator->getPageRange(); $pageNumber = $paginator->getCurrentPageNumber(); diff --git a/src/ScrollingStyle/ScrollingStyleInterface.php b/src/ScrollingStyle/ScrollingStyleInterface.php index 3bf7c97..66b91ee 100644 --- a/src/ScrollingStyle/ScrollingStyleInterface.php +++ b/src/ScrollingStyle/ScrollingStyleInterface.php @@ -9,7 +9,7 @@ namespace Zend\Paginator\ScrollingStyle; -use Zend\Paginator\Paginator; +use Zend\Paginator\PaginatorInterface; interface ScrollingStyleInterface { @@ -20,5 +20,5 @@ interface ScrollingStyleInterface * @param int $pageRange (Optional) Page range * @return array */ - public function getPages(Paginator $paginator, $pageRange = null); + public function getPages(PaginatorInterface $paginator, $pageRange = null); } diff --git a/src/ScrollingStyle/Sliding.php b/src/ScrollingStyle/Sliding.php index 00ecf50..c2822c9 100644 --- a/src/ScrollingStyle/Sliding.php +++ b/src/ScrollingStyle/Sliding.php @@ -9,7 +9,7 @@ namespace Zend\Paginator\ScrollingStyle; -use Zend\Paginator\Paginator; +use Zend\Paginator\PaginatorInterface; /** * A Yahoo! Search-like scrolling style. The cursor will advance to @@ -28,7 +28,7 @@ class Sliding implements ScrollingStyleInterface * @param int $pageRange (Optional) Page range * @return array */ - public function getPages(Paginator $paginator, $pageRange = null) + public function getPages(PaginatorInterface $paginator, $pageRange = null) { if ($pageRange === null) { $pageRange = $paginator->getPageRange(); diff --git a/src/SimplePaginator.php b/src/SimplePaginator.php new file mode 100644 index 0000000..c391c6d --- /dev/null +++ b/src/SimplePaginator.php @@ -0,0 +1,517 @@ +adapter = $adapter; + } elseif ($adapter instanceof AdapterAggregateInterface) { + $this->adapter = $adapter->getPaginatorAdapter(); + } else { + throw new Exception\InvalidArgumentException( + 'Zend\Paginator only accepts instances of the type ' . + 'Zend\Paginator\Adapter\AdapterInterface or Zend\Paginator\AdapterAggregateInterface.' + ); + } + } + + /** + * Returns the number of pages. + * + * @return int + */ + public function count() + { + if (!$this->pageCount) { + $this->pageCount = $this->_calculatePageCount(); + } + + return $this->pageCount; + } + + /** + * Returns the total number of items available. + * + * @return int + */ + public function getTotalItemCount() + { + return count($this->getAdapter()); + } + + /** + * Returns the absolute item number for the specified item. + * + * @param int $relativeItemNumber Relative item number + * @param int $pageNumber Page number + * @return int + */ + public function getAbsoluteItemNumber($relativeItemNumber, $pageNumber = null) + { + $relativeItemNumber = $this->normalizeItemNumber($relativeItemNumber); + + if ($pageNumber === null) { + $pageNumber = $this->getCurrentPageNumber(); + } + + $pageNumber = $this->normalizePageNumber($pageNumber); + + return (($pageNumber - 1) * $this->getItemCountPerPage()) + $relativeItemNumber; + } + + /** + * Returns the adapter. + * + * @return AdapterInterface + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * Returns the number of items for the current page. + * + * @return int + */ + public function getCurrentItemCount() + { + if ($this->currentItemCount === null) { + $this->currentItemCount = $this->getItemCount($this->getCurrentItems()); + } + + return $this->currentItemCount; + } + + /** + * Returns the items for the current page. + * + * @return Traversable + */ + public function getCurrentItems() + { + if ($this->currentItems === null) { + $this->currentItems = $this->getItemsByPage($this->getCurrentPageNumber()); + } + + return $this->currentItems; + } + + /** + * Returns the current page number. + * + * @return int + */ + public function getCurrentPageNumber() + { + return $this->normalizePageNumber($this->currentPageNumber); + } + + /** + * Sets the current page number. + * + * @param int $pageNumber Page number + * @return Paginator $this + */ + public function setCurrentPageNumber($pageNumber) + { + $this->currentPageNumber = (int) $pageNumber; + $this->currentItems = null; + $this->currentItemCount = null; + + return $this; + } + + /** + * Returns an item from a page. The current page is used if there's no + * page specified. + * + * @param int $itemNumber Item number (1 to itemCountPerPage) + * @param int $pageNumber + * @throws Exception\InvalidArgumentException + * @return mixed + */ + public function getItem($itemNumber, $pageNumber = null) + { + if ($pageNumber === null) { + $pageNumber = $this->getCurrentPageNumber(); + } elseif ($pageNumber < 0) { + $pageNumber = ($this->count() + 1) + $pageNumber; + } + + $page = $this->getItemsByPage($pageNumber); + $itemCount = $this->getItemCount($page); + + if ($itemCount == 0) { + throw new Exception\InvalidArgumentException('Page ' . $pageNumber . ' does not exist'); + } + + if ($itemNumber < 0) { + $itemNumber = ($itemCount + 1) + $itemNumber; + } + + $itemNumber = $this->normalizeItemNumber($itemNumber); + + if ($itemNumber > $itemCount) { + throw new Exception\InvalidArgumentException( + "Page {$pageNumber} does not contain item number {$itemNumber}" + ); + } + + return $page[$itemNumber - 1]; + } + + /** + * Returns the number of items per page. + * + * @return int + */ + public function getItemCountPerPage() + { + return $this->itemCountPerPage; + } + + /** + * Sets the number of items per page. + * + * @param int $itemCountPerPage + * @return Paginator $this + */ + public function setItemCountPerPage($itemCountPerPage = -1) + { + $this->itemCountPerPage = (int) $itemCountPerPage; + if ($this->itemCountPerPage < 1) { + $this->itemCountPerPage = $this->getTotalItemCount(); + } + $this->pageCount = $this->_calculatePageCount(); + $this->currentItems = null; + $this->currentItemCount = null; + + return $this; + } + + /** + * Returns the number of items in a collection. + * + * @param mixed $items Items + * @return int + */ + public function getItemCount($items) + { + $itemCount = 0; + + if (is_array($items) || $items instanceof Countable) { + $itemCount = count($items); + } elseif ($items instanceof Traversable) { // $items is something like LimitIterator + $itemCount = iterator_count($items); + } + + return $itemCount; + } + + /** + * Returns the items for a given page. + * + * @param int $pageNumber + * @return mixed + */ + public function getItemsByPage($pageNumber) + { + $pageNumber = $this->normalizePageNumber($pageNumber); + + $offset = ($pageNumber - 1) * $this->getItemCountPerPage(); + + $items = $this->adapter->getItems($offset, $this->getItemCountPerPage()); + + if (!$items instanceof Traversable) { + $items = new ArrayIterator($items); + } + + return $items; + } + + /** + * Returns a foreach-compatible iterator. + * + * @throws Exception\RuntimeException + * @return Traversable + */ + public function getIterator() + { + try { + return $this->getCurrentItems(); + } catch (\Exception $e) { + throw new Exception\RuntimeException('Error producing an iterator', null, $e); + } + } + + /** + * Returns the page range (see property declaration above). + * + * @return int + */ + public function getPageRange() + { + return $this->pageRange; + } + + /** + * Sets the page range (see property declaration above). + * + * @param int $pageRange + * @return Paginator $this + */ + public function setPageRange($pageRange) + { + $this->pageRange = (int) $pageRange; + + return $this; + } + + /** + * Returns the page collection. + * + * @param ScrollingStyleInterface $scrollingStyle Scrolling style + * @return \stdClass + */ + public function getPages($scrollingStyle = null) + { + if ($this->pages === null) { + $this->pages = $this->_createPages($scrollingStyle); + } + + return $this->pages; + } + + /** + * Returns a subset of pages within a given range. + * + * @param int $lowerBound Lower bound of the range + * @param int $upperBound Upper bound of the range + * @return array + */ + public function getPagesInRange($lowerBound, $upperBound) + { + $lowerBound = $this->normalizePageNumber($lowerBound); + $upperBound = $this->normalizePageNumber($upperBound); + + $pages = []; + + for ($pageNumber = $lowerBound; $pageNumber <= $upperBound; $pageNumber++) { + $pages[$pageNumber] = $pageNumber; + } + + return $pages; + } + + /** + * Brings the item number in range of the page. + * + * @param int $itemNumber + * @return int + */ + public function normalizeItemNumber($itemNumber) + { + $itemNumber = (int) $itemNumber; + + if ($itemNumber < 1) { + $itemNumber = 1; + } + + if ($itemNumber > $this->getItemCountPerPage()) { + $itemNumber = $this->getItemCountPerPage(); + } + + return $itemNumber; + } + + /** + * Brings the page number in range of the paginator. + * + * @param int $pageNumber + * @return int + */ + public function normalizePageNumber($pageNumber) + { + $pageNumber = (int) $pageNumber; + + if ($pageNumber < 1) { + $pageNumber = 1; + } + + $pageCount = $this->count(); + + if ($pageCount > 0 && $pageNumber > $pageCount) { + $pageNumber = $pageCount; + } + + return $pageNumber; + } + + /** + * Calculates the page count. + * + * @return int + */ + protected function _calculatePageCount() + { + return (int) ceil($this->getAdapter()->count() / $this->getItemCountPerPage()); + } + + /** + * Creates the page collection. + * + * @param string $scrollingStyle Scrolling style + * @return \stdClass + */ + protected function _createPages($scrollingStyle = null) + { + $pageCount = $this->count(); + $currentPageNumber = $this->getCurrentPageNumber(); + + $pages = new \stdClass(); + $pages->pageCount = $pageCount; + $pages->itemCountPerPage = $this->getItemCountPerPage(); + $pages->first = 1; + $pages->current = $currentPageNumber; + $pages->last = $pageCount; + + // Previous and next + if ($currentPageNumber - 1 > 0) { + $pages->previous = $currentPageNumber - 1; + } + + if ($currentPageNumber + 1 <= $pageCount) { + $pages->next = $currentPageNumber + 1; + } + + // Pages in range + $scrollingStyle = $this->_loadScrollingStyle($scrollingStyle); + $pages->pagesInRange = $scrollingStyle->getPages($this); + $pages->firstPageInRange = min($pages->pagesInRange); + $pages->lastPageInRange = max($pages->pagesInRange); + + // Item numbers + if ($this->getCurrentItems() !== null) { + $pages->currentItemCount = $this->getCurrentItemCount(); + $pages->totalItemCount = $this->getTotalItemCount(); + $pages->firstItemNumber = $pages->totalItemCount + ? (($currentPageNumber - 1) * $pages->itemCountPerPage) + 1 + : 0; + $pages->lastItemNumber = $pages->totalItemCount + ? $pages->firstItemNumber + $pages->currentItemCount - 1 + : 0; + } + + return $pages; + } + + /** + * Loads a scrolling style. + * + * @param string $scrollingStyle + * @return ScrollingStyleInterface + * @throws Exception\InvalidArgumentException + */ + protected function _loadScrollingStyle($scrollingStyle = null) + { + switch (strtolower(gettype($scrollingStyle))) { + case 'object': + if (!$scrollingStyle instanceof ScrollingStyleInterface) { + throw new Exception\InvalidArgumentException( + 'Scrolling style must implement Zend\Paginator\ScrollingStyle\ScrollingStyleInterface' + ); + } + + return $scrollingStyle; + + case 'null': + // Fall through to default case + + default: + throw new Exception\InvalidArgumentException( + 'Scrolling style must be a class ' . + 'name or object implementing Zend\Paginator\ScrollingStyle\ScrollingStyleInterface' + ); + } + } +}