Doctrine ORM 2.9 同时使用 AnnotationDriver 和 AttributeDriver 来解析实体元数据

Doctrine ORM 2.9 use both AnnotationDriver and AttributeDriver to parse entity metadata

最近我们将应用程序升级到 PHP8。

自从 PHP8 引入 attributes 并且 doctrine/orm 从版本 2.9 开始支持它们,使用这个功能来 似乎是个好主意(即 不是 一次所有实体)将实体元数据更新为属性格式。

为此,我需要以某种方式注册 Doctrine\ORM\Mapping\Driver\AnnotationDriverDoctrine\ORM\Mapping\Driver\AttributeDriver 以解析元数据。

棘手的部分是为一组使用注释或属性装饰的实体注册两个解析器。从Doctrine\ORM\Configuration的角度来看,我需要的似乎是不可能的。

我说的对吗(假设这无法合理实现)或者可以用一些不太黑的方式来完成吗?

我不确定它是否可以完成,但你可以看看 Rector 一次自动升级所有实体。似乎已经有一个配置。

https://github.com/rectorphp/rector

https://github.com/rectorphp/rector-doctrine/blob/4bbeb676e9ec8c146a81617f6362be4cafbdf3b3/config/sets/doctrine-orm-29.php

Doctrine 本身不提供这种可能性。但是我们可以实现自定义映射驱动程序来实现这一点。

实际实现可能如下所示:

<?php                                                                           
                                                                                
namespace Utils\Doctrine;                                                    
                                                                                
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;                               
use Doctrine\ORM\Mapping\Driver\AttributeDriver;                                
use Doctrine\ORM\Mapping\MappingException;                                      
use Doctrine\Persistence\Mapping\ClassMetadata;                                 
use Doctrine\Persistence\Mapping\Driver\AnnotationDriver as AbstractAnnotationDriver;
                                                                                
class HybridMappingDriver extends AbstractAnnotationDriver                      
{                                                                               
    public function __construct(                                                
        private AnnotationDriver $annotationDriver,                                
        private AttributeDriver $attributeDriver,                                  
    ) {                                                                            
    }                                                                              
                                                                                
    public function loadMetadataForClass($className, ClassMetadata $metadata): void
    {                                                                           
        try {                                                                      
            $this->attributeDriver->loadMetadataForClass($className, $metadata);
            return;                                                             
        } catch (MappingException $me) {                                        
            // Class X is not a valid entity, so try the other driver            
            if (!preg_match('/^Class(.)*$/', $me->getMessage())) {// meh           
                throw $me;                                                         
            }                                                                      
        }                                                                       
        $this->annotationDriver->loadMetadataForClass($className, $metadata);   
    }                                                                            
                                                                                 
    public function isTransient($className): bool                                     
    {                                                                           
        return $this->attributeDriver->isTransient($className)                     
            || $this->annotationDriver->isTransient($className);                   
    }                                                                              
}

简而言之:

  • 驱动程序首先尝试使用 AttributeDriver,然后回退到 AnnotationDriver,以防检查中的 class 未被评估为有效实体
  • 为了在扩展 Doctrine\Persistence\Mapping\Driver\AnnotationDriver 后符合 Doctrine\Persistence\Mapping\Driver\MappingDriver 接口 class 只需实现 2 个方法
  • 从示例实现中可以看出,两种方法都涉及元数据映射驱动程序
  • 通过解析消息来区分各种MappingException,一点也不优雅,但是没有更好的属性来区分;每个映射错误案例具有不同的异常子类型或一些唯一代码将有助于区分映射错误的各个原因

HybridMappingDriver 可以像这样连接到 EntityManagerFactory 中:

<?php

namespace App\Services\Doctrine;

use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\Proxy\AbstractProxyFactory as APF;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Utils\Doctrine\NullCache;

class EntityManagerFactory
{
    public static function create(
        array $params,
        MappingDriver $mappingDriver,
        bool $devMode,
    ): EntityManager {
        AnnotationRegistry::registerLoader('class_exists');
        $config = Setup::createConfiguration(
            $devMode,
            $params['proxy_dir'],
            new NullCache(), // must be an instance of Doctrine\Common\Cache\Cache
        );
        $config->setMetadataDriverImpl($mappingDriver); // <= this is the actual hook-up
        if (!$devMode) {
            $config->setAutoGenerateProxyClasses(APF::AUTOGENERATE_FILE_NOT_EXISTS);
        }

        return EntityManager::create($params['database'], $config);
    }
}