学说映射未找到案例

时间:2012-06-15 20:05:38

标签: php orm doctrine mapping

我有一张表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%干净的解决方案。

我希望有人有答案。

谢谢

4 个答案:

答案 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不再存在,我们不想使用某种清理脚本简单地删除它。这在正常的应用程序执行期间不会发生,但在手动编辑数据库表时很少发生,例如在迁移/升级期间。

我们考虑了几个但不太热衷的选择:

  • 使用供应商特定的修补程序,在这种情况下SQL ServerON DELETE SET NULL约束。)
  • 使用自定义的Twig扩展来捕获EntityNotFoundException(我们在Twig模板中只是遇到了这个问题,但是不想在任何地方添加扩展,然后我们的PHP控制器中仍然可能存在问题)。

相反,我们决定通过捕获其中的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;
        }
    }
}
相关问题