Ответ 1
Если вы хотите быстрее ObjectHydrator
, не теряя возможности работать с объектами, вам нужно будет создать свой собственный гидратор.
Для этого вам необходимо выполнить следующие действия:
-
Создайте свой собственный класс
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; } }
-
Зарегистрируйте гидратор в конфигурации Doctrine:
$config = new \Doctrine\ORM\Configuration() $config->addCustomHydrationMode(Hydrator::HYDRATE_SIMPLE_OBJECT, Hydrator::class);
-
Создайте
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_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