Symfony2: Doctrine で MySQL の FLOOR, ROUND などの数学関数を使用できるようにする


当然のように `FLOOR` 関数が使えると思ってた。
   
    $qb = $this->getDoctrine()
               ->getRepository('MyBundle:Test')
               ->createQueryBuilder('test')
               ->select('COUNT(test.id) AS rows')
               ->addSelect('FLOOR(test.hoge / test.fuga) AS rate');
       ~~
       ~~
       ~~
    $result = $qb->getQuery()->getResult();


Symfony2 で上記のような DQL を書いて実行したときに、こんなエラーがでた。

    QueryException: [Syntax Error] line {LINE}, col {COL}: Error: Expected known function, got 'FLOOR'
    in /path/to/vendor/doctrine/orm/lib/Doctrine/ORM/Query/QueryException.php at line 44


「え！デフォルトで FLOOR 使えないの！？」と思ってしまった。  
`FLOOR` 以外にも `ROUND` も `CEIL`, `TRUNCATE` ... `ABS` なども使えない。（[MySQL 数学関数](http://dev.mysql.com/doc/refman/5.6/en/mathematical-functions.html)）

それではどうしたら使えるようになるのか。  
ここ（[14.5.5. Adding your own functions to the DQL language](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#adding-your-own-functions-to-the-dql-language)）に答えが載ってた。

`FLOOR` が使いたい場合、以下のようなコードをバンドル以下に配置する。  
（各バンドルで作るよりかは、Doctrine 拡張用の汎用バンドルのほうが便利かなー、と思った。）


    // MyBundle/Query/AST/MysqlFloor.php
    <?php

    namespace MyBundle\Query\AST;
    
    use \Doctrine\ORM\Query\AST\Functions\FunctionNode;
    use \Doctrine\ORM\Query\Lexer;
    
    class MysqlFloor extends FunctionNode
    {
        public $simpleArithmeticExpression;
    
        public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
        {
            return 'FLOOR(' . $sqlWalker->walkSimpleArithmeticExpression(
                $this->simpleArithmeticExpression
            ) . ')';
        }
    
        public function parse(\Doctrine\ORM\Query\Parser $parser)
        {
            $lexer = $parser->getLexer();
    
            $parser->match(Lexer::T_IDENTIFIER);
            $parser->match(Lexer::T_OPEN_PARENTHESIS);
    
            $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression();
    
            $parser->match(Lexer::T_CLOSE_PARENTHESIS);
        }
    }


次に `FLOOR` 関数を Doctrine に教えるわけだが、[このやり方](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#adding-your-own-functions-to-the-dql-language)よりも設定ファイルで教えたかったので、`config.yml` を使うやり方にする。  
`config.yml` で設定する場合、以下が現状の `doctrine` 関係の設定箇所だとすると、

    // app/config/config.yml
    # Doctrine Configuration
    doctrine:
        dbal:
            ~~~~
            ~~~~
        orm:
            auto_generate_proxy_classes: %kernel.debug%
            auto_mapping: true


`doctrine.orm` を以下のように変更する必要がある。

    // app/config/config.yml
    # Doctrine Configuration
    doctrine:
        dbal:
            ~~~~
            ~~~~
        orm:
            auto_generate_proxy_classes: %kernel.debug%
            auto_mapping: true
            entity_managers:
                default:
                    dql:
                        numeric_functions:
                            FLOOR: MyBundle\Query\AST\MysqlFloor


しかしながら、これではまたもやエラーが発生してしまう。

    InvalidConfigurationException: Unrecognized options "auto_mapping" under "doctrine.orm"
    in /path/to/vendor/symfony/symfony/src/Symfony/Component/Config/Definition/ArrayNode.php line 278


`doctrine.orm.auto_mapping` を `doctrine.orm.entity_managers.default` に配置しなければいけないらしい。

    // app/config/config.yml
    # Doctrine Configuration
    doctrine:
        dbal:
            ~~~~
            ~~~~
        orm:
            auto_generate_proxy_classes: %kernel.debug%
            entity_managers:
                default:
                    auto_mapping: true
                    dql:
                        numeric_functions:
                            FLOOR: MyBundle\Query\AST\MysqlFloor


これで `FLOOR` 関数が使える。

もし数学関数以外で有効になっていない関数などがあるならば、これまでと同じやり方で有効にできる。  
[文字列関数](http://dev.mysql.com/doc/refman/5.6/en/string-functions.html)なら `numeric_functions` を `string_functions` にしてあげれば ok。


### 仲間募集

[Creema（クリーマ）](https://www.creema.jp)では、一緒にサイト改善や業務改善をおこなってくれる Web エンジニアを募集しています。
Creema のエンジニアとして働きたい！という方は以下のリンクからご応募ください！
皆さまからのご応募、お待ちしております！

[Web エンジニア採用ページ](https://www.creema.co.jp/job.html?_ref-x=j6Z7#web_engineer)


### 参考記事

- [doctrine2/lib/Doctrine/ORM/Query/Parser.php at master - doctrine/doctrine2 - GitHub](https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/Query/Parser.php)
- [InvalidConfigurationException: Unrecognized options "auto\_mapping" under "doctrine.orm" - Issue #141 - stof/StofDoctrineExtensionsBundle](https://github.com/stof/StofDoctrineExtensionsBundle/issues/141)
- [How use DoctrineExtensions in Symfony2? - Issue #70 - beberlei/DoctrineExtensions](https://github.com/beberlei/DoctrineExtensions/issues/70)
- [14. Doctrine Query Language - Doctrine 2 ORM 2.2 documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html)
- [MySQL :: MySQL 5.6 Reference Manual :: 12.6.2 Mathematical Functions](http://dev.mysql.com/doc/refman/5.6/en/mathematical-functions.html)


以上。 - [元記事](http://tetsuwo.tumblr.com/post/43318912735/symfony2-doctrine-mysql-math-functions)