diff --git a/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php b/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php index 95cdf53..3bc9dd2 100644 --- a/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php +++ b/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Database\Query; use Drupal\Core\Database\Connection; +use Drupal\Core\Pager\PagerFactoryInterface; /** * Query extender for pager queries. @@ -25,6 +26,10 @@ class PagerSelectExtender extends SelectExtender { * The highest element we've autogenerated so far. * * @var int + * + * @deprecated as of Drupal 8.1.x, will be removed in Drupal 9.0.0. + * Use \Drupal::service('pager.factory')->getFirstAvailableElementId() + * instead. */ static $maxElement = 0; @@ -36,11 +41,18 @@ class PagerSelectExtender extends SelectExtender { protected $limit = 10; /** - * The unique ID of this pager on this page. + * The pager object. * - * @var int + * @var \Drupal\Core\Pager\PagerInterface */ - protected $element = NULL; + protected $pager; + + /** + * The pager factory service. + * + * @var \Drupal\Core\Pager\PagerFactoryInterface + */ + protected $pagerFactory; /** * The count query that will be used for this pager. @@ -49,8 +61,9 @@ class PagerSelectExtender extends SelectExtender { */ protected $customCountQuery = FALSE; - public function __construct(SelectInterface $query, Connection $connection) { + public function __construct(SelectInterface $query, Connection $connection, PagerFactoryInterface $pager_factory = NULL) { parent::__construct($query, $connection); + $this->pagerFactory = $pager_factory ?: \Drupal::service('pager.factory'); // Add pager tag. Do this here to ensure that it is always added before // preExecute() is called. @@ -73,31 +86,41 @@ public function execute() { } // A NULL limit is the "kill switch" for pager queries. - if (empty($this->limit)) { + if ($this->pager && !$this->pager->getLimit()) { return; } + + // Ensure that there is a pager associated with this query. $this->ensureElement(); $total_items = $this->getCountQuery()->execute()->fetchField(); - $current_page = pager_default_initialize($total_items, $this->limit, $this->element); - $this->range($current_page * $this->limit, $this->limit); + $current_page = $this->pager->init($total_items, $this->pager->getLimit())->getCurrentPage(); + $this->range($current_page * $this->pager->getLimit(), $this->pager->getLimit()); // Now that we've added our pager-based range instructions, run the query normally. return $this->query->execute(); } /** - * Ensure that there is an element associated with this query. - * If an element was not specified previously, then the value of the - * $maxElement counter is taken, after which the counter is incremented. + * Ensure that there is a pager associated with this query. + * If the pager was not specified previously, then a pager for the first + * available element is created, with a limit set to 10. * - * After running this method, access $this->element to get the element for this - * query. + * After running this method, you can access ::getElement() to get the + * element for this query. + * + * @return $this + * + * @todo in D9, consider removing this method, and pass an element or null to + * the constructor. This will allow removing the ::$limit property on this + * class and just setLimit() to the PagerInterface object created on + * costruction. */ protected function ensureElement() { - if (!isset($this->element)) { - $this->element = self::$maxElement++; + if (!$this->pager) { + $this->pager = $this->pagerFactory->getFirstAvailable()->setLimit($this->limit); } + return $this; } /** @@ -142,7 +165,13 @@ public function getCountQuery() { * value (FALSE, 0, NULL), the pager is disabled. */ public function limit($limit = 10) { - $this->limit = $limit; + if ($this->pager) { + $this->pager->setLimit($limit); + } + else { + // Preserve the limit for when the pager object is created. + $this->limit = $limit; + } return $this; } @@ -155,21 +184,36 @@ public function limit($limit = 10) { * whatever reason you want to explicitly define an element for a given query, * you may do so here. * - * Setting the element here also increments the static $maxElement counter, - * which is used for determining the $element when there's none specified. - * * Note that no collision detection is done when setting an element ID * explicitly, so it is possible for two pagers to end up using the same ID * if both are set explicitly. * * @param $element * Element ID that is used to differentiate different pager queries. + * + * @todo in D9, consider removing this method, and pass an element or null to + * the constructor. This will allow removing the ::$limit property on this + * class and just setLimit() to the PagerInterface object created on + * costruction. */ public function element($element) { - $this->element = $element; - if ($element >= self::$maxElement) { - self::$maxElement = $element + 1; - } + $this->pager = $this->pagerFactory->get($element)->setLimit($this->limit); return $this; } + + /** + * Gets the element ID for this pager query. + * + * The element is used to differentiate different pager queries on the same + * page so that they may be operated independently. + * + * @return int + * Element ID that is used to differentiate between different pager + * queries. + */ + public function getElement() { + $this->ensureElement(); + return $this->pager->getElement(); + } + } diff --git a/core/lib/Drupal/Core/Pager/PagerFactory.php b/core/lib/Drupal/Core/Pager/PagerFactory.php index aa816ff..0b7d90b 100644 --- a/core/lib/Drupal/Core/Pager/PagerFactory.php +++ b/core/lib/Drupal/Core/Pager/PagerFactory.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Pager; use Drupal\Component\Utility\UrlHelper; +use Drupal\Core\Database\Query\PagerSelectExtender; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RequestStack; @@ -65,6 +66,10 @@ public function get($element) { if (!isset($this->pagers[$element])) { $class = $this->pagerClass; $this->pagers[$element] = $class::create($this->container, $element); + // @todo BC layer: remove in D9. + if (PagerSelectExtender::$maxElement < $element) { + PagerSelectExtender::$maxElement = $element; + } } return $this->pagers[$element]; } @@ -72,6 +77,26 @@ public function get($element) { /** * {@inheritdoc} */ + public function getFirstAvailableElementId() { + // @todo: BC layer. When removing the globals, return + // element from max(array_keys($this->pagers)) directly. + global $pager_total; + $first_from_globals = !empty($pager_total) ? max(array_keys($pager_total)) + 1 : 0; + $first = $first_from_globals < PagerSelectExtender::$maxElement ? PagerSelectExtender::$maxElement : $first_from_globals; + PagerSelectExtender::$maxElement = $first; + return $first; + } + + /** + * {@inheritdoc} + */ + public function getFirstAvailable() { + return $this->get($this->getFirstAvailableElementId()); + } + + /** + * {@inheritdoc} + */ public function all() { // @todo: BC layer. When removing the globals, remove the following and // return $this->pagers directly. diff --git a/core/lib/Drupal/Core/Pager/PagerFactoryInterface.php b/core/lib/Drupal/Core/Pager/PagerFactoryInterface.php index 80e4955..147e015 100644 --- a/core/lib/Drupal/Core/Pager/PagerFactoryInterface.php +++ b/core/lib/Drupal/Core/Pager/PagerFactoryInterface.php @@ -26,6 +26,22 @@ public function get($element); /** + * Returns a pager for the first pager element ID free for use. + * + * @return \Drupal\Core\Pager\Pager + * The pager object. + */ + public function getFirstAvailable(); + + /** + * Returns the first available pager element ID to use. + * + * @return int + * A pager element ID. + */ + public function getFirstAvailableElementId(); + + /** * Returns the current array of pager objects. * * @return \Drupal\Core\Pager\Pager[] diff --git a/core/modules/system/src/Tests/Database/SelectPagerDefaultTest.php b/core/modules/system/src/Tests/Database/SelectPagerDefaultTest.php index 1ec9231..858e43b 100644 --- a/core/modules/system/src/Tests/Database/SelectPagerDefaultTest.php +++ b/core/modules/system/src/Tests/Database/SelectPagerDefaultTest.php @@ -137,36 +137,42 @@ function testElementNumbers() { )); \Drupal::getContainer()->get('request_stack')->push($request); - $name = db_select('test', 't') + $query = db_select('test', 't') ->extend('Drupal\Core\Database\Query\PagerSelectExtender') ->element(2) ->fields('t', array('name')) ->orderBy('age') - ->limit(1) - ->execute() - ->fetchField(); + ->limit(1); + $name = $query->execute()->fetchField(); $this->assertEqual($name, 'Paul', 'Pager query #1 with a specified element ID returned the correct results.'); + // Check that the pager element ID has been set to 2. + $this->assertEqual(2, $query->getElement()); - // Setting an element smaller than the previous one - // should not overwrite the pager $maxElement with a smaller value. - $name = db_select('test', 't') + // Setting an element smaller than the previous one should not overwrite + // the maximum pager element ID with a smaller value. + $query = db_select('test', 't') ->extend('Drupal\Core\Database\Query\PagerSelectExtender') ->element(1) ->fields('t', array('name')) ->orderBy('age') - ->limit(1) - ->execute() - ->fetchField(); + ->limit(1); + $name = $query->execute()->fetchField(); $this->assertEqual($name, 'George', 'Pager query #2 with a specified element ID returned the correct results.'); + // Check that the first available element ID is still 3. + $this->assertEqual(3, \Drupal::service('pager.factory')->getFirstAvailableElementId()); + // Check that the pager element ID has been set to 1. + $this->assertEqual(1, $query->getElement()); - $name = db_select('test', 't') + $query = db_select('test', 't') ->extend('Drupal\Core\Database\Query\PagerSelectExtender') ->fields('t', array('name')) ->orderBy('age') - ->limit(1) - ->execute() - ->fetchField(); + ->limit(1); + $name = $query->execute()->fetchField(); $this->assertEqual($name, 'John', 'Pager query #3 with a generated element ID returned the correct results.'); + // Check that the pager element ID has been set to 3. + $this->assertEqual(3, $query->getElement()); } + }