Castear un String a Entero con Doctrine

El problema es el siguiente, teniendo un campo String en donde se guarda un campo numerico se requeria ordenar por este, al ser String se ordenaba de una manera «incorrecta», en cierto modo es correcto pero dado el contenido no era lo esperado por el Usuario. Lo mas logico seria dejar este campo como Entero y listo, pero el problema es que la Base de Datos contiene mucha informacion y la persona que la hizo en su momento lo definio asi, por lo tanto, no nos dejaron cambiarla, asi que asumimos que seria asi.

Y bueno, buscando informacion sobre como castear un string a entero con Doctrine 2 en Symfony 2, no encontre ninguna forma directa ya que por lo visto no existe, la unica forma que logre encontrar es creando una funcion personalizada.

Guiandome sobre este post encontrado en stackoverflow realizamos lo siguiente:

1) Creamos el archivo CastAsInteger

<?php

namespace AN\PruebaBundle\Query;

use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;

class CastAsInteger extends FunctionNode
{
    public $stringPrimary;

    public function getSql(SqlWalker $sqlWalker)
    {
        return 'CAST(' . $this->stringPrimary->dispatch($sqlWalker) . ' AS UNSIGNED INTEGER)';
    }

    public function parse(Parser $parser)
    {        
        $parser->match(Lexer::T_IDENTIFIER);
        $parser->match(Lexer::T_OPEN_PARENTHESIS);

        $this->stringPrimary = $parser->StringPrimary();

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

Tiene algunas pequeñas cosas distintas con respecto al original del Post, ya que no funcionaba el de dicho Post probado en Symfony 2.3.18

 

2) Cargamos nuestra nueva función a la configuración de Doctrine

$config = $this->em->getConfiguration();
$config->addCustomNumericFunction('INT', 'AN\PruebaBundle\Query\CastAsInteger');

 

3) La forma de ocuparlo seria como se indica en dicho POST

$dql = "SELECT a, INT(a.folio) AS HIDDEN orderId
            FROM ANPruebaBundle:Aviso a
            ORDER BY orderId ASC";

$query = $em->createQuery($dql);

 

Ahora bien, si ocuparemos esto muchas veces, creo que estar cargando cada vez la configuración no es lo mas optimo, asi que crearemos un servicio, para ello haremos lo siguiente:

1) Creamos el archivo Doctrine

<?php
//Ruta donde lo creamos
namespace AN\PruebaBundle\Services;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\ORM\EntityManager;

class Doctrine
{
    protected $entityManager;
    protected $container;

    public function __construct(EntityManager $entityManager, ContainerInterface $container)
    {
        $this->container = $container;
        $this->em = $entityManager;
    }

    public function getDoctrine()
    {        
        $config = $this->em->getConfiguration();
        $config->addCustomNumericFunction('INT', 'AN\PruebaBundle\Query\CastAsInteger');
        
        return $this->em;
    }
}

 

2) Agregamos este servicio a nuestro services.yml

#Creamos Servicio para instanciar Doctrine con config adicional
an_doctrine:
    class:     "%an_doctrine.class%"
    arguments: [@doctrine.orm.entity_manager, "@service_container"]

 

3) Agregamos an_doctrine.class a nuestro parameters.yml, si no, puedes agregarlo directamente en el Paso 2

an_doctrine.class: AN\PruebaBundle\Services\Doctrine

 

4) Lo llamamos en nuestro Controller

//Doctrine
$em = $this->get('an_doctrine')->getDoctrine();
    

$dql = "SELECT a, INT(a.folio) AS HIDDEN orderId
            FROM ANWebBundle:AvisoPago a
            ORDER BY orderId ASC";

$query = $em->createQuery($dql);

Y listo, ya carga nuestra nueva funcion, asi si requerimos agregar otra funcion personalizada, la agregamos a nuestro Servicio Doctrine y este la cargara

 

OBSERVACION

Me he dado cuenta que esta función funciona perfectamente para valores enteros positivos, es decir, si tienes un string y este contiene un valor positivo, esta funcion realiza la conversion exitosamente, pero si el valor que posee es negativo, retorna un valor totalmente distinto, por lo cual NO se recomienda para estos ultimos, una solucion parche para esos casos podria ser esta:

 

(a.folio + 0)

 

Saludos