Бърз entity Doctrine hydrator

Търся подобряване на скоростта на хидратация на доктрината. Преди съм използвал HYDRATE_OBJECT, но виждам, че в много случаи работата с него може да бъде доста тежка.

Наясно съм, че най-бързата налична опция е HYDRATE_ARRAY, но тогава раздавам много предимства от работата с обекти на обекти. В случаите, когато има бизнес логика в метод на обект, това ще се повтори, но това се обработва от масиви.

Така че това, което търся, е по-евтин хидрататор на предмети. Щастлив съм да направя някои отстъпки и да загубя някои функции в името на скоростта. Например, ако се окаже само за четене, това би било добре. Също така, ако мързеливото зареждане не беше нещо, това също би било добре.

Съществува ли такова нещо или питам твърде много?


person Rob Forrest    schedule 09.10.2015    source източник


Отговори (2)


Ако искате по-бързо 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
person Marcin Necsord Szulc    schedule 22.01.2016
comment
Благодаря ти Марчин за отговора. Възнамерявам да ви присъдя наградата, тъй като сте предоставили най-добрия отговор до момента, но няма да го маркирам като правилен с напразната надежда, че някой може да напише такъв, който може да се справи с отношенията ManyToMany/OneToMany/ManyToOne . - person Rob Forrest; 28.01.2016
comment
Благодаря @RobForrest Промених отговора си, за да включва поддръжка за асоциации. Не го тествах сериозно, но го тествах с ManyToOne и OneToMany с вложен OneToOne и работи добре. - person Marcin Necsord Szulc; 28.01.2016
comment
Страхотно!!! Благодаря за това. Очаквам с нетърпение да опитам това, ще се свържа с вас как ще продължа. - person Rob Forrest; 29.01.2016
comment
Няколко неща, в края на hydrateEntity() викате $entity->setFromArray($data); дали имахте предвид $entity->populate($data); ?. Също така трябваше да добавя use Doctrine\ORM\Mapping\ClassMetadataInfo; най-отгоре. - person Rob Forrest; 29.01.2016
comment
Освен тези неща, той се представя наистина добре. Ще го използвам в гняв през следващите месеци и ще докладвам как работи в реална среда. Много благодаря. - person Rob Forrest; 29.01.2016
comment
Да, прав си, благодаря, че ги посочи! Имам някакъв остарял код, който използва различни имена, затова беше различен. - person Marcin Necsord Szulc; 29.01.2016
comment
Здравей @MarcinNecsordSzulc, можеш ли да посочиш какво е местоположението на абстрактния клас? - Създавам хидрататор в MyBundle/Hydrator/ClassHydrator - В config.yml съм го конфигурирал по-долу: doctrine: orm: hydrators: ListHydrator: MyBundle\Hydrator\SimpleHydrator В MyBundle/Entity/AbstractEntity се създава. Но получавам грешки, можете ли да ми кажете защо? - person anujeet; 29.07.2019
comment
Трудно ми е да кажа без да знам грешката. AbstractEntity е мой собствен клас, така че няма нужда от конкретен път, стига вашите пътища да са включени - person Marcin Necsord Szulc; 31.07.2019

Може би търсите начин Doctrine да хидратира 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 обекти.

Можете да го намерите тук в документацията.

person Jasper N. Brouwer    schedule 22.01.2016
comment
Благодаря, Джаспър, не мисля, че DTO са съвсем правилният отговор тук, искам да използвам повторно класовете на обекти, които вече съществуват, вместо да създавам ново стадо класове, с които да работя. - person Rob Forrest; 29.01.2016
comment
Няма проблем! Ще го запазя тук като напомняне за хора с подобни въпроси, за които може да е валиден вариант :) - person Jasper N. Brouwer; 01.02.2016