快速实体学说水合器


Fast entity Doctrine hydrator

我正在考虑提高学说的水化速度。我以前一直在使用HYDRATE_OBJECT,但可以看到,在许多情况下,这可能是相当繁重的工作。

我知道最快的选择是HYDRATE_ARRAY,但随后我放弃了使用实体对象的很多好处。在实体方法中有业务逻辑的情况下,这将被重复用于数组处理。

所以我要的是更便宜的水合物。我很高兴以速度的名义做出一些让步和失去一些功能。例如,如果它最终是只读的,那就没关系。同样,如果没有延迟加载,也可以。

这种事情真的存在吗?还是我要求太多了?

如果你想要更快的ObjectHydrator而不失去与对象一起工作的能力,那么你将不得不创建自己的自定义水合器。

要做到这一点,你必须做以下步骤:

  1. 创建自己的Hydrator类,扩展Doctrine'ORM'Internal'Hydration'AbstractHydrator。在我的情况下,我扩展ArrayHydrator,因为它节省了我映射别名到对象变量的麻烦:

    use Doctrine'ORM'Internal'Hydration'ArrayHydrator;
    use Doctrine'ORM'Mapping'ClassMetadataInfo;
    use PDO;
    class Hydrator extends ArrayHydrator
    {
        const HYDRATE_SIMPLE_OBJECT = 55;
        protected function hydrateAllData()
        {
            $entityClassName = reset($this->_rsm->aliasMap);
            $entity = new $entityClassName();
            $entities = [];
            foreach (parent::hydrateAllData() as $data) {
                $entities[] = $this->hydrateEntity(clone $entity, $data);
            }
            return $entities;
        }
        protected function hydrateEntity(AbstractEntity $entity, array $data)
        {
            $classMetaData = $this->getClassMetadata(get_class($entity));
            foreach ($data as $fieldName => $value) {
                if ($classMetaData->hasAssociation($fieldName)) {
                    $associationData = $classMetaData->getAssociationMapping($fieldName);
                    switch ($associationData['type']) {
                        case ClassMetadataInfo::ONE_TO_ONE:
                        case ClassMetadataInfo::MANY_TO_ONE:
                            $data[$fieldName] = $this->hydrateEntity(new $associationData['targetEntity'](), $value);
                            break;
                        case ClassMetadataInfo::MANY_TO_MANY:
                        case ClassMetadataInfo::ONE_TO_MANY:
                            $entities = [];
                            $targetEntity = new $associationData['targetEntity']();
                            foreach ($value as $associatedEntityData) {
                                $entities[] = $this->hydrateEntity(clone $targetEntity, $associatedEntityData);
                            }
                            $data[$fieldName] = $entities;
                            break;
                        default:
                            throw new 'RuntimeException('Unsupported association type');
                    }
                }
            }
            $entity->populate($data);
            return $entity;
        }
    }
    
  2. 在Doctrine配置中注册水合器:

    $config = new 'Doctrine'ORM'Configuration()
    $config->addCustomHydrationMode(Hydrator::HYDRATE_SIMPLE_OBJECT, Hydrator::class);
    
  3. 使用填充实体的方法创建AbstractEntity。在我的示例中,我在实体中使用已经创建的setter方法来填充它:

    abstract class AbstractEntity
    {
        public function populate(Array $data)
        {
            foreach ($data as $field => $value) {
                $setter = 'set' . ucfirst($field);
                if (method_exists($this, $setter)) {
                    $this->{$setter}($value);
                }
            }
        }
    }
    

在这三个步骤之后,您可以传递HYDRATE_SIMPLE_OBJECT而不是HYDRATE_OBJECTgetResult查询方法。请记住,这个实现没有经过严格的测试,但应该可以使用嵌套映射来实现更高级的功能,您将不得不改进Hydrator::hydrateAllData(),除非您实现与EntityManager的连接,否则您将失去轻松保存/更新实体的能力,而另一方面,因为这些对象只是简单的对象,您将能够序列化和缓存它们。

性能测试

测试代码:

$hydrators = [
    'HYDRATE_OBJECT'        => 'Doctrine'ORM'AbstractQuery::HYDRATE_OBJECT,
    'HYDRATE_ARRAY'         => 'Doctrine'ORM'AbstractQuery::HYDRATE_ARRAY,
    'HYDRATE_SIMPLE_OBJECT' => Hydrator::HYDRATE_SIMPLE_OBJECT,
];
$queryBuilder = $repository->createQueryBuilder('u');
foreach ($hydrators as $name => $hydrator) {
    $start = microtime(true);
    $queryBuilder->getQuery()->getResult($hydrator);
    $end = microtime(true);
    printf('%s => %s <br/>', $name, $end - $start);
}

基于940条记录的结果,每条记录20~列:

HYDRATE_OBJECT => 0.57511210441589
HYDRATE_ARRAY => 0.19534111022949
HYDRATE_SIMPLE_OBJECT => 0.37919402122498

您可能正在为Doctrine寻找一种方法来填充DTO(数据传输对象)。它们不是真正的实体,而是用来传递数据的简单只读对象。

从Doctrine 2.4开始,它在DQL中使用NEW运算符来支持这种水合作用。

当你有这样的类:

class CustomerDTO
{
    private $name;
    private $email;
    private $city;
    public function __construct($name, $email, $city)
    {
        $this->name  = $name;
        $this->email = $email;
        $this->city  = $city;
    }
    // getters ...
}

你可以这样使用SQL:

$query     = $em->createQuery('SELECT NEW CustomerDTO(c.name, e.email, a.city) FROM Customer c JOIN c.email e JOIN c.address a');
$customers = $query->getResult();

$customers将包含CustomerDTO对象的数组。

您可以在文档中找到