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.

sábado, 1 de marzo de 2014

Symfony 2: Añadiendo bundles externos

Esta es la tercera parte del mini-curso de Symfony 2 que empecé hace tiempo. En el último capítulo creamos nuestro primer proyecto Symfony 2. El proyecto recién creado traerá todas las dependencias necesarias para empezar a programar con el framework Symfony. Sin embargo una de las grandes ventajas de Symfony 2 es que es fácilmente ampliable mediante librerías externas programadas por terceros llamadas bundles.

Hay bundles para todos los gustos: unos añaden características nuevas a Symfony 2, otros mejoran su seguridad, otros amplían funcionalidades, etc. La forma de instalar un bundle externo es nuevamente mediante el uso de composer. Para ello hay que añadir las dependencias en el archivo composer.json que encontraremos en la raíz de nuestro proyecto Symfony de la siguiente forma:

{
    "require": {
        "vendor/package": "version",
    }
}

En este caso vendor y package forma el nombre del bundle mientras que version es el número de versión (por ejemplo: 1.2.3 o 1.2.* o >1.2.3 o >1.2.3,<1.3, etc. Para buscar bundles compatibles podemos ir a Packagist, que es el repositorio de paquetes instalables vía Composer.


En ejemplo: añadiendo Bootstrap

Vamos a verlo con un ejemplo. Supongamos que queremos añadir el framework CSS Bootstrap a nuestro proyecto Symfony. Podríamos descargarnos la librería de Bootstrap y añadirla manualmente a nuestro proyecto, pero en Symfony siempre que sea posible preferiremos hacerlo vía bundle externo. Buscando en Packagist encontramos que hay varios bundles que añaden Bootstrap a Symfony. Yo he elegido Mopa Bootstrap porque lo conozco de antes, pero además es uno de los que más descargas y mejor valoración tienen en Packagist. Visitamos la página web oficial del proyecto Mopa Bootstrap en Github y allí la sección de instalación nos indica como añadir el bundle a Symfony 2; hay que añadir el siguiente código en el archivo composer.json:

{
    "require": {
        "mopa/bootstrap-bundle": "v3.0.0-beta3",
        "twbs/bootstrap": "v3.0.0"
    }
}

Una vez hecho esto hay que ejecutar composer update desde el directorio principal del proyecto para que se actualicen las dependencias y se descarguen las librerías.

$ composer update
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing mopa/composer-bridge (v1.3.0)
    Downloading: 100%

  - Installing mopa/bootstrap-bundle (v3.0.0-beta3)
    Downloading: 100%

  - Installing twbs/bootstrap (v3.0.0)
    Downloading: 100%

Como podemos ver además de los dos bundles indicados nos ha instalado un tercero: mopa/composer-bridge (v1.3.0). Esto es porque Composer sabe que es necesario instalarlo o sino mopa/bootstrap-bundle (v3.0.0-beta3) no podrá funcionar. Ahora ya podemos empezar a usar Bootstrap en nuestro proyecto web. Pero, ¿Como se usa mopa/bootstrap en Symfony? Para saber eso hay que leerse la documentación.


Mini-curso de Symfony 2

  • Primeros pasos: Conceptos y definiciones sobre Symfony 2.
  • Primer proyecto: Como empezar un proyecto Symfony 2.
  • Añadiendo bundles externos: Agregar dependencias a composer.json.