Я смотрю на улучшение скорости увлажнения доктрины. Я ранее использовал HYDRATE_OBJECT
но можно видеть, что во многих случаях с этим может быть довольно тяжело работать.
Я знаю, что самый быстрый вариант HYDRATE_ARRAY
но потом я откажусь от множества преимуществ работы с объектами сущностей. В тех случаях, когда в методе сущности есть бизнес-логика, это будет повторяться, однако это обрабатывается массивами.
Так что мне нужен более дешевый гидратор объектов. Я счастлив пойти на некоторые уступки и потерять некоторые функциональные возможности во имя скорости. Например, если бы он заканчивал тем, что был только для чтения, это было бы хорошо. Точно так же, если бы ленивая загрузка не была чем-то, это тоже было бы хорошо.
Существует ли такая вещь или я слишком много спрашиваю?
Если хочешь быстрее 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
с методом для заполнения сущности. В моем примере я использую уже созданные методы сеттера в объекте, чтобы заполнить его:
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
Возможно, вы ищете способ для Доктрины увлажнять 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
объекты.
Вы можете найти это здесь в документации.