doctrine教程14---自定义查询函数

Mario Sanchez

18 people read
Thumbnail

当我们使用symfony开发项目时,默认的doctrine ORM使得我们可以轻松的使用诸如mysql、postgresql、sqlite、oracle、mssql、MariaDB等

也正是因为要支持多种数据库,所以只对通用方法做了封装,一些数据库特有的函数就不能很好的使用

比如说我们在选择数据是,可能查询条件中要求指定datetime字段中的年份, YEAR(createdAt)=2023.

如果我们使用DQL,则会按下面的方法写

public function findByCustom()
    {
        $qb = $this->createQueryBuilder('a');
        $qb->where('YEAR(a.createdAt)=2023');
        return $qb->getQuery()->getResult();
    }

当你运行时,会得到一个错误

[Syntax Error] line 0, col 42: Error: Expected known function, got 'YEAR'

当然,按官方说法,你可以写原生语句,可以把任意SQL映射到对象,当然,这是一种解决方案,但是如果能不写SQL就能直接使用,应该更方便,同时还有开源库可以使用

所以我们可以完全自定义函数来实现,当我们准备开始自定义时,这个YEAR函数就不再是数据库的函数了,我的意思是这里你可以取任意一个函数名

比如说我们定义为YEAR_EQUAL

public function findByCustom()
    {
        $qb = $this->createQueryBuilder('a');
        $qb->where('YEAR_EQUAL(a.createdAt)=2023');
        return $qb->getQuery()->getResult();
    }

然后我们去config/packages/doctrine.yaml配置

doctrine:
  orm:
    dql:
      datetime_functions:
        YEAR_EQUAL: App\Dql\YearEqualFunction

在定义时,有string_functions,numeric_functions,datetime_functions三种类型,根据实际情况配置即可

接下来我们去实现这个方法

#App\Dql\YearEqualFunction.php
<?php

namespace App\Dql;

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

/**
 * YEAR_EQUAL ::= "YEAR_EQUAL" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
 */
class YearEqualFunction extends FunctionNode
{
    private $firstExpression = null;
    private $secondExpression = null;

    public function getSql(SqlWalker $sqlWalker)
    {
        return sprintf('YEAR(%s)',$this->firstExpression->dispatch($sqlWalker));
    }

    public function parse(Parser $parser)
    {
        $parser->match(Lexer::T_IDENTIFIER); // 匹配函数名
        $parser->match(Lexer::T_OPEN_PARENTHESIS); // 匹配左括号
        $this->firstExpression = $parser->ArithmeticPrimary();//匹配第一个参数
        //$parser->match(Lexer::T_COMMA); // 匹配逗号
        //$this->secondExpression = $parser->ArithmeticPrimary();//匹配第二个参数
        $parser->match(Lexer::T_CLOSE_PARENTHESIS); // 匹配右括号
    }
}

如上面代码所示,在parse方法中,我们需要匹配第一个参数,实际上在这一句查询中我们只有一个参数,如果你有特殊需求 你也可以传无限个参数,只要按格式匹配出来即可

然后我们在getSql方法中返回了正确的查询条件,相当于把

YEAR_EQUAL(a.createdAt)

替换成了

YEAR(a.createdAt)

现在报错信息也没有了,返回了正确的对象信息

注意

在实际开发过程中,如果需要自定义函数,在DQL中必须写=x。比如说我们在json中查找时,mysql可以这样写:

SELECT * FROM employee WHERE JSON_CONTAINS(roles, '"ROLE_ADMIN"')

那么DQL中必须有=号操作

public function findByCustom()
    {
        $qb = $this->createQueryBuilder('a');
        $qb->where('JSON_CONTAINS(roles, "ROLE_ADMIN")=1');
        return $qb->getQuery()->getResult();
    }

然后再去写自定义函数,这里因为我没有深入去看源码,所以不知道为什么一定要这样写。如果你愿意,也可以告诉我。

更新

昨天在写DATEDIFF函数时发现如果在DATEDIFF(a,b)后加上=1,反而会出错,所以更让我费解了

About Me

我是一位精通 Symfony 框架和 API Platform 的开发者,擅长构建高效、可扩展的 Web 应用程序和 API。 此外,我还具备 PrestaShop 模块开发经验,能够为您的电商平台定制功能,满足特定业务需求。