4

テーブルBを参照するテーブルAがあります。

編集:使用されるデータベースエンジンはMyISAMです。

教義のマッピングは魅力のように機能しますが、DBに無効なケースがあり、テーブルAの参照IDがテーブルBに実際には存在しない場合を除きます。

したがって、このコードを実行すると、次のようになります。

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

nullではない$objectBプロキシオブジェクトを実際に取得します。したがって、!empty($ objectB)が渡されます。

そして、$ objectBの任意のプロパティにアクセスしようとすると、次のようになります。

$ objectB-> getName();

Entitynotfound例外が発生します。$ objectBが実際には存在せず、$ objectBのNameプロパティがないことを、コードで予測する方法はありません。

$ objectBは実際にはnullに設定する必要がありますが、それは発生していません。

Hibernateには、実際には、欠落しているオブジェクトをプロキシオブジェクトに設定する代わりにNULLに設定するマッピングプロパティnot-found=ignoreがあります。Doctrineに似たようなものはありますか?

PS。もちろん、エンティティが見つからないという例外をいつでもキャッチして、それを試してみることができます。または、テーブルAに実際のobjectB_IDフィールドをマップすることもできますが、これらは100%クリーンなソリューションではありません。

誰かが答えを持っているといいのですが。

ありがとうございました

4

4 に答える 4

8

ただし、テーブルAの参照IDがテーブルBに実際には存在しない、DBに無効なケースがある場合を除きます。

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

生成されたプロキシクラスの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()と関数のスローを削除するだけで逃げることができるはずです。__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が存在しなくなった場合に、ある種のクリーンアップスクリプトを使用してオブジェクトAを削除したくありませんでした。これは、通常のアプリケーションの実行中は発生しませんが、移行/アップグレード中など、データベーステーブルを手動で編集する場合はめったに発生しません。

私たちが検討したがあまり熱心ではなかったいくつかのオプション:

  • ベンダー固有の修正を使用します。この場合はSQLServerの制約ON 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');
    }
}

このトレイトは、存在する場合は単にtrueを返す__invoke()PHPマジックメソッドを追加しますが、プロキシをロードして、関連付けられたエンティティが実際にeixstであるか、関連付けられたエンティティの孤立した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 に答える