如何在Symfony编译过程中自动加载Doctrine实体映射?
问题描述
我尝试基于目录结构自动加载实体映射,自定义加载器返回如下格式的数组,目标是在应用初始化时找到所有“capabilities”,并将其domain目录映射到Doctrine ORM实体映射。
[ "UploadsDomain" => [ "alias" => "SomeNameDomain", "dir" => "/home/someName/projects/video-service/src/Capability/SomeName/Domain", "is_bundle" => false, "prefix" => "App\Capability\SomeName\Domain", "type" => "attribute", ] ]
编译过程代码(已在Kernel.php中通过$container->addCompilerPass(new DoctrineMappingPass());调用):
namespace App\DependencyInjection\Compiler; use App\Doctrine\CapabilityMappingLoader; use Doctrine\ORM\Mapping\Driver\AttributeDriver; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; class DoctrineMappingPass implements CompilerPassInterface { public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('doctrine.orm.default_configuration')) { return; } $definition = $container->getDefinition('doctrine.orm.default_configuration'); // Instantiate the mapping loader $loader = new CapabilityMappingLoader(); $mappings = $loader->loadMappings(); foreach ($mappings as $alias => $mapping) { $definition->addMethodCall('addEntityNamespace', [$alias, $mapping['prefix']]); $definition->addMethodCall('setMetadataDriverImpl', [ new AttributeDriver([$mapping['dir']]), ]); } } }
出现错误:
Symfony\Component\DependencyInjection\Exception\RuntimeException: Unable to dump a service container if a parameter is an object or a resource, got "Doctrine\ORM\Mapping\Driver\AttributeDriver".
解决方案
错误核心是编译阶段直接实例化AttributeDriver对象,Symfony服务容器无法序列化对象类型参数,不能直接把实例传给addMethodCall。以下是两种可行修复方案:
方案一:用addDriver替代setMetadataDriverImpl
Doctrine的Configuration类提供addDriver方法,可添加多组驱动对应不同实体前缀,且需用**服务定义(Definition)**描述驱动创建逻辑,而非直接实例化:
修改process方法如下:
public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('doctrine.orm.default_configuration')) { return; } $definition = $container->getDefinition('doctrine.orm.default_configuration'); $loader = new CapabilityMappingLoader(); $mappings = $loader->loadMappings(); foreach ($mappings as $alias => $mapping) { // 添加实体命名空间 $definition->addMethodCall('addEntityNamespace', [$alias, $mapping['prefix']]); // 创建AttributeDriver的服务定义 $driverDefinition = new \Symfony\Component\DependencyInjection\Definition( AttributeDriver::class, [$mapping['dir']] ); // 调用addDriver关联驱动与实体前缀 $definition->addMethodCall('addDriver', [$driverDefinition, $mapping['prefix']]); } }
方案二:合并目录到现有AttributeDriver
如果应用原本就使用AttributeDriver,可获取现有驱动的目录列表,合并新目录后再设置:
public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('doctrine.orm.default_configuration')) { return; } $definition = $container->getDefinition('doctrine.orm.default_configuration'); $loader = new CapabilityMappingLoader(); $mappings = $loader->loadMappings(); // 收集所有待添加的实体目录 $allDirs = []; foreach ($mappings as $alias => $mapping) { $definition->addMethodCall('addEntityNamespace', [$alias, $mapping['prefix']]); $allDirs[] = $mapping['dir']; } // 查找现有驱动定义 $driverCalls = array_filter( $definition->getMethodCalls(), fn($call) => $call[0] === 'setMetadataDriverImpl' ); if (!empty($driverCalls)) { $existingDriver = reset($driverCalls)[1][0]; // 确认现有驱动是AttributeDriver if ($existingDriver instanceof \Symfony\Component\DependencyInjection\Definition && $existingDriver->getClass() === AttributeDriver::class) { // 合并新旧目录 $existingDirs = $existingDriver->getArgument(0); $existingDriver->replaceArgument(0, array_merge($existingDirs, $allDirs)); } } else { // 无现有驱动时,创建新的AttributeDriver定义 $driverDefinition = new \Symfony\Component\DependencyInjection\Definition( AttributeDriver::class, [$allDirs] ); $definition->addMethodCall('setMetadataDriverImpl', [$driverDefinition]); } }
关键注意点
- 编译阶段绝对不能直接实例化对象传给服务方法,必须用
Definition描述对象创建逻辑,确保Symfony可序列化容器配置。 setMetadataDriverImpl会覆盖原有驱动,而addDriver支持多驱动多前缀,更适配多模块场景。
内容的提问来源于stack exchange,提问作者floriank
相关产品推荐
相关产品推荐

