Symfony2: Crear funciones DQL propias para Doctrine

miércoles, 28 de noviembre de 2012 Etiquetas: , ,


Introducción

Cuando trabajamos con Doctrine, llega un momento que usando el lenguaje de consultas DQL, nos surge la necesidad de usar alguna función que no esta implementada, y que estamos acostumbrados a utilizar. Un caso común puede ser las funciones YEAR, MONTH o DAY de MySQL, para poder trabajar de forma sencilla con las fechas.

Si probamos a utilizar algunas de estas funciones en una sentencia DQL, comprobaremos que nos produce un error: la función no existe. Vamos a ver en este post como podemos crear nuestras propias funciones y configurar Symfony2 para poderlas utilizar en nuestras sentencias DQL. Si se sabe como, es sencillo y nos puede facilitar el trabajo.

Creación de la Función

Primero generamos la función PHP que tratara la llamada. Para ello nos basamos en la documentación que nos proporciona Doctrine[1], donde podemos ver un ejemplo de generación de una función. Basándonos en estos ejemplos creamos nuestro código:

// ******************************************************************
// Acme\SampleBundle\DQL\YearFunction.php
// ****************************************************************** 
class YearFunction extends FunctionNode
{
    public $fecha;

    public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
    {
        return "YEAR(" . $this->fecha->dispatch($sqlWalker) . ")";
    }

    public function parse(\Doctrine\ORM\Query\Parser $parser)
    {
        $lexer = $parser->getLexer();

        $parser->match(Lexer::T_IDENTIFIER);       //Nombre de la Funcion
        $parser->match(Lexer::T_OPEN_PARENTHESIS); //Parantesis abierto

        $this->fecha = $parser->ArithmeticPrimary(); //Dato

        $parser->match(Lexer::T_CLOSE_PARENTHESIS); //Parentesis Cerrado
    }
}

Como podemos ver creamos una clase que extiende de la clase FunctionNode. Debemos crear dos funciones, la primera Parse que indica como se debe parsear la función para obtener los datos que nos interesan. En este caso nos interesa la fecha que la guardamos en una variable para utilizarla mas tarde.

La segunda función que creamos es getSql que se encarga de devolver la cadena que se utilizar en la SQL. En este caso compone la llamada a la función YEAR de MySQL, pasandole la fecha que habíamos leído anteriormente.

Configuración Symfony2

Ya tenemos definida nuestra función que obtiene el año, pero si lo utiliamos en una sentencia DQL nos sigue dando error, eso que que todavía nos hemos configurado Symfony para que lo tenga en cuenta. Vamos a configurarlo, para ello nos basamos en la documentación de Smyfony [2].

#********************************************
# /app/config/config.yml
#********************************************

# Doctrine Configuration
doctrine:
    dbal:
     #.......
    orm:
        auto_generate_proxy_classes: %kernel.debug%
        #~ auto_mapping: true
        entity_managers:
            default:
                mappings: 
                    AcmeSampleBundle: ~
                dql:
                    numeric_functions:
                        year: Acme\SampleBundle\DQL\YearFunction

Hay que añadir esta configuración en el fichero config.yml. Notad que hay que elimiar la opción de auto_mapping ya que no es compatible con la definición de funciones propias. Si no lo quitas te producirá un error. Tambíen hay que añadir en la sección mappings los Bundles que tengamos definidos.

Ahora ya podemos ir a nuestro Repository y utilizar la función que acabamos de crear:

// /Acme/SampleBundle/Repository/InvoiceRepository.php

 return $this->getEntityManager()
  ->createQuery("SELECT i, c
  FROM AcmeSampleBundle:Invoice i 
  JOIN i.costumer c
  WHERE YEAR(i.date) = :y")
  ->setParameter("y",$year)
  ->getResult();

Bibliografia

[1] Doctrine Documentation. DQL User Defined Functions (inglés)
[2] Symfony2 Documentation. How to Register Custom DQL Functions (inglés)

0 comentarios:

Publicar un comentario