[Symfony][Doctrine] エンティティの特定のプロパティが変更されているかどうかを調べる
Symfony Advent Calendar 2021 の16日目の記事です!🎄🌙小ネタですみません!
ちなみに、僕はよく TwitterにもSymfonyネタを呟いている ので、よろしければぜひ フォローしてやってください🕊🤲
昨日は @77web さんの SymfonyUX Turboを使ってみる でした✨
はじめに
この記事はほぼ以下の過去記事の焼き増しです😂
[Symfony] DoctrineのpreUpdateで他のエンティティの生成をやろうとしたけどできなかった話(解決策あり)
通常、「エンティティの特定のプロパティが変更されていたらこういう処理をする」といったものは EntityListener の preUpdate
に書くことが多いと思います。
しかし、上記の過去記事でも解説しているとおり、preUpdate
のタイミングでは 別のエンティティを生成することはできません。
なので、「エンティティAのプロパティXが変更されていたらエンティティBを生成する」といった処理(例えば、変更の履歴をエンティティとして残す等)を実現したい場合、
- 自分で
Event
とEventSubscriber
(またはEventListener
)を実装する - その
EventSubscriber
(またはEventListener
)でエンティティBを生成するようにする - コントローラでエンティティAのプロパティXの変更を検知し、変更されていたらその
Event
をディスパッチするようにする
という実装が必要になります。
特定のプロパティが変更されているかどうかを調べる方法
上記の
コントローラでエンティティAのプロパティXの変更を検知し、
の部分の実装方法について解説します。
結論としては、例えば $entity->property
が変更されているかどうかを調べるには、以下のようなコードを書けばよいです。
$em->getUnitOfWork()->computeChangeSets();
$changeSets = $em->getUnitOfWork()->getEntityChangeSet($entity);
if (isset($changeSets['property']) && $changeSets['property'][0] !== $changeSets['property'][1]) {
// $entity->property が変更されている
}
このコードは、Doctrineの UnitOfWork
から getEntityChangeSet()
で変更内容を取得し、変更内容の一覧の中に 'property'
プロパティがあり、内容も確かに変化しているかどうかを調べています。
getEntityChangeSet()
する前に、1行目で $em->getUnitOfWork()->computeChangeSets()
しているところがポイントで、これをやらないとこの時点ではまだチェンジセットが空になっています。
ちなみに UnitOfWork
とかがピンと来ない方は 後藤さんのこのポスト がめちゃめちゃ分かりやすいのでぜひ読んでみてください。
使い回しやすいようにサービスクラスにしておく
複雑なアプリだとこの変更検知の処理が色々なコントローラに登場することもよくあるので、使い回しやすいようにサービスクラスにしておくとよいです。
僕はいつも下記のようなクラスを作ります。
// src/Doctrine/ChangeDetector.php
namespace App\Doctrine;
use Doctrine\ORM\EntityManagerInterface;
class ChangeDetector
{
private EntityManagerInterface $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function isChanged($entity, string $property): bool
{
$this->em->getUnitOfWork()->computeChangeSets();
$changeSets = $this->em->getUnitOfWork()->getEntityChangeSet($entity);
return isset($changeSets[$property]) && $changeSets[$property][0] !== $changeSets[$property][1];
}
}
これを作っておくと、コントローラのコードは以下のように非常にスッキリさせることができます。
public function edit(Foo $foo, ChangeDetector $detector, EventDispatcherInterface $dispatcher)
{
$form = $this->createForm(FooType::class, $foo);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// $foo->bar が変更されていたらイベントを発行
if ($detector->isChanged($foo, 'bar')) {
$dispatcher->dispatch(new \App\Event\Foo\BarChangedEvent($foo));
}
// ...
}
// ...
}
まとめ
というわけで、Symfonyでエンティティの特定のプロパティが変更されているかどうかを調べる方法について簡単に解説しました。どこかの誰かのお役に立てば幸いです!
Symfony Advent Calendar 2021、明日は @kin29ma_n さんです!お楽しみに!
Discussion