我有与其他实体有1:1或1:M关系的实体。然而,所有关系都是可以为空的。
我想将一些操作代理到相关实体。我在下面举例说明。问题是,如果关系仍然不存在,我有null
,所以我最终不断检查空值,这显然是错误的。我想做的是用空物体水合我的实体。原因:
$object->setSettings(new SettingsEntity)
当然,我可以在实体的构造函数中添加初始化,或者提供创建对象的新实例的getter(如果不存在)。有几个原因我不想要这个:
一些示例代码:
/**
* SomeObject
* @ORM\Entity()
* @ORM\Table(
name="some_object"
* )
*/ class SomeObject implements DataTransfer {
/**
* @ORM\OneToOne(targetEntity="Settings", mappedBy="SomeObject")
*/
protected $settings;
public function getSettings() {
return $this->settings;
}
public function get() {
$record = new \stdClass();
$record->id = $this->getId();
...
$settingsObject = $this->getSettings();
$record->someKey = $settingsObject ? $settingsObject->getSomeKey() : null;
$record->someOtherKey = $settingsObject ? $settingsObject->getSomeOtherKey() : null;
return $record;
}
欢迎任何建议,包括黑客攻击主义。
P.S。 Doctrine-ORM版本是2.3。如果这有助于解决问题,我可以升级。
答案 0 :(得分:1)
我不会讨论你的代理理论:你的代码,你的设计,我没有足够的知识来获得意见。
关于你知道Doctrine如何保护其实体,你可以看到它是如何在\Doctrine\ORM\UnitOfWork::createEntity
中完成的。它似乎没有调用构造函数(使用\ReflectionClass::newInstanceWithoutConstructor
,显然不应该使用构造函数),但您可能有兴趣听Doctrine的post-load event (part of the lifecycle events logic)。
关于初始化null
属性,即加载后事件应触发的代码,您应首先在所有实体上设置超类:而不是class SomeObject implements DataTransfer {...}
,而不是class SomeObject extends MyEntity {...}
。 d有MyEntity
(并DataTransfer
实施MyEntity
来保留您的界面。此@HasLifecycleCallbacks
类将为"mapped superclass",它将使用@PostLoad
进行注释,并声明使用/** @HasLifecycleCallbacks @MappedSuperclass ... */
public class MyEntity implements DataTransfer {
...
/** @PostLoad */
public function doPostLoad(\Doctrine\Common\Persistence\Event\LifecycleEventArgs $event) { //the argument is needed here, and is passed only since 2.4! If you don't want to upgrade, you can work around by using event listeners, but it's more complicated to implement ;)
$em = $event->getEntityManager();
$this->enableFakeMappings($em);
}
private function enableFakeMappings(\Doctrine\ORM\EntityManager $em) {
$mappings = $em->getClassMetadata(get_class($this))->getAssociationMappings(); //try and dump this $mappings array, it's full o'good things!
foreach ($mappings as $mapping) {
if (null === $this->{$mapping['fieldName']}) {
$class = $mapping['targetEntity'];
$this->{$mapping['fieldName']} = new $class(); //this could be cached in a static and cloned when needed
}
}
}
}
注释的方法。你有你的钩子来运行你的null-to-something代码。
为了使此代码具有通用性(因为它可以从这个超类中编码),您可以依赖Doctrine的实体元数据,该元数据保留关联映射以及工作单元需要计算的所有数据它的低级数据库访问业务。它应如下所示:
MyEntity
现在,考虑一下你必须要新建一个实体并且想要在没有空值检查的情况下访问其属性的情况:你必须为这个工作伪造一个体面的构造函数。由于您仍然需要实体管理器,最直接的方法是将EM传递给构造函数。在ZF2(和我相信的Symfony)中,您可以注入服务定位器并从那里检索EM。有几种方法,但这是另一个故事。所以,基本的,在public function __construct(\Doctrine\ORM\EntityManager $em) {
$this->enableFakeMappings($em);
}
:
/** @PrePersist */
public function doPrePersist(\Doctrine\Common\Persistence\Event\LifecycleEventArgs $event) {
$em = $event->getEntityManager();
$this->disableFakeMappings($em);
}
/** @PreUpdate */
public function doPreUpdate(\Doctrine\Common\Persistence\Event\LifecycleEventArgs $event) {
$em = $event->getEntityManager();
$this->disableFakeMappings($em);
}
private function disableFakeMappings(\Doctrine\ORM\EntityManager $em) {
$uow = $em->getUnitOfWork();
$mappings = $em->getClassMetadata()->getAssociationMappings();
foreach ($mappings as $mapping) {
if (!$this->{$mapping['fieldName']} instanceof MyEntity) {
continue;
}
//"reset" faked associations: assume they're fake if the object is not yet handled by Doctrine, which breaks the cascading auto-persist... risk nothing, gain nothing, heh? ;)
if (null === $uow->getEntityState($this->{$mapping['fieldName']}, null)) {
$this->{$mapping['fieldName']} = null;
}
}
}
但是,这样做可能会在实体被持久化时混淆Doctrine:它应该对所有这些实例化的空对象做什么?它会级联持久化它们,这不是你想要的(如果是,那么,你可以停止阅读;))。牺牲级联持久,一个简单的解决方案将是这样的,仍然在你的超类:
{{1}}
希望这有帮助! :)