- PVSM.RU - https://www.pvsm.ru -

Полная поддержка GROUP_CONCAT в Doctrine2

Привет всем.

Так сложилось, что в проекте, над которым я сейчас работаю, потребовалось использовать функцию GROUP_CONCAT(). К сожалению, Doctrine2 «из коробки» эту функцию не поддерживает. Имеющееся же расширение [1] от одного из разработчиков Doctrine2 (Benjamin Eberlei) значится как «limited support for GROUP_CONCAT». Понимаю, что использование данной функции автоматом ставит проект в зависимость от MySQL, но менять СУБД как перчатки не планируется. Так что оставим этот вопрос за рамками поста.

Поскольку погуглив я не нашел готового решения, решил написать его сам (взяв за основу разработку Benjamin'а). Комментировать там особо нечего, поэтому просто представляю его на суд общественности:

/**
 * DoctrineExtensions Mysql Function Pack
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to kontakt@beberlei.de so I can send you a copy immediately.
 */

namespace DoctrineExtensionsQueryMysql;

use DoctrineORMQueryASTFunctionsFunctionNode,
    DoctrineORMQueryLexer;

/**
 * Full support for:
 * 
 * GROUP_CONCAT([DISTINCT] expr [,expr ...]
 *              [ORDER BY {unsigned_integer | col_name | expr}
 *                  [ASC | DESC] [,col_name ...]]
 *              [SEPARATOR str_val])
 * 
 */
class GroupConcat extends FunctionNode
{
    public $isDistinct = false;
    public $pathExp = null;
    public $separator = null;
    public $orderBy = null;

    public function parse(DoctrineORMQueryParser $parser)
    {
        $parser->match(Lexer::T_IDENTIFIER);
        $parser->match(Lexer::T_OPEN_PARENTHESIS);
        
        $lexer = $parser->getLexer();
        if ($lexer->isNextToken(Lexer::T_DISTINCT)) {
            $parser->match(Lexer::T_DISTINCT);
            
            $this->isDistinct = true;
        }

        // first Path Expression is mandatory
        $this->pathExp = array();
        $this->pathExp[] = $parser->SingleValuedPathExpression();

        while ($lexer->isNextToken(Lexer::T_COMMA)) {
            $parser->match(Lexer::T_COMMA);
            $this->pathExp[] = $parser->StringPrimary();
        }

        if ($lexer->isNextToken(Lexer::T_ORDER)) {
            $this->orderBy = $parser->OrderByClause();
        }
        
        if ($lexer->isNextToken(Lexer::T_IDENTIFIER)) {
            if (strtolower($lexer->lookahead['value']) !== 'separator') {
                $parser->syntaxError('separator');
            }
            $parser->match(Lexer::T_IDENTIFIER);
        
            $this->separator = $parser->StringPrimary();
        }

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

    public function getSql(DoctrineORMQuerySqlWalker $sqlWalker)
    {
        $result = 'GROUP_CONCAT(' . ($this->isDistinct ? 'DISTINCT ' : '');

        $fields = array();
        foreach ($this->pathExp as $pathExp) {
            $fields[] = $pathExp->dispatch($sqlWalker);
        }
        
        $result .= sprintf('%s', implode(', ', $fields));
        
        if ($this->orderBy) {
            $result .= ' '.$sqlWalker->walkOrderByClause($this->orderBy);
        }
        
        if ($this->separator) {
            $result .= ' SEPARATOR '.$sqlWalker->walkStringPrimary($this->separator);
        }
        
        $result .= ')';
        
        return $result;
    }

}

Пример использования:

$query = $this->createQueryBuilder('c')
        ->select("
            c as company,
            GroupConcat(b.id, ';', b.headOffice, ';', b.city, ';', s.name
            ORDER by b.id
            SEPARATOR '|') AS branches
        ")->leftJoin('c.branches','b')
        ->leftJoin('b.country','s')
        ->groupBy('c.id')
        ->setFirstResult(0)
        ->setMaxResults(10)
        ->getQuery()
;
$result = $query->getResult();

Официальная документация по теме:
Регистрация пользовательских DQL функция в Doctrine2 [2].
Как подключить в пользовательские функции DQL в Symfony2 [3].
Описание MySQL-функции GROUP_CONCAT [4].

Автор: tendium

Источник [5]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/mysql/35650

Ссылки в тексте:

[1] Имеющееся же расширение: https://github.com/beberlei/DoctrineExtensions/blob/master/lib/DoctrineExtensions/Query/Mysql/GroupConcat.php

[2] Регистрация пользовательских DQL функция в Doctrine2: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/dql-user-defined-functions.html

[3] Как подключить в пользовательские функции DQL в Symfony2: http://symfony.com/doc/current/cookbook/doctrine/custom_dql_functions.html

[4] Описание MySQL-функции GROUP_CONCAT: http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat

[5] Источник: http://habrahabr.ru/post/181666/