Symfony 2.4, 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 el generador de consultas de Doctrine

Imagina que quieres buscar todos aquellos productos cuyo precio sea superior a 19.99 y devolver los resultados ordenados del más barato al más caro. Esta búsqueda se puede realizar de la siguiente manera con el QueryBuilder de Doctrine:

$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 la consulta. Al llamar al método getQuery(), el query builder devuelve el objeto de tipo Query con el que realmente se ejecuta la consulta.

Truco Observa que la consulta utiliza el método setParameter(). Cuando se trabaja con Doctrine, es una buena idea utilizar parámetros para todos los valores utilizados en las condiciones (como por ejemplo :price en el ejemplo anterior). Esto evita los ataques de inyección SQL.

El método getResult() devuelve un array de resultados. Para obtener solamente un resultado, utiliza getSingleResult() (que lanza una excepción cuando no hay ningún resultado) o getOneOrNullResult():

$product = $query->getOneOrNullResult();

Consulta la documentación de QueryBuilder para obtener más información.

8.2.2. Buscando objetos con DQL

Además del QueryBuilder, Doctrine también te permite realizar consultas directamente con su lenguaje DQL:

$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.

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.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().