Extrendre els repositoris. Paginació
Taula de continguts
Introducció
Com ja has vist, l’objecte Repository permet executar consultes senzilles sense pràcticament cap treball:
// from inside a controller
$repository = $this->getDoctrine()->getRepository(Link::class);
$links = $repository->findAll();
Els mètodes diponibles són els següents:
/**
 * @method Link|null find($id, $lockMode = null, $lockVersion = null)
 * @method Link|null findOneBy(array $criteria, array $orderBy = null)
 * @method Link[]    findAll()
 * @method Link[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
Però, i si necessitem una consulta més complexa? Quan generem una entitat amb make:entity, el comandament també genera una classe anomenada ProductRepository.
// src/Repository/LinkRepository.php
namespace App\Repository;
use App\Entity\Link;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
class LinkRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Product::class);
    }
}
Quan obtens el repositori (p.e. ->getRepository(Link::class)), estàs obtenint realment una instància d’aquest objecte!
Anotacions
Cal assegurar-se que l’entitat té correctament vinculat el repositori mitjançant una anotació. En el nostre exemple l’entitat Link haurà d’incloure:
@ORM\Entity(repositoryClass="App\Repository\LinkRepository")
QueryBuilder
Suposem que vols fer una consulta que obtinga tots els objectes Link amb data posterior a certa data. Hauries d’afegir un nou mètode dins del repositori:
// src/Repository/LinkRepository.php
// ...
class LinkRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Product::class);
    }
    /**
     * @return Link[]
     */
    public function findAllAfterDate(DateTime $date): array{
        $query = $this->createQueryBuilder('l')
            ->andwhere("l.createdAt >= :date")
            ->orderBy('l.createdAt', 'DESC')
            ->setParameter('date', $date)
            ->getQuery();
        return $query->getResult();
    }
Query Builder, és una forma orientada a objectes d’escriure consultes. Es recomana utilitzar-la quan les consultes es creen dinàmicament.
Ara, pots cridar el mètode del repositori:
// from inside a controller
$date = new DateTime("2020-01-01");
$products = $this->getDoctrine()
    ->getRepository(Link::class)
    ->findAllAfterDate($date);
// ...
Query
També es possible utilitzar l’objecte Query, així com executar consultes directament amb PDO.
   /**
     * @return Link[]
     */
    public function findAllAfterDate(DateTime $date): array{
        $query = $this->createQuery('
            SELECT l
            FROM App\Entity\Link l
            WHERE l.createdAt >= :date
            ORDER BY l.createdAt DESC')
            ->setParameter('date', $date);
        return $query->getResult();
    }
Consultes SQL
// src/Repository/LinkRepository.php
// ...
public function findAllAfterDate(DateTime $date): array
{
    $conn = $this->getEntityManager()->getConnection();
    $sql = '
        SELECT * FROM link l
        WHERE l.created_at > :date
        ORDER BY l.created_at DESC
        ';
    $stmt = $conn->prepare($sql);
    $stmt->execute(['date' => $date]);
    // returns an array of arrays (i.e. a raw data set)
    return $stmt->fetchAll();
}
Aquestes consultes SQL tornen arrays, no objectes (a no ser que uses la funcionalitat NativeQuery).
Més informació en Querying for objects the repository
Exemple
En el següent exemple pots observar un ús del nou mètode del repositori. Hem modificat la ruta homepage perquè comprove si ha rebut pel querystring el paràmetre start-date si és així executarà el mètode findAllAfterDate()si no cridarà findAll().
    /**
     * @Route("/", name="homepage")
     */
    public function index(Request $request)
    {
        $repo = $this->getDoctrine()
            ->getRepository(Link::class);
        if ($request->query->has("start-date")) {
            $date = new \DateTime($request->query->get("start-date"));
            $links = $repo->findAllAfterDate($date);
            // TODO: check if the paràmeter start-date is a valid date
        }
        else {
            $links = $repo->findAll();
        }
        // now pass the array of default object to the view
        return $this->render('default/index.html.twig', [
            'links' => $links,
        ]);
    }
Implementa el mètode findAllAfterDate
Implementa el mètode findAllAfterDate() del repositori i modifica la ruta homepage perquè en cas que reba el paràmetre start-date pel querystring mostre sols aquells enllaços amb data d’actualització posterior a la data indicada.
Paginació
Symfony no inclou un paginador de forma nativa però al incloure Doctrine permet implementar-ho fàcilment sense necessitat d’instal·lar nous components. A continuació presentarem tres possibles opcions d’implementació.
La classe Paginator de Doctrine
En aquest cas crearíem nous mètodes en el repositori. findAllPaginated crea la consulta i la passa al mètode paginate que serà el que farà la paginació.
El mètode paginate ens tornarà un objecte Paginator que conté en la propietat Paginator::results els resultats.
Ens faltaria obtenir el total de registres la qual cosa és ben senzilla ja que la classe Paginator implementa la interfície Countable i simplement usant el mètode count() obtindrem el total de registres.
use Doctrine\ORM\Tools\Pagination\Paginator;
...
public function findAllPaginated($currentPage = 1):?Paginator
{
    $query = $this->createQueryBuilder('l')
        ->orderBy('l.createdAt', 'DESC')
        ->getQuery();
    // No need to manually get get the result ($query->getResult())
    $paginator = $this->paginate($query, $currentPage);
    return $paginator;
}
// Paginate results. 
public function paginate($dql, $page = 1, $limit = 5):?Paginator
{
    $paginator = new Paginator($dql);
    $paginator->getQuery()
    ->setFirstResult($limit * ($page - 1)) // Offset
    ->setMaxResults($limit); // Limit
    return $paginator;
}
Més informació en Pagination i Symfony and Doctrine pagination with Twig
Classe Paginator de symfony/demo
Un altra opció és fer ús de la classe Paginator que s’utilitza en el projecte d’exemple de Symfony. Empra la classe Paginator de Doctrine i ens proporciona tots els mètodes necessaris per a fer la paginació.
- Classe Paginator.
 - Repositori PostRepositori:findLatest.
 - Mètode BlogController::index().
 - Plantilla de Twig blog/index.html.twig.
 
knplabs/knp-paginator-bundle
Paginator per a Symfony automatitza la paginació i simplifica l’ordenació i altres característiques.
En la descripció del paquet knplabs/knp-paginator-bundle obtindreu més informació.
Implementa el paginador
Implementa la paginació de links en la pàgina principal de forma que mostre 10 enllaços ordenats des del més actual al més antic. Pots emprar qualsevol dels mètodes que hem demostrat.