状況を想像してみましょう。オンラインストア(エンティティ)に注文があります。 注文には特定のステータスがあります。 注文のステータスを変更する場合、次のような関連する一連のアクションを実行する必要があります。
- 注文の最後の変更の日付を保存する
- 注文のステータス変更情報を記録する
- クライアントにメール/ SMSを送信
- 配送サービス/支払いシステム/パートナーなどのAPIメソッドを呼び出します
問題は、プログラムコードの観点からこれらすべてを正しく整理する方法です。
以下で説明するものはすべて、Doctrine 2およびSymfony> 3.1で有効です
Doctrineイベントモデルに慣れていない場合は、まずドキュメントを読むことをお勧めします。
エンティティオーダーの最も簡単なコードの例を次に示します。
/** * Order * * @ORM\Table(name="order") */ class Order { /** * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(name="fio", type="string", length=100) */ private $fio; /** * @ORM\Column(name="last_update_date", type="datetime") */ private $lastUpdateDate; /** * @ORM\Column(name="create_date", type="datetime") */ private $createDate; /** * @ORM\Column(name="status_id", type="integer") */ private $statusId; // getter/setter }
最も簡単なcreate_date
から始めましょう-注文を作成するとき、作成日はcreate_date
フィールドに、注文の変更についてはlast_update_date
フィールドに、最終変更の日付が書き込まれる必要があります。
最も簡単なことは、注文が作成および更新される場所(コントローラーまたは特別なサービス)にパラメーターを明示的に追加することです。
$order = new Order(); $order->setCreateDate(new \DateTime()); $order->setLastUpdateDate(new \DateTime()); // .... $em->persist($order); $em->flush();
このアプローチの欠点は明らかです。注文が作成され、さらに複数の場所で更新される場合、すべての場所でこのロジックを繰り返す必要があります。 幸いなことに、Doctrineにはイベント処理(LifecycleEvents)が含まれています。
エンティティの説明に構造を追加し、Doctrineに、エンティティに処理が必要な特定のイベントが含まれていることを伝えます:
/** * @ORM\HasLifecycleCallbacks() */
これらのイベントに「応答する」メソッドを作成します。 この場合、2つの方法があります。
/** * @ORM\PrePersist */ public function setCreateDate() { $this->createDate = new \DateTime(); } /** * @ORM\PreFlush */ public function setLastUpdateDate() { $this->lastUpdateDate = new \DateTime(); }
@ORM\PrePersist
と@ORM\PreFlush
は、エンティティを作成するときと更新されるたびに、Doctrineに適切なメソッドを実行するように指示します。 現在、これらの日付を個別に設定する必要はありません。 可能性のあるイベントの完全なリストはここにあります。
/** * Order * * @ORM\Table(name="order") * @ORM\HasLifecycleCallbacks() */ class Order { /** * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(name="fio", type="string", length=100) */ private $fio; /** * @ORM\Column(name="last_update_date", type="datetime") */ private $lastUpdateDate; /** * @ORM\Column(name="create_date", type="datetime") */ private $createDate; /** * @ORM\Column(name="status_id", type="integer") */ private $statusId; // getter/setter /** * @ORM\PrePersist */ public function setCreateDate() { $this->createDate = new \DateTime(); } /** * @ORM\PreFlush */ public function setLastUpdateDate() { $this->lastUpdateDate = new \DateTime(); } }
タスクを複雑にするために、この注文のステータスを誰がいつ変更したかに関する情報を記録する必要があります。さらに、ステータスの変更に関する通知をクライアントに送信する必要があります。
/** * OrderHistory * * @ORM\Table(name="order_status_history") * @ORM\HasLifecycleCallbacks() */ class OrderHistory { /** * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(name="order_id", type="integer") */ private $orderId; /** * @ORM\Column(name="manager_id", type="integer") */ private $managerId; /** * @ORM\Column(name="status_id", type="integer") */ private $statusId; /** * @ORM\ManyToOne(targetEntity="OrderStatus") * @ORM\JoinColumn(name="status_id", referencedColumnName="id") */ private $status; /** * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Manager") * @ORM\JoinColumn(name="manager_id", referencedColumnName="id") */ private $manager; /** * @ORM\ManyToOne(targetEntity="Order", inversedBy="orderHistory") * @ORM\JoinColumn(name="order_id", referencedColumnName="id") */ private $order; // getter/setter /** * @ORM\Column(name="create_date", type="datetime") */ private $createDate; /** * @ORM\PrePersist */ public function setCreateDate() { $this->createDate = new \DateTime(); } }
ステータスが変化するコードの場所でこのすべてを「手動で」行うことができますが、ステータスを変更する操作の場所を参照せずにすべてが「自動的に」行われるようにしたいと思います。
これを行うために、DoctrineにはEntityListeners-変更を追跡するクラスがあります。 イベント処理のすべてのロジックを保持できる場所。
次の2つのオプションがあります。エンティティ説明レベルでイベントハンドラーを追加します。
/** * @ORM\EntityListeners({"AppBundle\EntityListeners\OrderListener"}) */
そして、リスナークラスを作成します
class OrderHistoryListener { public function postUpdate(Order $order, LifecycleEventArgs $event) { // some code } }
最初のパラメーターは、イベントが発生したオブジェクトへの参照です。 2番目はイベントオブジェクトです(以下で説明します)。
または
- イベントに応答するロジックがたくさんあるので、それを異なるクラスに分解したい
- EntityListenerは、特定のクラスのイベントだけでなく応答する必要があります(たとえば、いくつかのタイプのEntityのイベントに同じ文字を送信します)
ハンドラーは、標準のSymfonyサービスを介して登録できます。
services: order.history.status.listener: class: AppBundle\EntityListeners\OrderListener tags: - { name: doctrine.event_listener, event: preUpdate, method: preUpdate } - { name: doctrine.event_listener, event: prePersist, method: prePersist }
event
パラメーターは、このサービスが呼び出されるイベントを定義します。method-サービス内の特定のメソッドを定義します。 すなわち サービスは1つですが、エンティティごとに異なるイベントを処理します。
この場合、リスナーは一般にエンティティのイベントに応答し、クラス内ではオブジェクトのタイプを確認する必要があります。
class OrderHistoryListener { public function preUpdate(PreUpdateEventArgs $event) { if ($event->getEntity() instanceof Order) { } } }
EntityListenerには、反応を受け取るイベントに応じて、さまざまなメソッド(ハンドラー)を含めることができます。
$event
オブジェクトには、EntityManagerとUnitOfWorkへの参照が既に含まれています。 したがって、Doctrineオブジェクトで動作するすべてのものがすでにあります。 必要なオブジェクトを引き出し、更新および削除できます。
データベースに関係のない何かをしたいとき、たとえばメールを送信したいときに、困難が始まります。 これを行うには、EntityListenerの外部サービスへの依存関係を実装する必要があります。
最初のケースでは、EntityListenerに依存関係を注入するビューレコードを作成します
services: app.doctrine.listener.order: class: AppBundle\EntityListeners\OrderListener public: false arguments: ["@mailer", "@security.token_storage"] tags: - { name: "doctrine.orm.entity_listener" }
2番目では、依存関係を持つ行を追加するだけです
services: order.history.status.listener: class: AppBundle\EntityListeners\OrderListener arguments: ["@mailer", "@security.token_storage"] tags: - { name: doctrine.event_listener, event: preUpdate, method: preUpdate } - { name: doctrine.event_listener, event: prePersist, method: prePersist }
その後、すべてが通常のSymfonyサービスのようになります。
リスナー内で、フィールドが変更されたかどうかのチェックを取得し、現在および以前の値を取得することもできます。
if ($event->hasChangedField('status_id')) { $oldValue = $event->getOldValue('status_id'); $newValue = $event->getNewValue('status_id'); }
/** * Order * * @ORM\Table(name="order") * @ORM\EntityListeners({"AppBundle\EntityListeners\OrderListener"}) * @ORM\HasLifecycleCallbacks() */ class Order { /** * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(name="fio", type="string", length=100) */ private $fio; /** * @ORM\Column(name="last_update_date", type="datetime") */ private $lastUpdateDate; /** * @ORM\Column(name="create_date", type="datetime") */ private $createDate; /** * @ORM\Column(name="status_id", type="integer") */ private $statusId; // getter/setter /** * @ORM\PrePersist */ public function setCreateDate() { $this->createDate = new \DateTime(); } /** * @ORM\PreFlush */ public function setLastUpdateDate() { $this->lastUpdateDate = new \DateTime(); } }
class OrderListener { private $_securityContext = null, $_mailer = null; public function __construct(\SwiftMailer $mailer, TokenStorage $securityContext) { $this->_mailer = $mailer; $this->_securityContext = $securityContext; } public function postUpdate(Order $order, LifecycleEventArgs $event) { $em = $event->getEntityManager(); if ($event->hasChangedField('status_id')) { $status = $em->getRepository('AppBundle:OrderStatus')->find($event->getNewValue('status_id')); $history = new OrderHistory(); $history->setManager($this->_securityContext->getToken()->getUser()); $history->setStatus($status); $history->setOrder($order); $em->persist($history); $em->flush(); // SwiftMailer } } }