Paginadores personalizados en Drupal

Paginadores personalizados en Drupal
Solucionex
21
Jul 23

Drupal es capaz de mostrar paginadores de manera automática en listados por defecto de entidades y views (si se configura la vista para usar paginador). En este post se explica cómo aprovechar los paginadores de Drupal en funcionalidad personalizada.

Base de código

Para los ejemplos de código, se usa un módulo personalizado "example_module" con los siguientes archivos:

example_module/example_module.routing.yml

En este archivo se crean 3 rutas, una para cada ejemplo de paginador.

example_module.entity_query_pager:
  path: '/entity-query-pager'
  defaults:
    _controller: '\Drupal\example_module\Controller\PagerController::entityQuery'
    _title: 'Entity query pager'
  requirements:
    _permission: 'access content'
example_module.select_pager:
  path: '/select-pager'
  defaults:
    _controller: '\Drupal\example_module\Controller\PagerController::select'
    _title: 'Select pager'
  requirements:
    _permission: 'access content'
example_module.api_pager:
  path: '/api-pager'
  defaults:
    _controller: '\Drupal\example_module\Controller\PagerController::api'
    _title: 'API pager'
  requirements:
    _permission: 'access content'

example_module/src/Controller/PagerController.php

<?php

declare(strict_types = 1);

namespace Drupal\example_module\Controller;

use Drupal\Component\Serialization\Json;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\PagerSelectExtender;
use Drupal\Core\Pager\PagerManagerInterface;
use Drupal\Core\Url;
use GuzzleHttp\ClientInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;

/**
 * Controller for showing custom pagers.
 */
class PagerController extends ControllerBase {

  /**
   * Class constructor.
   */
  public function __construct(
    protected PagerManagerInterface $pagerManager,
    protected Connection $database,
    protected ClientInterface $httpClient,
  ) {}
  
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('pager.manager'),
      $container->get('database'),
      $container->get('http_client'),
    );
  }
  
  /**
   * Entity query pager.
   */
  public function entityQuery() {
    $nids = $this->entityTypeManager()->getStorage('node')->getQuery()
      ->accessCheck(TRUE)
      ->condition('type', 'news')
      ->pager()
      ->execute();
    $nodes = $this->entityTypeManager()->getStorage('node')->loadMultiple($nids);
    return [
      'items' => $this->entityTypeManager()->getViewBuilder('node')->viewMultiple($nodes, 'teaser'),
      'pager' => [
        '#type' => 'pager',
      ],
    ];
  }
  
  /**
   * Select pager.
   */
  public function select() {
    $results = $this->database->select('node_field_data', 'n')
      ->condition('type', 'news')
      ->fields('n', ['title'])
      ->extend(PagerSelectExtender::class)
      ->execute()
      ->fetchCol();
    return [
      'items' => array_map(fn ($result) => ['#markup' => '<h2>' . $result . '</h2>'], $results),
      'pager' => [
        '#type' => 'pager',
      ],
    ];
  }
  
  /**
   * API pager.
   */
  public function api(Request $request) {
    $base_url = 'https://pokeapi.co/api/v2/pokemon';
    $url = Url::fromUri($base_url, [
      'query' => [
        'limit' => 10,
        'offset' => $request->query->get('page', 0) * 10,
      ],
    ]);
    $response = $this->httpClient->request('GET', $url->toString());
    $results = Json::decode($response->getBody()->getContents());
    $total = $results['count'];
    $this->pagerManager->createPager($total, 10);
    return [
      'items' => array_map(fn ($result) => ['#markup' => '<h2>' . $result['name'] . '</h2>'], $results['results']),
      'pager' => [
        '#type' => 'pager',
      ],
    ];
  }
  
}

Nota: Ni la instalación de módulos personalizados ni la inyección de dependencias se explican en este artículo.

Paginador usando entityQuery()

Paginación usando la consulta de entidades de Drupal.

entity_query

Esta página (/entity-query-pager) corresponde a la función "entityQuery" del controlador.

Paso a paso

  1. Se genera una consulta de entidades usando entityTypeManager. En este punto, lo importante es la inicialización del paginador usando el método "pager". Este método recibe el número de elementos por página (10 por defecto) y el id del paginador (se usa en caso de que haya más de un paginador en la misma página, no es necesario en este ejemplo)
  2. Usando entityTypeManager de nuevo, se cargan los nodos con los ids resultantes de la consulta
  3. Se devuelve un render array con los resultados (usando el modo de vista resumen en este caso). La clave en este punto es añadir al render array un elemento de tipo "pager". Como ya se ha inicializado el paginador en la consulta, Drupal se encarga del resto

Paginador usando select()

Paginación usando una consulta SQL normal a través del DAL de Drupal.

select

Esta página (/select-pager) corresponde a la función "select" del controlador.

Paso a paso

  1. Se genera una consulta a la base de datos usando el generador de consultas SQL de Drupal. En este punto, lo importante es la inicialización del paginador usando el método "extend(PagerSelectExtender::class)". Este método sirve para extender una consulta SQL con parámetros anteriormente defnidos (por ejemplo esta clase para la paginación o la clase "TableSortExtender" para ordenación de tablas)
    • Nota: para limitar el número de elementos por página, en lugar de usar el método "range", hay que usar el método "limit" tras la extensión con el paginador. El método "limit" recibe como parámetro el número de elementos por página. 10 por defecto
  2. Se devuelve un render array con los resultados (los elementos se envuelven en un h2 para mejor visualización). La clave en este punto es añadir al render array un elemento de tipo "pager". Como ya se ha inicializado el paginador en la consulta, Drupal se encarga del resto

Paginador con datos de API

Paginación ejecutando llamadas a una API. En este caso, la API de pokémon.

api

Esta página (/api-pager) corresponde a la función "api" del controlador.

Paso a paso

  1. Se genera una petición a la api de pokémon (en este caso, al listado general de pokémon). En este ejemplo no se hace control de excepciones
  2. Usando la respuesta de la petición, se genera el paginador. Para ello, se usa el servicio "pager.manager", al que hay que pasarle como argumento el total (la api lo devuelve en la clave "count") y el número de elementos por página a mostrar
  3. Se devuelve un render array con los resultados (los elementos se envuelven en un h2 para mejor visualización). La clave en este punto es añadir al render array un elemento de tipo "pager". Como ya se ha inicializado el paginador usando el servicio, Drupal se encarga del resto