🎻

[Symfony][Doctrine] エンティティの特定のプロパティが変更されているかどうかを調べる

2021/12/16に公開

Symfony Advent Calendar 2021 の16日目の記事です!🎄🌙小ネタですみません!

ちなみに、僕はよく TwitterにもSymfonyネタを呟いている ので、よろしければぜひ フォローしてやってください🕊🤲

昨日は @77web さんの SymfonyUX Turboを使ってみる でした✨

はじめに

この記事はほぼ以下の過去記事の焼き増しです😂

[Symfony] DoctrineのpreUpdateで他のエンティティの生成をやろうとしたけどできなかった話(解決策あり)

通常、「エンティティの特定のプロパティが変更されていたらこういう処理をする」といったものは EntityListenerpreUpdate に書くことが多いと思います。

しかし、上記の過去記事でも解説しているとおり、preUpdate のタイミングでは 別のエンティティを生成することはできません。

なので、「エンティティAのプロパティXが変更されていたらエンティティBを生成する」といった処理(例えば、変更の履歴をエンティティとして残す等)を実現したい場合、

  • 自分で EventEventSubscriber(または 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() しているところがポイントで、これをやらないとこの時点ではまだチェンジセットが空になっています。

参考:php - Is there a built-in way to get all of the changed/updated fields in a Doctrine 2 entity - Stack Overflow

ちなみに 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];
    }
}

https://twitter.com/ttskch/status/1440577292808323072

これを作っておくと、コントローラのコードは以下のように非常にスッキリさせることができます。

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 さんです!お楽しみに!

GitHubで編集を提案

Discussion