viernes, 28 de marzo de 2014

Symfony2 avanzado: Usando un objeto Query para crear un QueryBuilder

Este caso es un poco difícil de explicar. Vamos a suponer lo siguiente: Tenemos una entidad Factura y un repositorio FacturaRepository que tiene una función findFacturas(). Dicha función recibe un parámetro obligatorio $usuario de clase Usuario y otro opcional $filtros que por defecto es un array vacío. La mencionada función, que está perfectamente testeada y no desea cambiarse, prepara dinámicamente una query DQL teniendo en cuenta los permisos del usuario y un montón de filtros y devuelve un objeto de clase Query que al ejecutarla devuelve la lista de facturas del usuario.

namespace Company\ContabilidadBundle\Entity;

class Factura {
  //...
}

class FacturaRepository {
  public function findFacturas(Usuario $usuario, $filtros = array( )) {
    //...
    return $query;
  }
}

Por otro lado, en otra parte de la aplicación, tenemos un formulario en el que necesitamos poder seleccionar una de las facturas del usuario mediante un cuadro de selección (combobox). Para ello lo más lógico es añadir al formulario un campo de tipo entity. El problema es que que no podemos aprovechar la query que tenemos en nuestro repositorio porque los campos entity se cargan con un QueryBuilder.

Hoy me he encontrado con este mismo problema. Quedaba totalmente descartado Convertir en un QueryBuilder una query DQL de dos palmos y medio de largo, perfectamente testeada, por su complejidad (y porque no me daba la gana), así que me he puesto a investigar si había alguna forma de convertir o cargar un objeto Query en un objeto QueryBuilder. No ha sido fácil pero lo he conseguido...

Este es el resultado:

namespace Company\ContabilidadBundle\Form\Type;

class FormularioFormType extends AbstractType {

  //...

  public function buildForm(FormBuilderInterface $builder, array $options) {

    //...

    $builder->add(
      'factura',
      'entity',
      array(
        'class' => 'CompanyContabilidadBundle:Factura',
        'query_builder' => function(EntityRepository $repository) use($usuario) {

          $query = $repository->findFacturas($usuario);
          $qb = $repository->createQueryBuilder('f');
          $qb->where($qb->expr()->in('f', $query->getDql()));
          $qb->setParameters($query->getParameters());
          return $qb;
        }
    ));
  }
}

Veámoslo paso a paso:

  1. En primer lugar obtenemos el objeto Query del repositorio. Dicho objeto estará listo para ser ejecutado devolviendo el resultado que deseamos.
  2. En segundo lugar extraemos la sentencia DQL y los parámetros del objeto Query con las instrucciones $query->getDql() y $query->getParameters().
  3. En último lugar creamos un nuevo objeto QueryBuilder y simulamos una subquery como esta: SELECT * FROM Facturas f WHERE f IN (...), poniendo la sentencia DQL original en el lugar de los puntos suspensivos.

Es rebuscado pero funciona perfectamente.

No hay comentarios:

Publicar un comentario