From 38741fcbd8e7367c6d6b8842fc093fdd5b4e3bf6 Mon Sep 17 00:00:00 2001
From: Francesco Placella <plach@183211.no-reply.drupal.org>
Date: Sat, 12 Oct 2019 00:41:42 +0200
Subject: [PATCH] Issue #2044435 by mondrake, kim.pepper, Mile23, andypost,
 martin107, daffie, jibran, beejeebus, alexpott, larowlan, dawehner, catch,
 pwolanin: Convert pager.inc to a service

---
 core/core.services.yml                        |   8 +-
 core/globals.api.php                          |  38 ++++-
 core/includes/pager.inc                       | 122 +++++++-------
 .../Component/Utility/DeprecatedArray.php     |  69 ++++++++
 .../Core/Cache/Context/PagersCacheContext.php |  38 ++++-
 .../Database/Query/PagerSelectExtender.php    |   6 +-
 .../Drupal/Core/Entity/Query/QueryBase.php    |   4 +-
 core/lib/Drupal/Core/Pager/Pager.php          | 121 ++++++++++++++
 core/lib/Drupal/Core/Pager/PagerManager.php   | 132 +++++++++++++++
 .../Core/Pager/PagerManagerInterface.php      | 155 ++++++++++++++++++
 .../lib/Drupal/Core/Pager/PagerParameters.php |  72 ++++++++
 .../Core/Pager/PagerParametersInterface.php   |  59 +++++++
 core/lib/Drupal/Core/Render/Element/Pager.php |  19 ++-
 .../modules/pager_test/pager_test.module      |   7 +-
 .../src/Controller/PagerTestController.php    |  28 +++-
 .../src/Kernel/Pager/PagerDeprecationTest.php |  43 +++++
 .../taxonomy/src/Form/OverviewTerms.php       |  28 +++-
 .../views/src/Plugin/views/pager/SqlBase.php  | 109 +++++++-----
 core/modules/views/views.theme.inc            |  19 ++-
 .../Core/Pager/PagerManagerTest.php           |  95 +++++++++++
 .../Core/Pager/RequestPagerTest.php           |  49 ++++++
 21 files changed, 1082 insertions(+), 139 deletions(-)
 create mode 100644 core/lib/Drupal/Component/Utility/DeprecatedArray.php
 create mode 100644 core/lib/Drupal/Core/Pager/Pager.php
 create mode 100644 core/lib/Drupal/Core/Pager/PagerManager.php
 create mode 100644 core/lib/Drupal/Core/Pager/PagerManagerInterface.php
 create mode 100644 core/lib/Drupal/Core/Pager/PagerParameters.php
 create mode 100644 core/lib/Drupal/Core/Pager/PagerParametersInterface.php
 create mode 100644 core/modules/system/tests/src/Kernel/Pager/PagerDeprecationTest.php
 create mode 100644 core/tests/Drupal/KernelTests/Core/Pager/PagerManagerTest.php
 create mode 100644 core/tests/Drupal/KernelTests/Core/Pager/RequestPagerTest.php

diff --git a/core/core.services.yml b/core/core.services.yml
index 091289c2d265..b093d4bfac95 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -106,7 +106,7 @@ services:
       - { name: cache.context }
   cache_context.url.query_args.pagers:
     class: Drupal\Core\Cache\Context\PagersCacheContext
-    arguments: ['@request_stack']
+    arguments: ['@pager.parameters']
     tags:
       - { name: cache.context }
 
@@ -1742,3 +1742,9 @@ services:
     arguments: ['@keyvalue.expirable', '@lock', '@request_stack', '%tempstore.expire%']
     tags:
       - { name: backend_overridable }
+  pager.manager:
+    class: Drupal\Core\Pager\PagerManager
+    arguments: ['@pager.parameters']
+  pager.parameters:
+    class: Drupal\Core\Pager\PagerParameters
+    arguments: ['@request_stack']
diff --git a/core/globals.api.php b/core/globals.api.php
index 0f60ad558532..375c4fd8be2a 100644
--- a/core/globals.api.php
+++ b/core/globals.api.php
@@ -5,6 +5,8 @@
  * These are the global variables that Drupal uses.
  */
 
+use Drupal\Component\Utility\DeprecatedArray;
+
 /**
  * The insecure base URL of the Drupal installation.
  *
@@ -81,33 +83,53 @@
  *
  * The array index is the pager element index (0 by default).
  *
- * @see pager_default_initialize()
+ * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Do not
+ *   directly set or get values from this array. Use the pager.manager service
+ *   instead.
+ *
+ * @see https://www.drupal.org/node/2779457
+ * @see \Drupal\Core\Pager\PagerManagerInterface
  */
-global $pager_limits;
+$GLOBALS['pager_limits'] = new DeprecatedArray([], 'Global variable $pager_limits is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457');
 
 /**
  * Array of current page numbers for each pager.
  *
  * The array index is the pager element index (0 by default).
  *
- * @see pager_default_initialize()
+ * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Do not
+ *   directly set or get values from this array. Use the pager.manager service
+ *   instead.
+ *
+ * @see https://www.drupal.org/node/2779457
+ * @see \Drupal\Core\Pager\PagerManagerInterface
  */
-global $pager_page_array;
+$GLOBALS['pager_page_array'] = new DeprecatedArray([], 'Global variable $pager_page_array is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457');
 
 /**
  * Array of the total number of pages for each pager.
  *
  * The array index is the pager element index (0 by default).
  *
- * @see pager_default_initialize()
+ * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Do not
+ *   directly set or get values from this array. Use the pager.manager service
+ *   instead.
+ *
+ * @see https://www.drupal.org/node/2779457
+ * @see \Drupal\Core\Pager\PagerManagerInterface
  */
-global $pager_total;
+$GLOBALS['pager_total'] = new DeprecatedArray([], 'Global variable $pager_total is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457');
 
 /**
  * Array of the total number of items for each pager.
  *
  * The array index is the pager element index (0 by default).
  *
- * @see pager_default_initialize()
+ * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Do not
+ *   directly set or get values from this array. Use the pager.manager service
+ *   instead.
+ *
+ * @see https://www.drupal.org/node/2779457
+ * @see \Drupal\Core\Pager\PagerManagerInterface
  */
-global $pager_total_items;
+$GLOBALS['pager_total_items'] = new DeprecatedArray([], 'Global variable $pager_total_items is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457');
diff --git a/core/includes/pager.inc b/core/includes/pager.inc
index 81daa34c6e53..c18507331aa2 100644
--- a/core/includes/pager.inc
+++ b/core/includes/pager.inc
@@ -7,7 +7,6 @@
 
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Url;
-use Drupal\Component\Utility\UrlHelper;
 use Drupal\Component\Utility\Html;
 
 /**
@@ -26,15 +25,17 @@
  *   even though the default pager implementation adjusts for this and still
  *   displays the third page of search results at that URL.
  *
- * @see pager_default_initialize()
+ * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
+ *   \Drupal\Core\Pager\RequestPagerInterface->findPage() instead.
+ *
+ * @see https://www.drupal.org/node/2779457
+ * @see \Drupal\Core\Pager\PagerParametersInterface::findPage()
  */
 function pager_find_page($element = 0) {
-  $page = \Drupal::request()->query->get('page', '');
-  $page_array = explode(',', $page);
-  if (!isset($page_array[$element])) {
-    $page_array[$element] = 0;
-  }
-  return (int) $page_array[$element];
+  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\RequestPagerInterface->findPage() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
+  /* @var $pager_parameters \Drupal\Core\Pager\PagerParametersInterface */
+  $pager_parameters = \Drupal::service('pager.parameters');
+  return $pager_parameters->findPage($element);
 }
 
 /**
@@ -126,18 +127,19 @@ function pager_find_page($element = 0) {
  *   that does not correspond to the actual range of the result set was
  *   requested, this function will return the closest page actually within the
  *   result set.
+ *
+ * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
+ *   \Drupal\Core\Pager\PagerManagerInterface->defaultInitialize() instead.
+ *
+ * @see https://www.drupal.org/node/2779457
+ * @see \Drupal\Core\Pager\PagerManagerInterface::createPager()
  */
 function pager_default_initialize($total, $limit, $element = 0) {
-  global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
-
-  $page = pager_find_page($element);
-
-  // We calculate the total of pages as ceil(items / limit).
-  $pager_total_items[$element] = $total;
-  $pager_total[$element] = ceil($pager_total_items[$element] / $limit);
-  $pager_page_array[$element] = max(0, min($page, ((int) $pager_total[$element]) - 1));
-  $pager_limits[$element] = $limit;
-  return $pager_page_array[$element];
+  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface->createPager() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
+  /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */
+  $pager_manager = \Drupal::service('pager.manager');
+  $pager = $pager_manager->createPager($total, $limit, $element);
+  return $pager->getCurrentPage();
 }
 
 /**
@@ -146,13 +148,18 @@ function pager_default_initialize($total, $limit, $element = 0) {
  * @return array
  *   A URL query parameter array that consists of all components of the current
  *   page request except for those pertaining to paging.
+ *
+ * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
+ *   \Drupal\Core\Pager\RequestPagerInterface->getQueryParameters() instead.
+ *
+ * @see https://www.drupal.org/node/2779457
+ * @see \Drupal\Core\Pager\PagerParametersInterface::getQueryParameters()
  */
 function pager_get_query_parameters() {
-  $query = &drupal_static(__FUNCTION__);
-  if (!isset($query)) {
-    $query = UrlHelper::filterQueryParameters(\Drupal::request()->query->all(), ['page']);
-  }
-  return $query;
+  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\RequestPagerInterface->getQueryParameters() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
+  /* @var $pager_params \Drupal\Core\Pager\PagerParametersInterface */
+  $pager_params = \Drupal::service('pager.parameters');
+  return $pager_params->getQueryParameters();
 }
 
 /**
@@ -181,10 +188,21 @@ function template_preprocess_pager(&$variables) {
   $quantity = $variables['pager']['#quantity'];
   $route_name = $variables['pager']['#route_name'];
   $route_parameters = isset($variables['pager']['#route_parameters']) ? $variables['pager']['#route_parameters'] : [];
-  global $pager_page_array, $pager_total;
+
+  /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */
+  $pager_manager = \Drupal::service('pager.manager');
+
+  $pager = $pager_manager->getPager($element);
+
+  // Nothing to do if there is no pager.
+  if (!isset($pager)) {
+    return;
+  }
+
+  $pager_max = $pager->getTotalPages();
 
   // Nothing to do if there is only one page.
-  if ($pager_total[$element] <= 1) {
+  if ($pager_max <= 1) {
     return;
   }
 
@@ -193,14 +211,13 @@ function template_preprocess_pager(&$variables) {
   // Calculate various markers within this pager piece:
   // Middle is used to "center" pages around the current page.
   $pager_middle = ceil($quantity / 2);
-  // current is the page we are currently paged to.
-  $pager_current = $pager_page_array[$element] + 1;
-  // first is the first page listed by this pager piece (re quantity).
+  $current_page = $pager->getCurrentPage();
+  // The current pager is the page we are currently paged to.
+  $pager_current = $current_page + 1;
+  // The first pager is the first page listed by this pager piece (re quantity).
   $pager_first = $pager_current - $pager_middle + 1;
-  // last is the last page listed by this pager piece (re quantity).
+  // The last is the last page listed by this pager piece (re quantity).
   $pager_last = $pager_current + $quantity - $pager_middle;
-  // max is the maximum page number.
-  $pager_max = $pager_total[$element];
   // End of marker calculations.
 
   // Prepare for generation loop.
@@ -218,11 +235,11 @@ function template_preprocess_pager(&$variables) {
   // End of generation loop preparation.
 
   // Create the "first" and "previous" links if we are not on the first page.
-  if ($pager_page_array[$element] > 0) {
+  if ($current_page > 0) {
     $items['first'] = [];
     $items['first']['attributes'] = new Attribute();
     $options = [
-      'query' => pager_query_add_page($parameters, $element, 0),
+      'query' => $pager_manager->getUpdatedParameters($parameters, $element, 0),
     ];
     $items['first']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString();
     if (isset($tags[0])) {
@@ -232,7 +249,7 @@ function template_preprocess_pager(&$variables) {
     $items['previous'] = [];
     $items['previous']['attributes'] = new Attribute();
     $options = [
-      'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] - 1),
+      'query' => $pager_manager->getUpdatedParameters($parameters, $element, $current_page - 1),
     ];
     $items['previous']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString();
     if (isset($tags[1])) {
@@ -248,7 +265,7 @@ function template_preprocess_pager(&$variables) {
     // Now generate the actual pager piece.
     for (; $i <= $pager_last && $i <= $pager_max; $i++) {
       $options = [
-        'query' => pager_query_add_page($parameters, $element, $i - 1),
+        'query' => $pager_manager->getUpdatedParameters($parameters, $element, $i - 1),
       ];
       $items['pages'][$i]['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString();
       $items['pages'][$i]['attributes'] = new Attribute();
@@ -263,11 +280,11 @@ function template_preprocess_pager(&$variables) {
   }
 
   // Create the "next" and "last" links if we are not on the last page.
-  if ($pager_page_array[$element] < ($pager_max - 1)) {
+  if ($current_page < ($pager_max - 1)) {
     $items['next'] = [];
     $items['next']['attributes'] = new Attribute();
     $options = [
-      'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] + 1),
+      'query' => $pager_manager->getUpdatedParameters($parameters, $element, $current_page + 1),
     ];
     $items['next']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString();
     if (isset($tags[3])) {
@@ -277,7 +294,7 @@ function template_preprocess_pager(&$variables) {
     $items['last'] = [];
     $items['last']['attributes'] = new Attribute();
     $options = [
-      'query' => pager_query_add_page($parameters, $element, $pager_max - 1),
+      'query' => $pager_manager->getUpdatedParameters($parameters, $element, $pager_max - 1),
     ];
     $items['last']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString();
     if (isset($tags[4])) {
@@ -316,25 +333,16 @@ function template_preprocess_pager(&$variables) {
  *
  * @return array
  *   The altered $query parameter array.
+ *
+ * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
+ *   \Drupal\Core\Pager\PagerManagerInterface->queryAddPage() instead.
+ *
+ * @see https://www.drupal.org/node/2779457
+ * @see \Drupal\Core\Pager\PagerManagerInterface::getUpdatedParameters()
  */
 function pager_query_add_page(array $query, $element, $index) {
-  global $pager_page_array;
-
-  // Build the 'page' query parameter. This is built based on the current
-  // page of each pager element (or NULL if the pager is not set), with the
-  // exception of the requested page index for the current element.
-  $max_element = max(array_keys($pager_page_array));
-  $element_pages = [];
-  for ($i = 0; $i <= $max_element; $i++) {
-    $element_pages[] = ($i == $element) ? $index : (isset($pager_page_array[$i]) ? $pager_page_array[$i] : NULL);
-  }
-  $query['page'] = implode(',', $element_pages);
-
-  // Merge the query parameters passed to this function with the parameters
-  // from the current request. In case of collision, the parameters passed into
-  // this function take precedence.
-  if ($current_request_query = pager_get_query_parameters()) {
-    $query = array_merge($current_request_query, $query);
-  }
-  return $query;
+  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface->queryAddPage() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
+  /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */
+  $pager_manager = \Drupal::service('pager.manager');
+  return $pager_manager->getUpdatedParameters($query, $element, $index);
 }
diff --git a/core/lib/Drupal/Component/Utility/DeprecatedArray.php b/core/lib/Drupal/Component/Utility/DeprecatedArray.php
new file mode 100644
index 000000000000..88d028723267
--- /dev/null
+++ b/core/lib/Drupal/Component/Utility/DeprecatedArray.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\Component\Utility;
+
+/**
+ * An array that triggers a deprecation warning when accessed.
+ */
+class DeprecatedArray implements \ArrayAccess {
+
+  /**
+   * The array values.
+   *
+   * @var array
+   */
+  protected $values = [];
+
+  /**
+   * The deprecation message.
+   *
+   * @var string
+   */
+  protected $message;
+
+  /**
+   * DeprecatedArray constructor.
+   *
+   * @param array $values
+   *   The array values.
+   * @param $message
+   *   The deprecation message.
+   */
+  public function __construct(array $values, $message) {
+    $this->values = $values;
+    $this->message = $message;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetExists($offset) {
+    @trigger_error($this->message, E_USER_DEPRECATED);
+    return isset($this->values[$offset]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetGet($offset) {
+    @trigger_error($this->message, E_USER_DEPRECATED);
+    return $this->values[$offset] ?? NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetSet($offset, $value) {
+    @trigger_error($this->message, E_USER_DEPRECATED);
+    return $this->values[$offset] = $value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetUnset($offset) {
+    @trigger_error($this->message, E_USER_DEPRECATED);
+    unset($this->values[$offset]);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php b/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php
index cb34256dc7a1..ed462f24f15d 100644
--- a/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php
@@ -3,6 +3,8 @@
 namespace Drupal\Core\Cache\Context;
 
 use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
+use Drupal\Core\Pager\PagerParametersInterface;
 
 /**
  * Defines a cache context for "per page in a pager" caching.
@@ -11,7 +13,35 @@
  * Calculated cache context ID: 'url.query_args.pagers:%pager_id', e.g.
  * 'url.query_args.pagers:1' (to vary by the pager with ID 1).
  */
-class PagersCacheContext extends RequestStackCacheContextBase implements CalculatedCacheContextInterface {
+class PagersCacheContext implements CalculatedCacheContextInterface {
+
+  use DeprecatedServicePropertyTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $deprecatedProperties = ['requestStack' => 'request_stack'];
+
+  /**
+   * The pager parameters.
+   *
+   * @var \Drupal\Core\Pager\PagerParametersInterface
+   */
+  protected $pagerParams;
+
+  /**
+   * Constructs a new PagersCacheContext object.
+   *
+   * @param \Drupal\Core\Pager\PagerParametersInterface $pager_params
+   *   The pager parameters.
+   */
+  public function __construct($pager_params) {
+    if (!($pager_params instanceof PagerParametersInterface)) {
+      @trigger_error('Calling ' . __METHOD__ . ' with a $pager_params argument that does not implement \Drupal\Core\Pager\PagerParametersInterface is deprecated in drupal:8.8.0 and is required in drupal:9.0.0. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
+      $pager_params = \Drupal::service('pager.parameters');
+    }
+    $this->pagerParams = $pager_params;
+  }
 
   /**
    * {@inheritdoc}
@@ -23,16 +53,16 @@ public static function getLabel() {
   /**
    * {@inheritdoc}
    *
-   * @see pager_find_page()
+   * @see \Drupal\Core\Pager\PagerParametersInterface::findPage()
    */
   public function getContext($pager_id = NULL) {
     // The value of the 'page' query argument contains the information that
     // controls *all* pagers.
     if ($pager_id === NULL) {
-      return $this->requestStack->getCurrentRequest()->query->get('page', '');
+      return $this->pagerParams->getPagerParameter();
     }
 
-    return $pager_id . '.' . pager_find_page($pager_id);
+    return $pager_id . '.' . $this->pagerParams->findPage($pager_id);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php b/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php
index 5207f58cb92e..f32b1c733cf8 100644
--- a/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php
+++ b/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php
@@ -73,8 +73,10 @@ public function execute() {
     $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);
+    /** @var \Drupal\Core\Pager\PagerManagerInterface $pager_manager */
+    $pager_manager = \Drupal::service('pager.manager');
+    $pager = $pager_manager->createPager($total_items, $this->limit, $this->element);
+    $this->range($pager->getCurrentPage() * $this->limit, $this->limit);
 
     // Now that we've added our pager-based range instructions, run the query normally.
     return $this->query->execute();
diff --git a/core/lib/Drupal/Core/Entity/Query/QueryBase.php b/core/lib/Drupal/Core/Entity/Query/QueryBase.php
index 57dd9025a33d..e7fbb82fad0a 100644
--- a/core/lib/Drupal/Core/Entity/Query/QueryBase.php
+++ b/core/lib/Drupal/Core/Entity/Query/QueryBase.php
@@ -310,11 +310,11 @@ public function pager($limit = 10, $element = NULL) {
    */
   protected function initializePager() {
     if ($this->pager && !empty($this->pager['limit']) && !$this->count) {
-      $page = pager_find_page($this->pager['element']);
+      $page = \Drupal::service('pager.parameters')->findPage($this->pager['element']);
       $count_query = clone $this;
       $this->pager['total'] = $count_query->count()->execute();
       $this->pager['start'] = $page * $this->pager['limit'];
-      pager_default_initialize($this->pager['total'], $this->pager['limit'], $this->pager['element']);
+      \Drupal::service('pager.manager')->createPager($this->pager['total'], $this->pager['limit'], $this->pager['element']);
       $this->range($this->pager['start'], $this->pager['limit']);
     }
   }
diff --git a/core/lib/Drupal/Core/Pager/Pager.php b/core/lib/Drupal/Core/Pager/Pager.php
new file mode 100644
index 000000000000..dedeed2657f9
--- /dev/null
+++ b/core/lib/Drupal/Core/Pager/Pager.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace Drupal\Core\Pager;
+
+/**
+ * A value object that represents a pager.
+ */
+class Pager {
+
+  /**
+   * The total number of items .
+   *
+   * @var int
+   */
+  protected $totalItems;
+
+  /**
+   * The total number of pages.
+   *
+   * @var int
+   */
+  protected $totalPages;
+
+  /**
+   * The current page of the pager.
+   *
+   * @var int
+   */
+  protected $currentPage;
+
+  /**
+   * The maximum number of items per page.
+   *
+   * @var int
+   */
+  protected $limit;
+
+  /**
+   * Pager constructor.
+   *
+   * @param int $totalItems
+   *   The total number of items.
+   * @param int $limit
+   *   The maximum number of items per page.
+   * @param int $currentPage
+   *   The current page.
+   */
+  public function __construct($totalItems, $limit, $currentPage = 0) {
+    $this->totalItems = $totalItems;
+    $this->limit = $limit;
+    $this->setTotalPages($totalItems, $limit);
+    $this->setCurrentPage($currentPage);
+  }
+
+  /**
+   * Sets the current page to a valid value within range.
+   *
+   * If a page that does not correspond to the actual range of the result set
+   * was provided, this function will set the closest page actually within
+   * the result set.
+   *
+   * @param int $currentPage
+   *   (optional) The current page.
+   */
+  protected function setCurrentPage($currentPage = 0) {
+    $this->currentPage = max(0, min($currentPage, $this->getTotalPages() - 1));
+  }
+
+  /**
+   * Sets the total number of pages.
+   *
+   * @param int $totalItems
+   *   The total number of items.
+   * @param int $limit
+   *   The maximum number of items per page.
+   */
+  protected function setTotalPages($totalItems, $limit) {
+    $this->totalPages = (int) ceil($totalItems / $limit);
+  }
+
+  /**
+   * Gets the total number of items.
+   *
+   * @return int
+   *   The total number of items.
+   */
+  public function getTotalItems() {
+    return $this->totalItems;
+  }
+
+  /**
+   * Gets the total number of pages.
+   *
+   * @return int
+   *   The total number of pages.
+   */
+  public function getTotalPages() {
+    return $this->totalPages;
+  }
+
+  /**
+   * Gets the current page.
+   *
+   * @return int
+   *   The current page.
+   */
+  public function getCurrentPage() {
+    return $this->currentPage;
+  }
+
+  /**
+   * Gets the maximum number of items per page.
+   *
+   * @return int
+   *   The the maximum number of items per page.
+   */
+  public function getLimit() {
+    return $this->limit;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Pager/PagerManager.php b/core/lib/Drupal/Core/Pager/PagerManager.php
new file mode 100644
index 000000000000..b97b813d7d81
--- /dev/null
+++ b/core/lib/Drupal/Core/Pager/PagerManager.php
@@ -0,0 +1,132 @@
+<?php
+
+namespace Drupal\Core\Pager;
+
+use Drupal\Component\Utility\DeprecatedArray;
+use Drupal\Core\DependencyInjection\DependencySerializationTrait;
+
+/**
+ * Provides a manager for pagers.
+ *
+ * Pagers are cached, and can be retrieved when rendering.
+ */
+class PagerManager implements PagerManagerInterface {
+
+  use DependencySerializationTrait;
+
+  /**
+   * The pager parameters.
+   *
+   * @var \Drupal\Core\Pager\PagerParametersInterface
+   */
+  protected $pagerParams;
+
+  /**
+   * An associative array of pagers.
+   *
+   * Implemented as an array consisting of:
+   *   - key: the element id integer.
+   *   - value: a \Drupal\Core\Pager\Pager.
+   *
+   * @var array
+   */
+  protected $pagers;
+
+  /**
+   * Construct a PagerManager object.
+   *
+   * @param \Drupal\Core\Pager\PagerParametersInterface $pager_params
+   *   The pager parameters.
+   */
+  public function __construct(PagerParametersInterface $pager_params) {
+    $this->pagerParams = $pager_params;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createPager($total, $limit, $element = 0) {
+    $currentPage = $this->pagerParams->findPage($element);
+    $pager = new Pager($total, $limit, $currentPage);
+    $this->setPager($pager, $element);
+    return $pager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPager($element = 0) {
+    return isset($this->pagers[$element]) ? $this->pagers[$element] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getUpdatedParameters(array $query, $element, $index) {
+    // Build the 'page' query parameter. This is built based on the current
+    // page of each pager element (or NULL if the pager is not set), with the
+    // exception of the requested page index for the current element.
+    $element_pages = [];
+    $max = $this->getMaxPagerElementId();
+    for ($i = 0; $i <= $max; $i++) {
+      $currentPage = ($pager = $this->getPager($i)) ? $pager->getCurrentPage() : NULL;
+      $element_pages[] = ($i == $element) ? $index : $currentPage;
+    }
+    $query['page'] = implode(',', $element_pages);
+
+    // Merge the query parameters passed to this function with the parameters
+    // from the current request. In case of collision, the parameters passed
+    // into this function take precedence.
+    if ($current_query = $this->pagerParams->getQueryParameters()) {
+      $query = array_merge($current_query, $query);
+    }
+    return $query;
+  }
+
+  /**
+   * Gets the extent of the pager page element IDs.
+   *
+   * @return int
+   *   The maximum element ID available, -1 if there are no elements.
+   */
+  protected function getMaxPagerElementId() {
+    return empty($this->pagers) ? -1 : max(array_keys($this->pagers));
+  }
+
+  /**
+   * Saves a pager to the static cache.
+   *
+   * @param \Drupal\Core\Pager\Pager $pager
+   *   The pager.
+   * @param int $element
+   *   The pager index.
+   */
+  protected function setPager(Pager $pager, $element = 0) {
+    $this->pagers[$element] = $pager;
+    $this->updateGlobals();
+  }
+
+  /**
+   * Updates global variables with a pager data for backwards compatibility.
+   */
+  protected function updateGlobals() {
+    $pager_total_items = [];
+    $pager_total = [];
+    $pager_page_array = [];
+    $pager_limits = [];
+
+    /** @var $pager \Drupal\Core\Pager\Pager */
+    foreach ($this->pagers as $pager_id => $pager) {
+      $pager_total_items[$pager_id] = $pager->getTotalItems();
+      $pager_total[$pager_id] = $pager->getTotalPages();
+      $pager_page_array[$pager_id] = $pager->getCurrentPage();
+      $pager_limits[$pager_id] = $pager->getLimit();
+    }
+
+    $GLOBALS['pager_total_items'] = new DeprecatedArray($pager_total_items, 'Global variable $pager_total_items is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457');
+    $GLOBALS['pager_total'] = new DeprecatedArray($pager_total, 'Global variable $pager_total is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457');
+    $GLOBALS['pager_page_array'] = new DeprecatedArray($pager_page_array, 'Global variable $pager_page_array is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457');
+    $GLOBALS['pager_limits'] = new DeprecatedArray($pager_limits, 'Global variable $pager_limits is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457');
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Pager/PagerManagerInterface.php b/core/lib/Drupal/Core/Pager/PagerManagerInterface.php
new file mode 100644
index 000000000000..77ead948cc5b
--- /dev/null
+++ b/core/lib/Drupal/Core/Pager/PagerManagerInterface.php
@@ -0,0 +1,155 @@
+<?php
+
+namespace Drupal\Core\Pager;
+
+/**
+ * This is a service for pager information.
+ *
+ * The pager.manager service manages the pager information which will eventually
+ * be rendered into pager elements in the response. To gather information
+ * related to pager information in the request, use the pager.parameters
+ * service.
+ *
+ * Since there can be multiple pagers per requested page, each one is
+ * represented by an 'element' ID. This is an integer. It represents the index
+ * of the pager element within the 'page' query. The value of the element is an
+ * integer telling us the current page number for that pager.
+ *
+ * This class generally replaces the functions in core/includes/pager.inc. Those
+ * functions use globals to store data which they all use. Since we require
+ * backwards compatibility with this behavior, this class presents a public API
+ * for using pager information, which is implemented using the same globals as a
+ * 'backend.'
+ *
+ * @see \Drupal\Core\Pager\PagerParametersInterface
+ */
+interface PagerManagerInterface {
+
+  /**
+   * Initializes a pager.
+   *
+   * This function sets up the necessary variables so that the render system
+   * will correctly process #type 'pager' render arrays to output pagers that
+   * correspond to the items being displayed.
+   *
+   * If the items being displayed result from a database query performed using
+   * Drupal's database API, and if you have control over the construction of the
+   * database query, you do not need to call this function directly; instead,
+   * you can extend the query object with the 'PagerSelectExtender' extender
+   * before executing it. For example:
+   * @code
+   *   $query = db_select('some_table')
+   *     ->extend('Drupal\Core\Database\Query\PagerSelectExtender');
+   * @endcode
+   *
+   * However, if you are using a different method for generating the items to be
+   * paged through, then you should call this service in preparation.
+   *
+   * The following example shows how this service can be used in a controller
+   * that invokes an external datastore with an SQL-like syntax:
+   * @code
+   *   // First find the total number of items and initialize the pager.
+   *   $where = "status = 1";
+   *   $total = mymodule_select("SELECT COUNT(*) FROM data " . $where)->result();
+   *   $num_per_page = \Drupal::config('mymodule.settings')->get('num_per_page');
+   *   $pager = \Drupal::service('pager.manager')->createPager($total, $num_per_page);
+   *   $page = $pager->getCurrentPage();
+   *
+   *   // Next, retrieve the items for the current page and put them into a
+   *   // render array.
+   *   $offset = $num_per_page * $page;
+   *   $result = mymodule_select("SELECT * FROM data " . $where . " LIMIT %d, %d", $offset, $num_per_page)->fetchAll();
+   *   $render = [];
+   *   $render[] = [
+   *     '#theme' => 'mymodule_results',
+   *     '#result' => $result,
+   *   ];
+   *
+   *   // Finally, add the pager to the render array, and return.
+   *   $render[] = ['#type' => 'pager'];
+   *   return $render;
+   * @endcode
+   *
+   * A second example involves a controller that invokes an external search
+   * service where the total number of matching results is provided as part of
+   * the returned set (so that we do not need a separate query in order to
+   * obtain this information). Here, we call PagerManagerInterface->findPage()
+   * to calculate the desired offset before the search is invoked:
+   * @code
+   *
+   *   // Perform the query, using the requested offset from
+   *   // PagerManagerInterface::findPage(). This comes from a URL parameter, so
+   *   // here we are assuming that the URL parameter corresponds to an actual
+   *   // page of results that will exist within the set.
+   *   $pager_parameters = \Drupal::service('pager.parameters');
+   *   $page = $pager_parameters->findPage();
+   *   $num_per_page = \Drupal::config('mymodule.settings')->get('num_per_page');
+   *   $offset = $num_per_page * $page;
+   *   $result = mymodule_remote_search($keywords, $offset, $num_per_page);
+   *
+   *   // Now that we have the total number of results, initialize the pager.
+   *   $pager_manager = \Drupal::service('pager.manager');
+   *   $pager_manager->createPager($result->total, $num_per_page);
+   *
+   *   // Create a render array with the search results.
+   *   $render = [];
+   *   $render[] = [
+   *     '#theme' => 'search_results',
+   *     '#results' => $result->data,
+   *     '#type' => 'remote',
+   *   ];
+   *
+   *   // Finally, add the pager to the render array, and return.
+   *   $render[] = ['#type' => 'pager'];
+   *   return $render;
+   * @endcode
+   *
+   * @param int $total
+   *   The total number of items to be paged.
+   * @param int $limit
+   *   The number of items the calling code will display per page.
+   * @param int $element
+   *   (optional) An integer to distinguish between multiple pagers on one page.
+   *
+   * @return \Drupal\Core\Pager\Pager
+   *   The pager.
+   */
+  public function createPager($total, $limit, $element = 0);
+
+  /**
+   * Gets a pager from the static cache.
+   *
+   * @param int $element
+   *   The pager element index.
+   *
+   * @return \Drupal\Core\Pager\Pager|null
+   *   The pager, or null if not found.
+   */
+  public function getPager($element = 0);
+
+  /**
+   * Gets the URL query parameter array of a pager link.
+   *
+   * Adds to or adjusts the 'page' URL query parameter so that if you follow the
+   * link, you'll get page $index for pager $element on the page.
+   *
+   * The 'page' URL query parameter is a comma-delimited string, where each
+   * value is the target content page for the corresponding pager $element. For
+   * instance, if we have 5 pagers on a single page, and we want to have a link
+   * to a page that should display the 6th content page for the 3rd pager, and
+   * the 1st content page for all the other pagers, then the URL query will look
+   * like this: ?page=0,0,5,0,0 (page numbering starts at zero).
+   *
+   * @param array $query
+   *   An associative array of URL query parameters to add to.
+   * @param int $element
+   *   An integer to distinguish between multiple pagers on one page.
+   * @param int $index
+   *   The index of the target page, for the given element, in the pager array.
+   *
+   * @return array
+   *   The altered $query parameter array.
+   */
+  public function getUpdatedParameters(array $query, $element, $index);
+
+}
diff --git a/core/lib/Drupal/Core/Pager/PagerParameters.php b/core/lib/Drupal/Core/Pager/PagerParameters.php
new file mode 100644
index 000000000000..9a70c74d1a5b
--- /dev/null
+++ b/core/lib/Drupal/Core/Pager/PagerParameters.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Drupal\Core\Pager;
+
+use Drupal\Component\Utility\UrlHelper;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Provides pager information contained within the current request.
+ *
+ * @see \Drupal\Core\Pager\PagerManagerInterface
+ */
+class PagerParameters implements PagerParametersInterface {
+
+  /**
+   * The HTTP request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * Construct a PagerManager object.
+   *
+   * @param \Symfony\Component\HttpFoundation\RequestStack $stack
+   *   The current HTTP request stack.
+   */
+  public function __construct(RequestStack $stack) {
+    $this->requestStack = $stack;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQueryParameters() {
+    $request = $this->requestStack->getCurrentRequest();
+    if ($request) {
+      return UrlHelper::filterQueryParameters(
+        $request->query->all(), ['page']
+      );
+    }
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function findPage($pager_id = 0) {
+    $pages = $this->getPagerQuery();
+    return (int) ($pages[$pager_id] ?? 0);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPagerQuery() {
+    $query = $this->getPagerParameter();
+    return !empty($query) ? explode(',', $query) : [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPagerParameter() {
+    $request = $this->requestStack->getCurrentRequest();
+    if ($request) {
+      return $request->query->get('page', '');
+    }
+    return '';
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Pager/PagerParametersInterface.php b/core/lib/Drupal/Core/Pager/PagerParametersInterface.php
new file mode 100644
index 000000000000..a88286e51278
--- /dev/null
+++ b/core/lib/Drupal/Core/Pager/PagerParametersInterface.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\Core\Pager;
+
+/**
+ * Interface describing pager information contained within the request.
+ *
+ * @see \Drupal\Core\Pager\PagerManagerInterface
+ */
+interface PagerParametersInterface {
+
+  /**
+   * Gets all request URL query parameters that are unrelated to paging.
+   *
+   * @return array
+   *   A URL query parameter array that consists of all components of the
+   *   current page request except for those pertaining to paging.
+   */
+  public function getQueryParameters();
+
+  /**
+   * Returns the current page being requested for display within a pager.
+   *
+   * @param int $pager_id
+   *   (optional) An integer to distinguish between multiple pagers on one page.
+   *
+   * @return int
+   *   The number of the current requested page, within the pager represented by
+   *   $element. This is determined from the URL query parameter
+   *   \Drupal::request()->query->get('page'), or 0 by default. Note that this
+   *   number may differ from the actual page being displayed. For example, if a
+   *   search for "example text" brings up three pages of results, but a user
+   *   visits search/node/example+text?page=10, this function will return 10,
+   *   even though the default pager implementation adjusts for this and still
+   *   displays the third page of search results at that URL.
+   */
+  public function findPage($pager_id = 0);
+
+  /**
+   * Gets the request query parameter.
+   *
+   * @return int[]
+   *   Array of pagers. Keys are integers which are the element ID. Values are
+   *   the zero-based current page from the request. The first page is 0, the
+   *   second page is 1, etc.
+   */
+  public function getPagerQuery();
+
+  /**
+   * Gets the 'page' query parameter for the current request.
+   *
+   * @return string
+   *   The 'page' query parameter for the current request. This is a
+   *   comma-delimited string of pager element values. Defaults to empty string
+   *   if the query does not have a 'page' parameter.
+   */
+  public function getPagerParameter();
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Pager.php b/core/lib/Drupal/Core/Render/Element/Pager.php
index 6ba3dff75779..948fd7789f09 100644
--- a/core/lib/Drupal/Core/Render/Element/Pager.php
+++ b/core/lib/Drupal/Core/Render/Element/Pager.php
@@ -5,10 +5,10 @@
 /**
  * Provides a render element for a pager.
  *
- * The pager must be initialized with a call to pager_default_initialize() in
- * order to render properly. When used with database queries, this is performed
- * for you when you extend a select query with
- * \Drupal\Core\Database\Query\PagerSelectExtender.
+ * The pager must be initialized with a call to
+ * \Drupal\Core\Pager\PagerManagerInterface::createPager() in order to render
+ * properly. When used with database queries, this is performed for you when you
+ * extend a select query with \Drupal\Core\Database\Query\PagerSelectExtender.
  *
  * Properties:
  * - #element: (optional, int) The pager ID, to distinguish between multiple
@@ -68,11 +68,12 @@ public function getInfo() {
    */
   public static function preRenderPager(array $pager) {
     // Note: the default pager theme process function
-    // template_preprocess_pager() also calls pager_query_add_page(), which
-    // maintains the existing query string. Therefore
-    // template_preprocess_pager() adds the 'url.query_args' cache context,
-    // which causes the more specific cache context below to be optimized away.
-    // In other themes, however, that may not be the case.
+    // template_preprocess_pager() also calls
+    // \Drupal\Core\Pager\PagerManagerInterface::queryAddPage(), which maintains
+    // the existing query string. Therefore template_preprocess_pager() adds the
+    // 'url.query_args' cache context, which causes the more specific cache
+    // context below to be optimized away. In other themes, however, that may
+    // not be the case.
     $pager['#cache']['contexts'][] = 'url.query_args.pagers:' . $pager['#element'];
     return $pager;
   }
diff --git a/core/modules/system/tests/modules/pager_test/pager_test.module b/core/modules/system/tests/modules/pager_test/pager_test.module
index 1961f2d51e17..fe09c30467be 100644
--- a/core/modules/system/tests/modules/pager_test/pager_test.module
+++ b/core/modules/system/tests/modules/pager_test/pager_test.module
@@ -9,11 +9,12 @@
  * Implements hook_preprocess_HOOK().
  */
 function pager_test_preprocess_pager(&$variables) {
-  global $pager_total;
-
   // Nothing to do if there is only one page.
   $element = $variables['pager']['#element'];
-  if ($pager_total[$element] <= 1) {
+  /** @var \Drupal\Core\Pager\PagerManagerInterface $pager_manager */
+  $pager_manager = \Drupal::service('pager.manager');
+  $pager = $pager_manager->getPager($element);
+  if ($pager->getTotalPages() <= 1) {
     return;
   }
 
diff --git a/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php b/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php
index 5048e131bd47..2cdba6601203 100644
--- a/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php
+++ b/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php
@@ -5,13 +5,39 @@
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\Query\PagerSelectExtender;
+use Drupal\Core\Pager\PagerParametersInterface;
 use Drupal\Core\Security\TrustedCallbackInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Controller routine for testing the pager.
  */
 class PagerTestController extends ControllerBase implements TrustedCallbackInterface {
 
+  /**
+   * The pager request service.
+   *
+   * @var \Drupal\Core\Pager\PagerParametersInterface
+   */
+  protected $pagerParams;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('pager.parameters'));
+  }
+
+  /**
+   * Construct a new PagerTestController object.
+   *
+   * @param \Drupal\Core\Pager\PagerParametersInterface $pager_params
+   *   The pager parameters.
+   */
+  public function __construct(PagerParametersInterface $pager_params) {
+    $this->pagerParams = $pager_params;
+  }
+
   /**
    * Builds a render array for a pageable test table.
    *
@@ -59,7 +85,7 @@ public function queryParameters() {
     $build['pager_table_0'] = $this->buildTestTable(0, 5);
 
     // Counter of calls to the current pager.
-    $query_params = pager_get_query_parameters();
+    $query_params = $this->pagerParams->getQueryParameters();
     $pager_calls = isset($query_params['pager_calls']) ? ($query_params['pager_calls'] ? $query_params['pager_calls'] : 0) : 0;
     $build['l_pager_pager_0'] = ['#markup' => $this->t('Pager calls: @pager_calls', ['@pager_calls' => $pager_calls])];
 
diff --git a/core/modules/system/tests/src/Kernel/Pager/PagerDeprecationTest.php b/core/modules/system/tests/src/Kernel/Pager/PagerDeprecationTest.php
new file mode 100644
index 000000000000..37ea40fc870d
--- /dev/null
+++ b/core/modules/system/tests/src/Kernel/Pager/PagerDeprecationTest.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\Tests\system\Kernel\Pager;
+
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Ensure that deprecated pager functions trigger deprecation errors.
+ *
+ * @group Pager
+ * @group legacy
+ */
+class PagerDeprecationTest extends KernelTestBase {
+
+  /**
+   * @expectedDeprecation pager_find_page is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\RequestPagerInterface->findPage() instead. See https://www.drupal.org/node/2779457
+   */
+  public function testFindPage() {
+    $this->assertInternalType('int', pager_find_page());
+  }
+
+  /**
+   * @expectedDeprecation pager_default_initialize is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface->createPager() instead. See https://www.drupal.org/node/2779457
+   */
+  public function testDefaultInitialize() {
+    $this->assertInternalType('int', pager_default_initialize(1, 1));
+  }
+
+  /**
+   * @expectedDeprecation pager_get_query_parameters is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\RequestPagerInterface->getQueryParameters() instead. See https://www.drupal.org/node/2779457
+   */
+  public function testGetQueryParameters() {
+    $this->assertInternalType('array', pager_get_query_parameters());
+  }
+
+  /**
+   * @expectedDeprecation pager_query_add_page is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface->queryAddPage() instead. See https://www.drupal.org/node/2779457
+   */
+  public function testQueryAddPage() {
+    $this->assertArrayHasKey('page', pager_query_add_page([], 1, 1));
+  }
+
+}
diff --git a/core/modules/taxonomy/src/Form/OverviewTerms.php b/core/modules/taxonomy/src/Form/OverviewTerms.php
index 75fe42cd7677..502900b87d12 100644
--- a/core/modules/taxonomy/src/Form/OverviewTerms.php
+++ b/core/modules/taxonomy/src/Form/OverviewTerms.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Pager\PagerManagerInterface;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Url;
 use Drupal\taxonomy\VocabularyInterface;
@@ -70,6 +71,13 @@ class OverviewTerms extends FormBase {
    */
   protected $entityRepository;
 
+  /**
+   * The pager manager.
+   *
+   * @var \Drupal\Core\Pager\PagerManagerInterface
+   */
+  protected $pagerManager;
+
   /**
    * Constructs an OverviewTerms object.
    *
@@ -81,8 +89,10 @@ class OverviewTerms extends FormBase {
    *   The renderer service.
    * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
    *   The entity repository.
+   * @param \Drupal\Core\Pager\PagerManagerInterface|null $pager_manager
+   *   The pager manager.
    */
-  public function __construct(ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer = NULL, EntityRepositoryInterface $entity_repository = NULL) {
+  public function __construct(ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer = NULL, EntityRepositoryInterface $entity_repository = NULL, PagerManagerInterface $pager_manager = NULL) {
     $this->moduleHandler = $module_handler;
     $this->entityTypeManager = $entity_type_manager;
     $this->storageController = $entity_type_manager->getStorage('taxonomy_term');
@@ -93,6 +103,11 @@ public function __construct(ModuleHandlerInterface $module_handler, EntityTypeMa
       $entity_repository = \Drupal::service('entity.repository');
     }
     $this->entityRepository = $entity_repository;
+    if (!$pager_manager) {
+      @trigger_error('Calling OverviewTerms::__construct() without the $pager_manager argument is deprecated in drupal:8.8.0 and the $pager_manager argument will be required in drupal:9.0.0. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
+      $pager_manager = \Drupal::service('pager.manager');
+    }
+    $this->pagerManager = $pager_manager;
   }
 
   /**
@@ -103,7 +118,8 @@ public static function create(ContainerInterface $container) {
       $container->get('module_handler'),
       $container->get('entity_type.manager'),
       $container->get('renderer'),
-      $container->get('entity.repository')
+      $container->get('entity.repository'),
+      $container->get('pager.manager')
     );
   }
 
@@ -131,10 +147,6 @@ public function getFormId() {
    *   The form structure.
    */
   public function buildForm(array $form, FormStateInterface $form_state, VocabularyInterface $taxonomy_vocabulary = NULL) {
-    // @todo Remove global variables when https://www.drupal.org/node/2044435 is
-    //   in.
-    global $pager_page_array, $pager_total, $pager_total_items;
-
     $form_state->set(['taxonomy', 'vocabulary'], $taxonomy_vocabulary);
     $vocabulary_hierarchy = $this->storageController->getVocabularyHierarchyType($taxonomy_vocabulary->id());
     $parent_fields = FALSE;
@@ -227,9 +239,7 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
 
     // Because we didn't use a pager query, set the necessary pager variables.
     $total_entries = $before_entries + $page_entries + $after_entries;
-    $pager_total_items[0] = $total_entries;
-    $pager_page_array[0] = $page;
-    $pager_total[0] = ceil($total_entries / $page_increment);
+    $this->pagerManager->createPager($total_entries, $page_increment);
 
     // If this form was already submitted once, it's probably hit a validation
     // error. Ensure the form is rebuilt in the same order as the user
diff --git a/core/modules/views/src/Plugin/views/pager/SqlBase.php b/core/modules/views/src/Plugin/views/pager/SqlBase.php
index bba7848c7868..29b3ba850f66 100644
--- a/core/modules/views/src/Plugin/views/pager/SqlBase.php
+++ b/core/modules/views/src/Plugin/views/pager/SqlBase.php
@@ -5,11 +5,77 @@
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheableDependencyInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Pager\PagerManagerInterface;
+use Drupal\Core\Pager\PagerParameters;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * A common base class for sql based pager.
  */
-abstract class SqlBase extends PagerPluginBase implements CacheableDependencyInterface {
+abstract class SqlBase extends PagerPluginBase implements CacheableDependencyInterface, ContainerFactoryPluginInterface {
+
+  /**
+   * The pager manager.
+   *
+   * @var \Drupal\Core\Pager\PagerManagerInterface
+   */
+  protected $pagerManager;
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * The pager parameters.
+   *
+   * @var \Drupal\Core\Pager\PagerParametersInterface
+   */
+  protected $pagerParameters;
+
+  /**
+   * Constructs a SqlBase object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Pager\PagerManagerInterface $pager_manager
+   *   The pager manager.
+   * @param \Drupal\Core\Pager\PagerParameters|null $pager_parameters
+   *   The pager parameters.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, PagerManagerInterface $pager_manager = NULL, PagerParameters $pager_parameters = NULL) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    if (!$pager_manager) {
+      @trigger_error('Calling ' . __METHOD__ . ' without the $pager_manager argument is deprecated in drupal:8.8.0 and is required in drupal:9.0.0. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
+      $pager_manager = \Drupal::service('pager.manager');
+    }
+    $this->pagerManager = $pager_manager;
+    if (!$pager_parameters) {
+      @trigger_error('Calling ' . __METHOD__ . ' without the $pager_parameters argument is deprecated in drupal:8.8.0 and is required in drupal:9.0.0. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
+      $pager_parameters = \Drupal::service('pager.parameters');
+    }
+    $this->pagerParameters = $pager_parameters;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('pager.manager'),
+      $container->get('pager.parameters')
+    );
+  }
 
   protected function defineOptions() {
     $options = parent::defineOptions();
@@ -242,7 +308,7 @@ public function query() {
    *
    * @param $number
    *   If provided, the page number will be set to this. If NOT provided,
-   *   the page number will be set from the global page array.
+   *   the page number will be set from the pager manager service.
    */
   public function setCurrentPage($number = NULL) {
     if (isset($number)) {
@@ -250,25 +316,7 @@ public function setCurrentPage($number = NULL) {
       return;
     }
 
-    // If the current page number was not specified, extract it from the global
-    // page array.
-    global $pager_page_array;
-
-    if (empty($pager_page_array)) {
-      $pager_page_array = [];
-    }
-
-    // Fill in missing values in the global page array, in case the global page
-    // array hasn't been initialized before.
-    $page = $this->view->getRequest()->query->get('page');
-    $page = isset($page) ? explode(',', $page) : [];
-
-    for ($i = 0; $i <= $this->options['id'] || $i < count($pager_page_array); $i++) {
-      $pager_page_array[$i] = empty($page[$i]) ? 0 : $page[$i];
-    }
-
-    // Don't allow the number to be less than zero.
-    $this->current_page = max(0, intval($pager_page_array[$this->options['id']]));
+    $this->current_page = max(0, $this->pagerParameters->findPage($this->options['id']));
   }
 
   public function getPagerTotal() {
@@ -297,24 +345,11 @@ public function updatePageInfo() {
     // Don't set pager settings for items per page = 0.
     $items_per_page = $this->getItemsPerPage();
     if (!empty($items_per_page)) {
-      // Dump information about what we already know into the globals.
-      global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
-      // Set the limit.
-      $pager_limits[$this->options['id']] = $this->options['items_per_page'];
-      // Set the item count for the pager.
-      $pager_total_items[$this->options['id']] = $this->total_items;
-      // Calculate and set the count of available pages.
-      $pager_total[$this->options['id']] = $this->getPagerTotal();
-
+      $pager = $this->pagerManager->createPager($this->getTotalItems(), $this->options['items_per_page'], $this->options['id']);
       // See if the requested page was within range:
-      if ($this->current_page >= $pager_total[$this->options['id']]) {
-        // Pages are numbered from 0 so if there are 10 pages, the last page is 9.
-        $this->setCurrentPage($pager_total[$this->options['id']] - 1);
+      if ($this->getCurrentPage() >= $pager->getTotalPages()) {
+        $this->setCurrentPage($pager->getTotalPages() - 1);
       }
-
-      // Put this number in to guarantee that we do not generate notices when the pager
-      // goes to look for it later.
-      $pager_page_array[$this->options['id']] = $this->current_page;
     }
   }
 
diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc
index f2ea74682912..509a146e0381 100644
--- a/core/modules/views/views.theme.inc
+++ b/core/modules/views/views.theme.inc
@@ -1000,18 +1000,25 @@ function template_preprocess_views_exposed_form(&$variables) {
  *     exposed input.
  */
 function template_preprocess_views_mini_pager(&$variables) {
-  global $pager_page_array, $pager_total;
+  /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */
+  $pager_manager = \Drupal::service('pager.manager');
 
   $tags = &$variables['tags'];
   $element = $variables['element'];
   $parameters = $variables['parameters'];
+  $pager = $pager_manager->getPager($element);
+  if (!$pager) {
+    return;
+  }
+  $current = $pager->getCurrentPage();
+  $total = $pager->getTotalPages();
 
   // Current is the page we are currently paged to.
-  $variables['items']['current'] = $pager_page_array[$element] + 1;
+  $variables['items']['current'] = $current + 1;
 
-  if ($pager_total[$element] > 1 && $pager_page_array[$element] > 0) {
+  if ($total > 1 && $current > 0) {
     $options = [
-      'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] - 1),
+      'query' => $pager_manager->getUpdatedParameters($parameters, $element, $current - 1),
     ];
     $variables['items']['previous']['href'] = Url::fromRoute('<current>', [], $options)->toString();
     if (isset($tags[1])) {
@@ -1020,9 +1027,9 @@ function template_preprocess_views_mini_pager(&$variables) {
     $variables['items']['previous']['attributes'] = new Attribute();
   }
 
-  if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
+  if ($current < ($total - 1)) {
     $options = [
-      'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] + 1),
+      'query' => $pager_manager->getUpdatedParameters($parameters, $element, $current + 1),
     ];
     $variables['items']['next']['href'] = Url::fromRoute('<current>', [], $options)->toString();
     if (isset($tags[3])) {
diff --git a/core/tests/Drupal/KernelTests/Core/Pager/PagerManagerTest.php b/core/tests/Drupal/KernelTests/Core/Pager/PagerManagerTest.php
new file mode 100644
index 000000000000..a03e759ad301
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Pager/PagerManagerTest.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Pager;
+
+use Drupal\KernelTests\KernelTestBase;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * @group Pager
+ *
+ * @coversDefaultClass \Drupal\Core\Pager\PagerManager
+ */
+class PagerManagerTest extends KernelTestBase {
+
+  /**
+   * @covers ::createPager
+   */
+  public function testDefaultInitializeGlobals() {
+    $pager_globals = [
+      'pager_page_array',
+      'pager_total_items',
+      'pager_total',
+      'pager_limits',
+    ];
+    foreach ($pager_globals as $pager_global) {
+      $this->assertFalse(isset($GLOBALS[$pager_global]));
+    }
+    /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */
+    $pager_manager = $this->container->get('pager.manager');
+
+    $pager_manager->createPager(5, 1);
+
+    foreach ($pager_globals as $pager_global) {
+      $this->assertTrue(isset($GLOBALS[$pager_global]));
+    }
+  }
+
+  /**
+   * @covers ::getUpdatedParameters
+   */
+  public function testQueryAddPage() {
+    $element = 2;
+    $index = 5;
+    $test_parameters = [
+      'other' => 'arbitrary',
+    ];
+    $request = Request::create('http://example.com', 'GET', $test_parameters);
+
+    /* @var $request_stack \Symfony\Component\HttpFoundation\RequestStack */
+    $request_stack = $this->container->get('request_stack');
+    $request_stack->push($request);
+
+    /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */
+    $pager_manager = $this->container->get('pager.manager');
+
+    $pager_manager->createPager(30, 10, $element);
+    $query = $pager_manager->getUpdatedParameters($request->query->all(), $element, $index);
+
+    $this->assertArrayHasKey('other', $query);
+
+    $this->assertEquals(",,$index", $query['page']);
+  }
+
+  /**
+   * @group legacy
+   * @expectedDeprecation Global variable $pager_page_array is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457
+   * @expectedDeprecation Global variable $pager_total_items is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457
+   * @expectedDeprecation Global variable $pager_total is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457
+   * @expectedDeprecation Global variable $pager_limits is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457
+   */
+  public function testGlobalsSafety() {
+
+    /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */
+    $pager_manager = $this->container->get('pager.manager');
+
+    $pager_manager->createPager(30, 10);
+
+    $pager_globals = [
+      'pager_page_array',
+      'pager_total_items',
+      'pager_total',
+      'pager_limits',
+    ];
+    // Check globals were set.
+    foreach ($pager_globals as $pager_global) {
+      $this->assertTrue(isset($GLOBALS[$pager_global]));
+    }
+
+    $this->assertEquals(0, $GLOBALS['pager_page_array'][0]);
+    $this->assertEquals(30, $GLOBALS['pager_total_items'][0]);
+    $this->assertEquals(3, $GLOBALS['pager_total'][0]);
+    $this->assertEquals(10, $GLOBALS['pager_limits'][0]);
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Pager/RequestPagerTest.php b/core/tests/Drupal/KernelTests/Core/Pager/RequestPagerTest.php
new file mode 100644
index 000000000000..bb1bc9066b7d
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Pager/RequestPagerTest.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Pager;
+
+use Drupal\KernelTests\KernelTestBase;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * @group Pager
+ *
+ * @coversDefaultClass \Drupal\Core\Pager\PagerParameters
+ */
+class RequestPagerTest extends KernelTestBase {
+
+  /**
+   * @covers ::findPage
+   */
+  public function testFindPage() {
+    $request = Request::create('http://example.com', 'GET', ['page' => '0,10']);
+
+    /* @var $request_stack \Symfony\Component\HttpFoundation\RequestStack */
+    $request_stack = $this->container->get('request_stack');
+    $request_stack->push($request);
+
+    $pager_params = $this->container->get('pager.parameters');
+
+    $this->assertEquals(10, $pager_params->findPage(1));
+  }
+
+  /**
+   * @covers ::getQueryParameters
+   */
+  public function testGetQueryParameters() {
+    $test_parameters = [
+      'other' => 'arbitrary',
+    ];
+    $request = Request::create('http://example.com', 'GET', array_merge(['page' => '0,10'], $test_parameters));
+
+    /* @var $request_stack \Symfony\Component\HttpFoundation\RequestStack */
+    $request_stack = $this->container->get('request_stack');
+    $request_stack->push($request);
+
+    $pager_params = $this->container->get('pager.parameters');
+
+    $this->assertEquals($test_parameters, $pager_params->getQueryParameters());
+    $this->assertEquals(0, $pager_params->findPage());
+  }
+
+}
-- 
GitLab