Symfony 4 | Pagination und Suche

Im aktuellen Projekt bin ich nun im Frontend soweit fertig – nun geht es endlich ans eingemachte. Da ich mit Listen von über 5000 Helfern und zig tausend Aufträgen arbeiten muss muss auf jeden Fall eine Paginierung und eine Suche her…

Pagination

Bei der Seitennummerierung habe ich mich für ein Bundle in Symfony entschieden. Nachdem ich mich mal schlau gemacht habe im „Internetz“ habe ich dazu versch. Beiträge gefunden. Die meisten davon landen Schlussendlich beim WhiteOctoberPagerfantaBundle. Warum das Rad neu erfinden?

In Cakephp gibt es dafür übrigens schon eine integrierte Paginator Komponente (Pagination in CakePHP) – da war ich auf jeden Fall verwöhnt 😐 Aber weiter im Text

composer require white-october/pagerfanta-bundle

Das wars auch schon, die Zeile 

    WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle::class => ['all' => true]

wird beim require automatisch in eure bundles.php eingebunden.

Nutzung

Da ich Doctrine als ORM nutze werde ich hier den DoctrineORMAdapter zeigen. Ein komplette Doku findet ihr auf dem Github Repo

use Pagerfanta\Pagerfanta;
use Pagerfanta\Adapter\DoctrineORMAdapter;

Es ist überlassen ob Ihr das ganze über einen Service im Repository oder direkt im Repository aufruft.

Repository / Service

AdressesService.php
<?php namespace App\Service;

use App\Repository\AdressesRepository;
use Pagerfanta\Pagerfanta;
use Psr\Log\LoggerInterface;

/**
 * @package App\Service
 */
class AdressesService
{
    /**
     * @var AdressesRepository
     */
    private $repository;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * AdressesService constructor
     * @param AdressesRepository    $repository
     * @param LoggerInterface       $logger
     */
    public function __construct(
        AdressesRepository $repository,
        LoggerInterface $logger
    ) {
        $this->repository = $repository;
        $this->logger = $logger;
    }


    /**
     * @param string $char
     * @param int    $page
     * @param int    $max
     *
     * @return Pagerfanta|bool
     */
    public function pageResults(int $page = 1, int $max = 30)
    {
        try {
            return $this->repository->pagenate($max, $page);
        } catch (Exception $e) {
            $this->logger->error($e->getMessage());

            return false;
        }
    }
}

AdressesRepository.php

<?php

namespace App\Repository;

use App\Entity\Adresses;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
use Pagerfanta\Pagerfanta;
use Pagerfanta\Adapter\DoctrineORMAdapter;

class AdressesRepository extends ServiceEntityRepository
{

    public function __construct(RegistryInterface $registry)
    {
        parent::__construct($registry, Adresses::class);
    }


    /**
     * @param int    $limit
     * @param int    $offset
     * @return Pagerfanta
     */
    public function pagenate(int $limit = 10, int $offset = 1)
    {
        $queryBuilder = $this->createQueryBuilder('a');

        $queryBuilder->select('a');

        $adapter = new DoctrineORMAdapter($queryBuilder);
        $pagerfanta = new Pagerfanta($adapter);

        $pagerfanta->setMaxPerPage($limit);
        $pagerfanta->setCurrentPage($offset);

        return $pagerfanta;
    }

}

In meinem Fall nutze ich hier keinen Service, sondern greife direkt auf das Repository zu (Diskussion dazu gibt’s hier) Für mich hieße das in 95% der Fälle einfach „pass though methods“ zu erstellen – einfach Zeitverschwendung…

Wie Ihr im Repository seht übergebt Ihr einfach euren Querybuilder an den DoctrineORMAdapter – diesen Adapter übergebt Ihr dann an an das Pagerfanta Objekt – setzt noch euer Limit und Offset und returned euer Pagerfanta Objekt.

Controller

public function index(AdressesService $adressesService): Response
{
$pagerfanta = $this->getDoctrine()->getRepository(Adresses::class)->pagenate(20, 5);

return $this->render('adresses/admin/index.html.twig', ['pager' => $pagerfanta]);
}

View / Twig

In Twig könnt Ihr die Results mit currentPageResults ausgeben.

{% for item in pagination.currentPageResults %}
{{ item.name }}
{% endfor %}

eure Paginierung sieht in Bootstrap 4 bei mir wie folgt aus:

<div class="row">
        <div class="col-12 nopadding">
            <div class="pagerfanta">
                {{ pagerfanta(pagination, 'twitter_bootstrap4') }}
            </div>
        </div>
    </div>

Das Bundle liefert hier schon versch. Bootstrap Themes mit – euch ist aber überlassen hier auch ein eigenes Theme zu übergeben (siehe DOKU)

Wenn Ihr euch previous und next übersetzen lassen wollt, nutzt einfach twitter_bootstrap4_translated statt twitter_bootstrap4

Wollt Ihr individuell Daten mitgeben könnt Ihr dem Template auch die options anhängen

{{ pagerfanta(pagination, 'twitter_bootstrap4', {'prev_message': 'custom.previous'|trans, 'custom.next': 'next_message'|trans}) }}

Als Optionen habt Ihr:

static protected $defaultOptions = array(
        'prev_message'        => '← Previous',
        'next_message'        => 'Next →',
        'dots_message'        => '…',
        'active_suffix'       => '',
        'css_container_class' => 'pagination',
        'css_prev_class'      => 'prev',
        'css_next_class'      => 'next',
        'css_disabled_class'  => 'disabled',
        'css_dots_class'      => 'disabled',
        'css_active_class'    => 'active',
        'rel_previous'        => 'prev',
        'rel_next'            => 'next'
    );

Ergebnis

Das ganze sieht am Ende dann so aus:

Paginierung mit Pagerfanta

Ganz ordentlich und schnell zu konfigurieren. Mein nächstes Projekt nutzt auf jeden Fall wieder Pagerfanta für die Seitennummerierung.

Hier geht es weiter zur Suche mit typeahead…

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.