Symfony 2.2, el libro oficial

8.2. Buscando objetos

En las secciones anteriores has visto cómo el objeto repositorio te permite realizar consultas básicas sin ningún esfuerzo:

$repository->find($id);

$repository->findOneByName('Foo');

Doctrine también te permite escribir consultas más complejas utilizando el lenguaje de consulta Doctrine o DQL (por sus siglas en inglés, Doctrine Query Language). DQL es bastante similar a SQL, salvo que en este caso estás buscando objetos de una determinada entidad (por ejemplo, Product) en vez de buscar filas de una tabla (por ejemplo, product).

Además, al realizar consultas en Doctrine, tienes dos opciones: escribir las consultas enteras a mano o utilizar el generador de consultas de Doctrine.

8.2.1. Buscando objetos con DQL

Imagina que necesitas buscar los productos que cuestan más de 19.99 y quieres ordenar los resultados del más barato al más caro. El código necesario dentro de un controlador de Symfony sería el siguiente:

$em = $this->getDoctrine()->getManager();
$query = $em->createQuery(
    'SELECT p FROM AcmeStoreBundle:Product p WHERE p.price > :price ORDER BY p.price ASC'
)->setParameter('price', '19.99');

$products = $query->getResult();

Si te manejas con soltura con SQL, verás que el nuevo lenguaje DQL es una forma muy natural de buscar información. La mayor diferencia es que tienes que pensar en términos de objetos en lugar de filas. Por esta razón, seleccionas objetos de tipo AcmeStoreBundle:Product y utilizas el alias p.

El método getResult() devuelve un array de resultados. Si la consulta devuelve un solo objeto, puedes utilizar en su lugar el método getSingleResult():

$product = $query->getSingleResult();

Advertencia El método getSingleResult() lanza una excepción de tipo Doctrine\ORM\NoResultException si no existen resultados y excepción de tipo Doctrine\ORM\NonUniqueResultException si existe más de un resultado. Así que al utilizar este método es conveniente hacerlo dentro de un bloque try-catch y asegurarte de que sólo devuelve un resultado:

$query = $em->createQuery('SELECT ...')
    ->setMaxResults(1);

try {
    $product = $query->getSingleResult();
} catch (\Doctrine\Orm\NoResultException $e) {
    $product = null;
}
// ...

La sintaxis DQL es increíblemente poderosa, permitiéndote unir fácilmente diferentes entidades (el tema de las relaciones se explica más adelante), realizar agrupaciones, etc. Para más información, consulta la documentación oficial de Doctrine Query Language.

8.2.2.  Usando el generador de consultas de Doctrine

En lugar de escribir las consultas a mano, también puedes usar el QueryBuilder de Doctrine para hacer el mismo trabajo más cómodamente con una interfaz orientada a objetos. Si usas un entorno de desarrollo avanzado, esta forma alternativa también te permite utilizar el autocompletado de código. Siguiendo los ejemplos anteriores, este es el código necesario dentro de un controlador de Symfony:

$repository = $this->getDoctrine()
    ->getRepository('AcmeStoreBundle:Product');

$query = $repository->createQueryBuilder('p')
    ->where('p.price > :price')
    ->setParameter('price', '19.99')
    ->orderBy('p.price', 'ASC')
    ->getQuery();

$products = $query->getResult();

El objeto QueryBuilder contiene todos los métodos necesarios para construir tu consulta. Al invocar el método getQuery(), el generador de consultas devuelve un objeto Query similar al que construiste a mano con la consulta de la sección anterior.

Para obtener más información sobre el generador de consultas de Doctrine, consulta la documentación del QueryBuilder de Doctrine.

8.2.3. Repositorio de clases personalizado

En las secciones anteriores, las consultas se crean y se ejecutan dentro del controlador. Sin embargo, para desacoplar el código, para poder crear tests fácilmente y para reutilizar las consultas, es mejor crear una clase propia de tipo repositorio e incluir en ella todos los métodos que necesites para realizar las consultas.

Para ello, añade en la información de mapeo de la entidad la ruta de la nueva clase de su repositorio:

// src/Acme/StoreBundle/Entity/Product.php
namespace Acme\StoreBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="Acme\StoreBundle\Entity\ProductRepository")
 */
class Product
{
    //...
}
# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml
Acme\StoreBundle\Entity\Product:
    type: entity
    repositoryClass: Acme\StoreBundle\Entity\ProductRepository
    # ...
<!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.xml -->

<!-- ... -->
<doctrine-mapping>

    <entity name="Acme\StoreBundle\Entity\Product"
            repository-class="Acme\StoreBundle\Entity\ProductRepository">
            <!-- ... -->
    </entity>
</doctrine-mapping>

Doctrine puede generar la clase de repositorio vacía ejecutando el mismo comando que utilizaste anteriormente para generar los getters y los setters:

$ php app/console doctrine:generate:entities Acme

A continuación, añade un nuevo método llamado findAllOrderedByName() a la clase del repositorio recién generado. Este método busca todas las entidades de tipo Product ordenadas alfabéticamente.

// src/Acme/StoreBundle/Entity/ProductRepository.php
namespace Acme\StoreBundle\Entity;

use Doctrine\ORM\EntityRepository;

class ProductRepository extends EntityRepository
{
    public function findAllOrderedByName()
    {
        return $this->getEntityManager()
            ->createQuery('SELECT p FROM AcmeStoreBundle:Product p ORDER BY p.name ASC')
            ->getResult();
    }
}

Truco Dentro del repositorio puedes utilizar el método $this->getEntityManager() para acceder al entity manager.

Y ahora ya puedes utilizar este nuevo método para realizar la consulta dentro de un controlador de Symfony:

$em = $this->getDoctrine()->getManager();
$products = $em->getRepository('AcmeStoreBundle:Product')
               ->findAllOrderedByName();

Nota Aunque utilices una clase repositorio propia, todavía puedes hacer uso de los métodos de búsqueda predeterminados como find() y findAll().