Symfony 2 et Doctrine: comment ajouter les fonctions spécifiques MySQL

Imaginons que vous fassiez un Repository dans lequel vous essayez d’éxecuter une fonction ainsi :

    <?php
    public function findNearest(...) {
        return $this->createQueryBuilder('p')
            ->select(array(
                'p.id',
                'COS(5) as distance'
            ))
            ->having('distance>:dmin')
            ->addOrderBy('distance', 'DESC')
            ->setParameters(array(
                'dmin' => $distance_min,
            ));
    }

Alors vous verrez que Doctrine ne connait pas la fonction cosinus COS().
Vous aurez l’erreur :

[Syntax Error] line 0, col 27: Error: Expected known function, got 'COS'

Doctrine ne connait pas la fonction cosinus COS(), mais aussi plein d’autres fonctions.

Pour les avoir disponibles, il faut aller les chercher sur github, ici.

Ici, vous pourrez récupérer toutes ces fonctions : DateAdd(), Sha1(), NullIf(), Field(), Month(), Sin(), MatchAgainst(), Acos(), Day(), CountIf(), GroupConcat(), Radians(), TimestampDiff(), ConcatWs(), Cot(), IfElse(), FindInSet(), IfNull(), Cos(), Asin(), Md5(), Year(), Sha2(), Week(), CharLength(), DateDiff(), StrToDate(), Atan(), Tan(), Atan2() et Degrees()

Ensuite, comme je ne voulais pas tous les fichiers, mais que les extensions, je les ai toutes mises dans mon répertoire dédié : src/HQF/Bundle/PizzasBundle/DQL/MySQL/.
Puis j’ai modifié à la main tous les fichiers en les déclarant dans le namespace adéquat (namespace HQF\Bundle\PizzasBundle\DQL\MySQL;).

Une fois cela fait, j’ai ajouté les déclarations de ces fonctions dans le fichier de config général app/config/config.yml. La petite astuce très importante et qui m’a fait perdre un temps fou (et c’est pour ça que je fait cet article en fait), qui n’est pas très claire dans la documentation : le nom qu’on déclare doit correspondre au texte qu’on trouve dans la fonction. Donc, dans le fichier, on a [nom de la fonction]:[classe] :

J’essayais de déclarer mafonction_acos: HQF\Bundle\PizzasBundle\DQL\MySQL\Acos et ça ne fonctionnait pas. Il fallait donner le nom de la fonction : acos: HQF\Bundle\PizzasBundle\DQL\MySQL\Acos

doctrine:
    ...blabla...
    orm:
        default_entity_manager: default
        entity_managers:
            default:
                connection: default
                ...blabla...
                dql:
                    numeric_functions:
                        acos: HQF\Bundle\PizzasBundle\DQL\MySQL\Acos
                        asin: HQF\Bundle\PizzasBundle\DQL\MySQL\Asin
                        atan2: HQF\Bundle\PizzasBundle\DQL\MySQL\Atan2
                        atan: HQF\Bundle\PizzasBundle\DQL\MySQL\Atan
                        charlength: HQF\Bundle\PizzasBundle\DQL\MySQL\CharLength
                        concatws: HQF\Bundle\PizzasBundle\DQL\MySQL\ConcatWs
                        cos: HQF\Bundle\PizzasBundle\DQL\MySQL\Cos
                        cot: HQF\Bundle\PizzasBundle\DQL\MySQL\Cot
                        countif: HQF\Bundle\PizzasBundle\DQL\MySQL\CountIf
                        dateadd: HQF\Bundle\PizzasBundle\DQL\MySQL\DateAdd
                        datediff: HQF\Bundle\PizzasBundle\DQL\MySQL\DateDiff
                        day: HQF\Bundle\PizzasBundle\DQL\MySQL\Day
                        degrees: HQF\Bundle\PizzasBundle\DQL\MySQL\Degrees
                        field: HQF\Bundle\PizzasBundle\DQL\MySQL\Field
                        findinset: HQF\Bundle\PizzasBundle\DQL\MySQL\FindInSet
                        groupconcat: HQF\Bundle\PizzasBundle\DQL\MySQL\GroupConcat
                        ifelse: HQF\Bundle\PizzasBundle\DQL\MySQL\IfElse
                        ifnull: HQF\Bundle\PizzasBundle\DQL\MySQL\IfNull
                        matchagainst: HQF\Bundle\PizzasBundle\DQL\MySQL\MatchAgainst
                        md5: HQF\Bundle\PizzasBundle\DQL\MySQL\Md5
                        month: HQF\Bundle\PizzasBundle\DQL\MySQL\Month
                        nullif: HQF\Bundle\PizzasBundle\DQL\MySQL\NullIf
                        radians: HQF\Bundle\PizzasBundle\DQL\MySQL\Radians
                        sha1: HQF\Bundle\PizzasBundle\DQL\MySQL\Sha1
                        sha2: HQF\Bundle\PizzasBundle\DQL\MySQL\Sha2
                        sin: HQF\Bundle\PizzasBundle\DQL\MySQL\Sin
                        strtodate: HQF\Bundle\PizzasBundle\DQL\MySQL\StrToDate
                        tan: HQF\Bundle\PizzasBundle\DQL\MySQL\Tan
                        timestampdiff: HQF\Bundle\PizzasBundle\DQL\MySQL\TimestampDiff
                        week: HQF\Bundle\PizzasBundle\DQL\MySQL\Week
                        year: HQF\Bundle\PizzasBundle\DQL\MySQL\Year

6 comments

  1. Marcus

    http://www.olivierpons.fr/mot-cle/cours/
    Bonjour,

    Il y a d autres déclarations à faire car j’ai ce message d’erreur : FatalErrorException: Error: Class 'Tandem\AdminBundle\DQL\ConcatWs' not found in /Applications/MAMP/htdocs/tandem/vendor/doctrine/orm/lib/Doctrine/ORM/Query/Parser.php line 3127

        dql:
            string_functions:
                ConcatWs: Tandem\AdminBundle\DQL\ConcatWs

    • Zertz

      La meilleure façon de corriger ce problème est d’installer DoctrineExtensions par composer en y ajoutant:

      "beberlei/DoctrineExtensions": "dev-master"

      Il faut ensuite ajouter ces deux lignes dans autoload.php:

      $classLoader = new \Doctrine\Common\ClassLoader('DoctrineExtensions', __DIR__.'/../vendor/beberlei-doctrine-extensions');
      $classLoader->register();

      De plus, dépendamment de votre configuration, il est possible que vous deviez placer la clé « dql » directement sous « orm » et non sous « orm entity_managers default« 

  2. Ping : Symfony 2 : avantages et inconvénients | Olivier Pons
  3. Arnaud

    Au lieu de passer par l’autoload, vous pouvez tout simplement déclarer ses nouvelles fonctions dans le fichier de config :
    app/config/config.yml
    […]
    orm:
    auto_generate_proxy_classes: « %kernel.debug% »
    auto_mapping: true
    dql:
    string_functions:
    ACOS: DoctrineExtensions\Query\Mysql\Acos
    COS: DoctrineExtensions\Query\Mysql\Cos
    RADIANS: DoctrineExtensions\Query\Mysql\Radians
    SIN: DoctrineExtensions\Query\Mysql\Sin

  4. KENZGATES

    salut! J’ai appliqué à la lettre ce que tu as expliqué dans tcet article, cependant j’ai cette erreur qui me revient:
    Error: Call to a member function format() on a non-object in C:\wamp\www\djago_i\vendor\doctrine\dbal\lib\Doctrine\DBAL\Types\TimeType.php line 53

    Peux-tu me guider un peu stp?! Merci!

Poster un commentaire

Vous devriez utiliser le HTML:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>