快速实体主义水化器
Fast entity Doctrine hydrator
我正在考虑提高学说水合作用的速度。我以前一直在使用 HYDRATE_OBJECT
,但可以看到在很多情况下,使用它可能会非常繁重。
我知道可用的最快选项是 HYDRATE_ARRAY
,但我放弃了使用实体对象的很多好处。在实体方法中存在业务逻辑的情况下,将重复该操作,但由数组处理。
所以我想要的是一种更便宜的水化器。我很乐意以速度的名义做出一些让步并放弃一些功能。例如,如果它最终是只读的,那也没关系。同样,如果延迟加载不是问题,那也没关系。
有这种事情吗还是我问的太多了?
如果你想更快ObjectHydrator
又不失去处理对象的能力,那么你将不得不创建自己的自定义水化器。
为此,您必须执行以下步骤:
创建您自己的 Hydrator
class 扩展 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 配置中注册 hydrator:
$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
您可能正在寻找一种让 Doctrine 滋润 DTO 的方法 (Data Transfer Object)。这些不是真实的实体,而是用来传递数据的简单 read-only 对象。
从 Doctrine 2.4 开始,它使用 DQL 中的 NEW
运算符原生支持这种水合作用。
当你 class 像这样:
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
个对象的数组。
你可以找到它here in the documentation。
我正在考虑提高学说水合作用的速度。我以前一直在使用 HYDRATE_OBJECT
,但可以看到在很多情况下,使用它可能会非常繁重。
我知道可用的最快选项是 HYDRATE_ARRAY
,但我放弃了使用实体对象的很多好处。在实体方法中存在业务逻辑的情况下,将重复该操作,但由数组处理。
所以我想要的是一种更便宜的水化器。我很乐意以速度的名义做出一些让步并放弃一些功能。例如,如果它最终是只读的,那也没关系。同样,如果延迟加载不是问题,那也没关系。
有这种事情吗还是我问的太多了?
如果你想更快ObjectHydrator
又不失去处理对象的能力,那么你将不得不创建自己的自定义水化器。
为此,您必须执行以下步骤:
创建您自己的
Hydrator
class 扩展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 配置中注册 hydrator:
$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
您可能正在寻找一种让 Doctrine 滋润 DTO 的方法 (Data Transfer Object)。这些不是真实的实体,而是用来传递数据的简单 read-only 对象。
从 Doctrine 2.4 开始,它使用 DQL 中的 NEW
运算符原生支持这种水合作用。
当你 class 像这样:
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
个对象的数组。
你可以找到它here in the documentation。