当我们使用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,反而会出错,所以更让我费解了