我有一张表A,它引用了表B.
编辑:使用的数据库引擎是MyISAM。
Doctrine映射的工作原理就像一个魅力,除非我在DB中有无效的情况,表A中的引用ID实际上不存在于表B中。
所以当你执行这段代码时:
$objectB = $objectA->getObjectB();//with lazy load
你实际上得到了$ objectB代理对象 not null 。所以!空($ objectB)将通过。
当您尝试访问$ objectB的任何属性时,例如:
$ objectB->的getName();
您获得实体未找到例外。您无法在代码中预测$ objectB实际上不存在并且$ objectB没有Name属性。
$ objectB实际应该设置为null但是没有发生。
Hibernate实际上映射了属性 not-found = ignore ,它将缺少的对象设置为 NULL ,而不是将其设置为Proxy对象。 Doctrine有类似的东西吗?
PS。当然,您总是可以捕获实体未找到的异常,并使用它。或者您可以映射表A中的实际objectB_ID字段,但这些字段不是100%干净的解决方案。
我希望有人有答案。
谢谢
答案 0 :(得分:7)
除非我在DB中有无效案例,表A中的引用ID实际上不存在于表B中
IMO这是一个垃圾进入垃圾箱的情况。如果TableA中TableA可能有或可能没有TableB中的行,则应在TableB上实现FK约束,以便在从TableB中删除行时,TableA中引用已删除行的任何行都将其值更改为null。
如果您真的想继续推进您提出的架构实施,可以尝试:
$rowExists = ($objectA->getObjectB()->getId() > 0) ? true : false;
这当然假设你在tableB上有一个id列,并且它是无符号的。
- 更新 -
try {
$objectB = $objectA->getObjectB();
} catch (Exception $e) {
$objectB = null;
}
if ($objectB instanceof ClassB) {
// Do work
}
答案 1 :(得分:1)
如果你查看一个生成的代理类,你会发现__load()
和__clone()
函数都抛出了EntityNotFoundException
。
当您“懒洋洋地”调用__load()
函数时,会调用getName()
函数。
class ObjectB extends \Foo\Entity\ObjectB implements \Doctrine\ORM\Proxy\Proxy
{
private $_entityPersister;
private $_identifier;
public $__isInitialized__ = false;
public function __construct($entityPersister, $identifier)
{
$this->_entityPersister = $entityPersister;
$this->_identifier = $identifier;
}
/** @private */
public function __load()
{
if (!$this->__isInitialized__ && $this->_entityPersister) {
$this->__isInitialized__ = true;
if (method_exists($this, "__wakeup")) {
// call this after __isInitialized__to avoid infinite recursion
// but before loading to emulate what ClassMetadata::newInstance()
// provides.
$this->__wakeup();
}
if ($this->_entityPersister->load($this->_identifier, $this) === null) {
throw new \Doctrine\ORM\EntityNotFoundException();
}
unset($this->_entityPersister, $this->_identifier);
}
}
...
public function getName()
{
$this->__load();
return parent::getName();
}
...
}
你基本上有几个选项,第一个是使用try/catch
块。
try {
$name = $objectB->getName();
} catch (\Doctrine\ORM\EntityNotFoundException $e) {
$name = null;
}
或者你可以看一下在ObjectB
中实现__wakeup()
函数并可能自己处理它(尽管你很可能还是需要抛出异常)。
最后,如果您有野心,可以更改Proxy
模板。 \Doctrine\ORM\Proxy\ProxyFactory
包含模板。
/** Proxy class code template */
private static $_proxyClassTemplate =
'<?php
namespace <namespace>;
/**
* THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
*/
class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy
{
private $_entityPersister;
private $_identifier;
public $__isInitialized__ = false;
public function __construct($entityPersister, $identifier)
{
$this->_entityPersister = $entityPersister;
$this->_identifier = $identifier;
}
/** @private */
public function __load()
{
if (!$this->__isInitialized__ && $this->_entityPersister) {
$this->__isInitialized__ = true;
if (method_exists($this, "__wakeup")) {
// call this after __isInitialized__to avoid infinite recursion
// but before loading to emulate what ClassMetadata::newInstance()
// provides.
$this->__wakeup();
}
if ($this->_entityPersister->load($this->_identifier, $this) === null) {
throw new \Doctrine\ORM\EntityNotFoundException();
}
unset($this->_entityPersister, $this->_identifier);
}
}
尽管可能会出现意外的副作用,但您应该只是删除EntityNotFoundException
和__load()
函数中__clone()
的投掷。如果您计划定期升级Doctrine,您可能还希望将此更改作为补丁。
答案 2 :(得分:1)
在ObjectA类的注释中使用它:
@ORM\ManyToOne(targetEntity="ObjectB", fetch="EAGER")
答案 3 :(得分:0)
我们使用Doctrine ORM来解决这个问题,其中对象A非常重要,如果对象A不再存在,我们不想使用某种清理脚本简单地删除它。这在正常的应用程序执行期间不会发生,但在手动编辑数据库表时很少发生,例如在迁移/升级期间。
我们考虑了几个但不太热衷的选择:
ON DELETE SET NULL
约束。)相反,我们决定通过捕获其中的EntityNotFoundException来污染我们的实体,但是包含EntityExistanceCheckableTrait特征的所有逻辑。
将特征添加到对象A和对象B之后,我们需要做的就是在Twig中调用$objectB->hasObjectB()
或{{ objectB.hasObjectA() }}
并根据控制器/模板的逻辑来处理
class ObjectA
{
use EntityExistanceCheckableTrait;
...
}
和
class ObjectB
{
use EntityExistanceCheckableTrait;
...
public function hasObjectB()
{
return $this->hasEntity('ObjectB');
}
}
该特性添加了__invoke()PHP魔术方法,如果它存在则只返回true但是这就是我们需要使代理加载并查看关联实体是否实际上是eixsts或者只是关联实体中的孤立ID。
以下是特征的完整代码:
/**
* Add to the entities on both sides of a Doctrine Association then implement a wrapper around hasEntity() in the
* owning entity.
*
* Trait EntityExistanceCheckableTrait
*/
trait EntityExistanceCheckableTrait
{
/**
* This can be empty but needs to be defined so we can check that the entity is callable.
*
* @return bool
*/
public function __invoke()
{
return true;
}
/**
* @param string $entityName
* @return bool
*/
public function hasEntity($entityName)
{
// Create the callable method
$entityMethod = 'get'.$entityName;
try {
$entity = $this->$entityMethod();
// We invoke the associated entity to force the real entity to be lazy-loaded instead of the proxy.
// This way we can ensure that we don't have an orphan entity (e.g. a proxy with the ID of the associated
// entity, loaded from the original entity, but the associated entity no longer exists in the database).
return (isset($entity) && $entity()) ? true : false;
} catch (EntityNotFoundException $e) {
return false;
}
}
}