DrK*_*Key 7 php collections symfony doctrine-orm
关于OneToMany <-> ManyToOne
我的实体Device
和我之间的双向关系,我有一个问题Event
.这是映射的外观:
// Device entity
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Event", mappedBy="device")
*/
protected $events;
// Event entity
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\Device", inversedBy="events")
*/
protected $device;
Run Code Online (Sandbox Code Playgroud)
问题来自因为Device
是单表继承实体
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="device_class_type", type="string")
Run Code Online (Sandbox Code Playgroud)
每次我获取并迭代某些Event
实体,然后$device
总是急切地获取.发生这种情况是因为它是相关文档中报告的STI实体
单表继承存在一般性能考虑因素:如果多对一或一对一关联的目标实体是STI实体,则出于性能原因,它最好是继承中的叶实体层次结构,(即没有子类).否则,Doctrine不能创建该实体的代理实例,并且总是急切地加载该实体.
现在有另一个被称为Gateway
与两者有关系的实体Device
和Event
:
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Device", mappedBy="gateway")
*/
protected $devices;
/**
* @ORM\OneToMany(targetEntity="targetEntity="AppBundle\Entity\Event", mappedBy="gateway")
*/
protected $events;
public function getEvents(): Collection
{
return $this->events;
}
Run Code Online (Sandbox Code Playgroud)
当然,每次迭代$gateway->getEvents()
所有相关事件时,都会急切地获取设备.即使我没有得到任何$device
信息也会发生这种情况- 一个空的foreach
就足以让Doctrine为每个对象执行1个查询以获取相关信息$device
foreach ($gateway->getEvents() as $event) {}
Run Code Online (Sandbox Code Playgroud)
现在我知道我可以使用QueryBuilder
设置不同的水合模式来避免$device
取物
return $this->getEntityManager()->createQueryBuilder()
->select('e')
->from('AppBundle:Event', 'e')
->where('e.gateway = :gateway')
->setParameter('gateway', $gateway)
->getQuery()->getResult(Query::HYDRATE_SIMPLEOBJECT);
Run Code Online (Sandbox Code Playgroud)
但我想以某种方式直接在Gateway
实体中做到这一点.
那么可能Gateway->events
直接在Gateway
实体类中进行水合作用吗?
您有一个循环引用,其中这些节点之一(设备)将强制执行FETCH EAGER
. 更糟糕的是,其中一个节点(网关)的作用类似于其他两个节点之间的多对多连接表,导致FETCH EAGER
在近乎无限循环中加载所有内容(或至少是大块相关数据)。
+\xe2\x94\x80\xe2\x94\x80< OneToMany\n >\xe2\x94\x80\xe2\x94\x80+ ManyToOne\n >\xe2\x94\x80\xe2\x94\x80< ManyToMany\n +\xe2\x94\x80\xe2\x94\x80+ OneToOne\n\n \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80< Gateway >\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90\n \xe2\x94\x82 \xe2\x94\x82\n + +\n Event +\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80< Device*\n
Run Code Online (Sandbox Code Playgroud)\n\n正如您所看到的,当设备执行 EAGER fetch 时,它将收集 Many Gateways
,therefore much Events
,therefore much Devices
,therefore much more Gateways
,等等,Fetch EAGER
直到所有引用都被填充为止。
构建您自己的水化器将需要一些仔细的数据操作,但对于您的用例来说可能会有些简单。请记住在 Doctrine 中注册您的水化器,并将其作为参数传递给$query->execute([], \'GatewayHydrator\');
class GatewayHydrator extends DefaultEntityHydrator\n{\n public function hydrateResultSet($stmt)\n {\n $data = $stmt->fetchAll(PDO::FETCH_ASSOC);\n $class = $this->em->getClassMetadata(Gateway::class);\n $gateway = $class->newInstance();\n\n $gateway->setName($data[0][\'gateway_name\']); // example only\n\n return $gateway;\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n从 Doctrine 的角度来看,从 和中删除$gateway => Gateway
映射,Device 将有效地成为一片叶子。这将避免该引用循环,但有一个缺点:必须手动设置“设备”->“网关”属性(可能在“网关”和“事件”中)Device
mappedBy="gateway"
Gateway->device
setDevice
方法中)。