web-dev-qa-db-ja.com

Doctrine 2でエンティティが変更されたかどうかを確認する方法は?

永続化されたエンティティが変更され、データベースで更新する必要があるかどうかを確認する必要があります。私が作った(そしてうまくいかなかった)のは次のとおりです。

$product = $entityManager->getRepository('Product')->find(3);
$product->setName('A different name');

var_export($entityManager->getUnitOfWork()->isScheduledForUpdate($product));

そのコードは常にfalseを出力します。作業単位をチェックする前にフラッシュしようとしましたが、機能しませんでした。

誰にも提案がありますか?

28
eagleoneraptor

setName関数が実際に何かを実行していることを最初に確認します($ this-> name = $ name ...)既に機能している場合は、イベントリスナーを定義できますフラッシュを呼び出すときにトリガーされるservices.yml。

entity.listener:
  class: YourName\YourBundle\EventListener\EntityListener
  calls:
    - [setContainer,  ["@service_container"]]
  tags:
    - { name: doctrine.event_listener, event: onFlush }

次に、EntityListenerを定義します

namespace YourName\YourBundle\EventListener;

use Doctrine\ORM\Event;
use Symfony\Component\DependencyInjection\ContainerAware;

class EntityListener extends ContainerAware
{   

    /**
     * Gets all the entities to flush
     *
     * @param Event\OnFlushEventArgs $eventArgs Event args
     */
    public function onFlush(Event\OnFlushEventArgs $eventArgs)
    {   
        $em = $eventArgs->getEntityManager();
        $uow = $em->getUnitOfWork();

        //Insertions
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
            # your code here for the inserted entities
        }

        //Updates
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
            # your code here for the updated entities
        }

        //Deletions
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
            # your code here for the deleted entities
        }
    }
}

どのエンティティが変更されているかを知る必要があるが、それらで何かを行う必要がある場合afterそれらはデータベースに保存され、変更されたエンティティをプライベート配列に格納し、次にonFlushイベントを定義します配列からエンティティを取得します。

ところで、この種のイベントをトリガーするには、エンティティに@ORM\HasLifecycleCallbacksを追加する必要があります。

24
Sergi

私の場合、リスナーを作成する必要はありませんでしたかったので、最終的には

$product->setName('A different name');
$uow = $entityManager->getUnitOfWork();
$uow->computeChangeSets();
if ($uow->isEntityScheduled($product)) {
    // My entity has changed
}
12

古い値と新しい値を持つエンティティフィールドにアクセスする必要がある場合は、 PreUpdate イベントを確認することもできます。

主に提供されているリンクから取られた例のビット:

<?php
class NeverAliceOnlyBobListener
{
    public function preUpdate(PreUpdateEventArgs $eventArgs)
    {
        if ($eventArgs->getEntity() instanceof User) {
            if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
                $oldValue = $eventArgs->getOldValue('name');
                $eventArgs->setNewValue('name', 'Bob');
            }
        }
    }
}
10
Andrew Atkinson

Doctrine2 Docs。17.変更追跡ポリシー

私のように3番目のフォーム(17.3。通知)を使用する場合、エンティティが変更されているかどうかをテストできます。

$uow = $entityManager->getUnitOfWork();
$uow->computeChangeSets();
$aChangeSet = $uow->getEntityChangeSet($oEntity);

何も変更されていない場合は、空の配列を返します。

7
Mikl

この問題はかなり古いものですが、別の観点からこの問題に直面する可能性のある人々のグループがまだ存在する可能性があります。 UnitOfWorkはうまく機能しますが、変更の配列のみを返します。誰がどのフィールドが変更されたかを実際に知らず、エンティティ全体をオブジェクトとして取得して_$oldEntity_と_$newEntity_を比較したい場合、それは苦痛になります。次のように誰かがデータベースからデータを取得しようとすると、イベントの名前はpreUpdateになります。

_$er->find($id);
_

返されるエンティティにはすべての変更が含まれます。回避策は非常に簡単ですが、いくつかのフックがあります。

_public function preUpdate(Entity $entity, PreUpdateEventArgs $args)
{
    $entity = clone $entity; //as Doctrine under the hood 
                             //uses reference to manage entities you might want 
                             //to work on the entity copy. Otherwise,        
                             //the below refresh($entity) will affect both 
                             //old and new entity.
    $em = $args->getEntityManager();
    $currentEntity = $em->getRepository('AppBundle:Entity')->find($entity->getId());
    $em->refresh($currentEntity);

}
_

PreFlushのような別のイベントを使用している人のために、すぐに確認しましたが、おそらくrefresh()メソッドはフラッシュの変更を破棄するため、回避策はうまくいきませんでした。リスナーでもう一度フラッシュし、静的な_$alreadyFlushed_トグルを作成して循環参照を回避します。

2
Mikołaj Król

彼が言ったとき、私は@Andrew Atkinsonに同意します:

古い値と新しい値を持つエンティティフィールドにアクセスする必要がある場合は、 PreUpdate イベントを確認することもできます。

しかし、私の経験から、彼が提案した例には同意しません。何かが変わったかどうかをチェックするより良い方法があります。

<?php
class Spock
{
    public function preUpdate(PreUpdateEventArgs $eventArgs)
    {
        if (!empty($eventArgs->getEntityChangeSet())) {
            // fill this how you see fit
        }
    }
}

この方法は、実際に変更されたフィールドまたは変更されていないフィールドがある場合にのみifがトリガーされます。

このフィールドまたはそのフィールドが変更された場合の方法については、そうです、彼のソリューションをお勧めします。

0
Rafael

私はDoctrineとpostFlushをドキュメント化するすべての人に興味があります。場合によっては、進行中のトランザクションがあります。postTransactionCommitもあります。 postFlushイベントで達成しようとしています。

0
Tristan

私のニーズ、ここでの回答、および ドキュメント に基づいて、エンティティのmodifiedAtタイムスタンプの次のソリューションを思い付きました。

_/**
 * @Doctrine\ORM\Mapping\PreUpdate()
 *
 * @param \Doctrine\ORM\Event\PreUpdateEventArgs $args
 * @return $this
 */
public function preUpdateModifiedAt(\Doctrine\ORM\Event\PreUpdateEventArgs $args)
{
    $this->setModifiedAt(new \DateTime('now'));

    return $this;
}
_

これは、EventPostPersistなどの他の利用可能なものとは対照的に、このPreFlushについて ドキュメントの説明 に基づいています。

PreUpdateは、EntityManager#flush()メソッド内のエンティティに対してupdateステートメントが呼び出される直前に呼び出されるため、イベントの使用が最も制限されています。 計算されたチェンジセットが空の場合、このイベントはトリガーされないことに注意してください。

他とは対照的にPreUpdateを使用すると、すべての計算および計算集中型関数を、Doctrineによって既に定義されているプロセスに任せることができます。上記の これらanswers のように、変更セットの計算を手動でトリガーすると、サーバーのCPUに負荷がかかります。 受け入れられた答え で使用されるようなonFlushイベントはオプションです(実証された方法で)。ただし、上記の関数(preUpdateModifiedAt(PreUpdateEventArgs $args))。

0
rkeet