-
Notifications
You must be signed in to change notification settings - Fork 1
自定义函数
本章节翻译自DQL User Defined Functions,并对部分内容进行了调整。
DQL解析器提供了一些钩子,用于注册函数。这些函数可以在你的DQL查询中使用,并被转换为SQL,从而增强Doctrine查询的功能。本文将解释DQL解析器的用户定义函数API(UDF),并提供一些示例,以帮助你更好地理解如何扩展DQL。
在DQL中,有三种函数类型:返回数值、返回字符串和返回日期。你的自定义方法需要注册为其中之一。返回类型信息在DQL解析器中用于检查解析过程中可能的语法错误,例如在数学表达式中使用字符串函数的返回值。
总的来说,通过这些函数的注册,你可以更灵活地定制DQL查询,使其适应不同的需求和数据库操作。
你可以通过配置文件来注册函数:
<?php
declare(strict_types=1);
return [
'default' => [
'configuration' => [
......
'functions' => [
[
'name' => 'test_substring',
'className' => SubstringFunction::class,
'type' => 'string',
],
[
'name' => 'test_rand',
'className' => RandFunction::class,
'type' => 'numeric',
],
[
'name' => 'test_now',
'className' => NowFunction::class,
'type' => 'datetime',
],
],
],
'connection' => [
......
],
],
];
在上述配置文件的 functions 部分,我们分别配置了三种类型的函数。
name
是函数在DQL查询中的引用名称。class
是一个字符串,表示一个类名,该类必须继承自Doctrine\ORM\Query\Node\FunctionNode
。这个类提供了实现用户定义函数(UDF)所需的所有API和方法。
在本篇文章中,我们将实现一些特定于MySQL的日期计算方法:
Mysql的DateDiff函数 接受两个日期作为参数,并计算日期之间的天数差date1-date2
。
DQL解析器采用自顶向下递归下降(top-down recursive descent)的方式生成抽象语法树(AST),并通过TreeWalker方法从AST生成相应的SQL。这种设计使得我们能够在有限的时间内相对轻松地阅读Parser/TreeWalker
的代码。
之前提到的FunctionNode类要求你实现两个方法,一个用于解析过程(parse
方法),另一个用于TreeWalker过程的getSql
方法。接下来,我将展示DateDiff方法的代码,并逐步讨论它:
<?php
/**
* DateDiffFunction ::= "DATEDIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
*/
class DateDiff extends FunctionNode
{
// (1)
public $firstDateExpression = null;
public $secondDateExpression = null;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(TokenType::T_IDENTIFIER); // (2)
$parser->match(TokenType::T_OPEN_PARENTHESIS); // (3)
$this->firstDateExpression = $parser->ArithmeticPrimary(); // (4)
$parser->match(TokenType::T_COMMA); // (5)
$this->secondDateExpression = $parser->ArithmeticPrimary(); // (6)
$parser->match(TokenType::T_CLOSE_PARENTHESIS); // (3)
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'DATEDIFF(' .
$this->firstDateExpression->dispatch($sqlWalker) . ', ' .
$this->secondDateExpression->dispatch($sqlWalker) .
')'; // (7)
}
}
DATEDIFF函数的解析过程会找到两个表达式,即date1和date2的值,并将它们的AST节点表示保存在DateDiff FunctionNode实例的变量中(1)。
parse()方法必须将函数调用DATEDIFF及其参数切分为片段。由于解析器使用先行符进行函数检测,函数名的T_IDENTIFIER必须从堆栈中获取(2),然后在(4)-(6)中检测参数。同时,还需检测开括号和闭括号。这些都发生在解析过程中,并最终在dql语句的AST中生成一个DateDiff FunctionNode。
调用ArithmeticPrimary
方法是因为它代表了在DQL语法中最基本、最通用的令牌。这些令牌符合我们对于DateDiff DQL函数输入的要求。选择正确的令牌对于这些方法来说可能是有挑战性的,但EBNF语法(DQL EBNF语法)提供了很好的指导,就好像查看解析器源代码也能提供一些启示。
在TreeWalker的过程中,我们需要捕获这个节点并从中生成SQL,从代码中(7)看起来这似乎相当容易。由于我们不知道第一个和第二个日期表达式的AST节点类型,因此我们只需将它们分发回SQL Walker以生成SQL,然后将DATEDIFF函数调用包装在这个输出周围。
在配置文件中注册这个 DateDiff FunctionNode:
<?php
declare(strict_types=1);
return [
'default' => [
'configuration' => [
......
'functions' => [
[
'name' => 'datediff',
'className' => DateDiff::class,
'type' => 'string',
]
],
],
'connection' => [
......
],
],
];
现在我们可以尝试执行以下DQL:
SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATEDIFF(CURRENT_TIME(), p.created) < 7