Nomisoft
Menu

Doctrine Inet_Aton

15th November 2016

Imagine we have a table in our database (and a Doctrine model), like the below, that holds a list of IP address ranges and their associated countries. We want to find the country that corresponds to the IP address 1.0.0.12

IP Start IP End Country
0.0.0.0 0.255.255.255 US
1.0.0.0 1.0.0.255 AU
1.0.1.0 1.0.3.255 CN

In order to do this we want to effectively search for a row where our chosen IP is between the IP Start and IP End figures. Because these values are not numeric it's not as straight forward as simply performing a greater than or less than query. We can however use the INET_ATON functionality built into MySQL to return integers that represents our IP addresses. Our example IP of 1.0.0.12 would be represented as 16777228 (1×256³ + 0×256² + 0×256 + 12)

Doctrine doesn't support INET_ATON out of the box due to only supporting SQL functions that are common across all vendors. The Inet Aton function is specific to MySQL so we have to register a custom DQL extension to allow this functionality. First we create our class which tells Doctrine how we're going to use this function.


# src\AppBundle\Extension\Doctrine\InetAton.php

namespace AppBundle\Extension\Doctrine;

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

class InetAton extends FunctionNode
{

    public $valueExpression = null;

    /**
     * parse
     *
     * @param \Doctrine\ORM\Query\Parser $parser
     * @access public
     * @return void
     */
    public function parse(\Doctrine\ORM\Query\Parser $parser)
    {
        $parser->match(Lexer::T_IDENTIFIER);
        $parser->match(Lexer::T_OPEN_PARENTHESIS);
        $this->valueExpression = $parser->StringPrimary();
        $parser->match(Lexer::T_CLOSE_PARENTHESIS);
    }

    /**
     * getSql
     *
     * @param \Doctrine\ORM\Query\SqlWalker $sqlWalker
     * @access public
     * @return string
     */
    public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
    {
        return 'INET_ATON('. $this->valueExpression->dispatch($sqlWalker) . ')';
    }
} 

Next we need to instruct Symfony and Doctrine to load our custom functionality by adding the following to our config.yml file


# app/config/config.yml
doctrine:
    orm:
        dql:
            string_functions:
                INET_ATON: AppBundle\Extension\Doctrine\InetAton

We can now use our INET_ATON function directly in our Doctrine queries like:


$qb = $this->getEntityManager()
    ->createQuery("SELECT i.country FROM AppBundle:IpCountries i WHERE INET_ATON(:ip) >= INET_ATON(i.ipStart) AND INET_ATON(:ip) <= INET_ATON(i.ipEnd)")
    ->setParameter('ip', '10.0.0.12');