Symfony7.3+Doctrine3.5多实体管理器下自定义函数编译失败
我自定义了BizDaysInclusive MySQL函数用于计算两个日期之间的工作日天数,按照官方文档在config/packages/doctrine.yaml中与多实体管理器一同声明。首次使用正常,但Symfony编译系统缓存后出现错误提示:
Attempted to load class 'BizDaysInclusive' from namespace 'App\Query\MySql'. Did you forget a 'use' statement for 'App\Query\Mysql\BizDaysInclusive'?
该提示并不准确,首次使用时类引用有效。问题仅在开发环境缓存编译后出现,生产环境始终正常;移除额外实体管理器声明后,自定义函数可正常工作。通过缓存检查发现,Symfony容器将App\Query\Mysql\BizDaysInclusive标记为未使用服务并移除。
测试案例
配置文件 (config/packages/doctrine.yaml)
orm: default_entity_manager: default entity_managers: default: connection: default dql: string_functions: REPLACE: DoctrineExtensions\Query\Mysql\Replace MONTH: DoctrineExtensions\Query\Mysql\Month YEAR: DoctrineExtensions\Query\Mysql\Year NOW: DoctrineExtensions\Query\Mysql\Now DATE: DoctrineExtensions\Query\Mysql\Date DAY: DoctrineExtensions\Query\Mysql\Day WEEKOFYEAR: DoctrineExtensions\Query\Mysql\WeekOfYear GROUP_CONCAT: DoctrineExtensions\Query\Mysql\GroupConcat CAST: DoctrineExtensions\Query\Mysql\Cast FLOOR: DoctrineExtensions\Query\Mysql\Floor FIRST: App\Doctrine\FirstFunction IF: DoctrineExtensions\Query\Mysql\IfElse CONCAT: Doctrine\ORM\Query\AST\Functions\ConcatFunction ROUND: DoctrineExtensions\Query\Mysql\Round JSON_EXTRACT: Scienta\DoctrineJsonFunctions\Query\AST\Functions\Mysql\JsonExtract JSON_SEARCH: Scienta\DoctrineJsonFunctions\Query\AST\Functions\Mysql\JsonSearch JSON_UNQUOTE: Scienta\DoctrineJsonFunctions\Query\AST\Functions\Mysql\JsonUnquote numeric_functions: BIZDAYSINCLUSIVE: App\Query\MySql\BizDaysInclusive FERIADOSENTREFECHAS: App\Query\MySql\FeriadosEntreFechas ANIOS_MESES: App\Query\MySql\AniosMeses mappings: Main: type: attribute is_bundle: false dir: '%kernel.project_dir%/src/Entity/Main' prefix: App\Entity\Main alias: Main meta4: connection: meta4 mappings: Meta4: type: attribute is_bundle: false dir: '%kernel.project_dir%/src/Entity/Meta4' prefix: App\Entity\Meta4 alias: Meta4 controller_resolver: auto_mapping: false
自定义函数类 (src/Query/Mysql/BizDaysInclusive.php)
<?php namespace App\Query\Mysql; use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\TokenType; class BizDaysInclusive extends FunctionNode { public $firstDateExpression = null; public $secondDateExpression = null; public function parse(Parser $parser): void { $parser->match(TokenType::T_IDENTIFIER); $parser->match(TokenType::T_OPEN_PARENTHESIS); $this->firstDateExpression = $parser->ArithmeticPrimary(); $parser->match(TokenType::T_COMMA); $this->secondDateExpression = $parser->ArithmeticPrimary(); $parser->match(TokenType::T_CLOSE_PARENTHESIS); } public function getSql(SqlWalker $sqlWalker): string { return 'BizDaysInclusive(' . $this->firstDateExpression->dispatch($sqlWalker) . ', ' . $this->secondDateExpression->dispatch($sqlWalker) . ')'; } }
缓存检查日志
在var/目录执行grep命令后,得到Symfony编译日志:
Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass:
Removed service "App\Query\Mysql\BizDaysInclusive"; reason: unused
解决方案
原因
Symfony的RemoveUnusedDefinitionsPass编译流程会自动移除未被容器识别为有依赖关系的服务。在多实体管理器场景下,Doctrine的DQL函数注册逻辑未被Symfony容器正确识别为对自定义函数类的依赖,导致开发环境缓存编译时被误判为“未使用”而移除。开发环境的debug模式会更激进地执行这个优化,而生产环境缓存策略不同,因此不会出现该问题。
解决方法
1. 用服务标签显式关联Doctrine实体管理器(推荐)
在config/services.yaml中为自定义函数添加Doctrine DQL函数标签,让容器明确识别其依赖关系:
services: App\Query\Mysql\BizDaysInclusive: tags: - { name: doctrine.dql_function, entity_manager: default, type: numeric, alias: BIZDAYSINCLUSIVE } App\Query\Mysql\FeriadosEntreFechas: tags: - { name: doctrine.dql_function, entity_manager: default, type: numeric, alias: FERIADOSENTREFECHAS } App\Query\Mysql\AniosMeses: tags: - { name: doctrine.dql_function, entity_manager: default, type: numeric, alias: ANIOS_MESES }
添加标签后,可以移除doctrine.yaml中dql.numeric_functions下对应的手动配置,由标签自动注册。
2. 标记服务为公共服务
如果不想用标签,也可以直接在config/services.yaml中将自定义函数类标记为公共服务,避免被移除:
services: App\Query\Mysql\BizDaysInclusive: public: true App\Query\Mysql\FeriadosEntreFechas: public: true App\Query\Mysql\AniosMeses: public: true
3. 关闭开发环境的未用服务移除(不推荐)
在config/packages/dev/framework.yaml中关闭未用服务移除功能,会影响整体缓存优化,仅作为临时方案:
framework: dependency_injection: unused_definitions: false
内容的提问来源于stack exchange,提问作者xpotronix

