4

我有一个引用表 B 的表 A。

编辑:使用的数据库引擎是 MyISAM。

教义映射就像一个魅力,除非我在数据库中有无效案例,其中表 A 中引用的 ID 在表 B 中并不真正存在。

因此,当您执行此代码时:

$objectB = $objectA->getObjectB();//with lazy load

你实际上得到了 $objectB 代理对象,它不是 null。所以 !empty($objectB) 会通过。

当您尝试访问 $objectB 的任何属性时,例如:

$objectB->getName();

你得到Entity not found 异常。您无法在代码中预测 $objectB 实际上不存在并且 $objectB 没有 Name 属性。

$objectB 实际上应该设置为 null,但这并没有发生。

Hibernate 实际上具有映射属性not-found=ignore,它将丢失的对象设置为NULL而不是将其设置为代理对象。Doctrine有类似的东西吗?

PS。当然,你总是可以捕捉到 Entity not found 异常,然后玩弄它。或者您可以映射表 A 中的实际 objectB_ID 字段,但这些不是 100% 干净的解决方案。

我希望有人有答案。

谢谢你

4

4 回答 4

8

除非我在数据库中有无效的情况,其中表 A 中引用的 ID 在表 B 中并不真正存在

IMO 这是一个垃圾进,垃圾出的案例。如果您的架构中 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
}
于 2012-06-15T21:05:53.070 回答
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;
}

或者你可以看看实现这个__wakeup()函数ObjectB并可能自己处理这个(尽管你很可能需要抛出一个异常)。

最后,如果您有野心,您可以更改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()and__clone()函数中的抛出,尽管可能会有意想不到的副作用。如果您计划定期升级 Doctrine,您可能还希望将此更改视为一个补丁。

于 2012-06-20T16:54:23.823 回答
1

在 ObjectA 类的注释中使用它:

@ORM\ManyToOne(targetEntity="ObjectB", fetch="EAGER")
于 2016-04-07T23:43:21.027 回答
0

我们在使用 Doctrine ORM 时遇到了这个问题,其中对象 A 非常重要,如果对象 A 不再存在,我们不想使用某种清理脚本简单地删除它。这在正常的应用程序执行期间不会发生,但在手动编辑数据库表时很少发生,例如在迁移/升级期间。

我们考虑过但不太热衷的几个选项:

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

相反,我们决定通过在实体中捕获 EntityNotFoundException 来稍微污染我们的实体,但将所有逻辑都包含到 EntityExistanceCheckableTrait 特征中。

一旦特征被添加到对象 A 和对象 B 中,那么我们需要做的就是调用$objectB->hasObjectB(){{ objectB.hasObjectA() }}在 Twig 中并按照控制器/模板的逻辑来处理它。

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;
        }
    }
}
于 2016-11-26T13:45:20.470 回答