Быстрая сущность Учение гидратор

Я смотрю на улучшение скорости увлажнения доктрины. Я ранее использовал HYDRATE_OBJECT но можно видеть, что во многих случаях с этим может быть довольно тяжело работать.

Я знаю, что самый быстрый вариант HYDRATE_ARRAY но потом я откажусь от множества преимуществ работы с объектами сущностей. В тех случаях, когда в методе сущности есть бизнес-логика, это будет повторяться, однако это обрабатывается массивами.

Так что мне нужен более дешевый гидратор объектов. Я счастлив пойти на некоторые уступки и потерять некоторые функциональные возможности во имя скорости. Например, если бы он заканчивал тем, что был только для чтения, это было бы хорошо. Точно так же, если бы ленивая загрузка не была чем-то, это тоже было бы хорошо.

Существует ли такая вещь или я слишком много спрашиваю?

11

Решение

Если хочешь быстрее 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 с методом для заполнения сущности. В моем примере я использую уже созданные методы сеттера в объекте, чтобы заполнить его:

    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_OBJECT в getResult метод запроса. Имейте в виду, что эта реализация не была тщательно протестирована, но должна работать даже с вложенными отображениями для более продвинутой функциональности, которую вам придется улучшить 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
13

Другие решения

Возможно, вы ищете способ для Доктрины увлажнять DTO (Объект передачи данных). Это не реальные объекты, а простые объекты только для чтения, предназначенные для передачи данных.

Начиная с Doctrine 2.4 у него есть естественная поддержка такой гидратации с использованием NEW оператор в DQL.

Когда у вас есть такой класс:

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 объекты.

Вы можете найти это здесь в документации.

4

По вопросам рекламы [email protected]