理解 Doctrine 2 事件

什么是Doctrine Event?

Doctrine Event事件是指在Doctrine 执行中为执行某些额外动作,定义的触发方式。

例如,当用户注册帐号后,系统向他发送注册成功邮件。 实现这个业务最简单的方法是在注册成功后增加一段逻辑代码去发送邮件,这样做的话两个业务被混合在一起,如果修改密码后也要发送邮件,那只能修改用户修改密码的业务逻辑实现。现在通过Doctrine事件功能可以非常轻松的解耦代码,发送邮件的逻辑调整不需要改动用户部分业务代码。

如果系统逻辑稍微复杂后,那么通过Doctrine事件功能对逻辑进行解耦会是非常必要的。

Doctrine Event 事件类型

  • preRemove – 在执行给定实体的相应EntityManager删除操作之前,先执行preRemove事件。DQL DELETE语句不调用它。
  • postRemove – 删除实体后,执行postRemove事件。数据库删除操作后将调用它。DQL DELETE语句不调用它。
  • prePersist – 在执行给定实体的相应EntityManager持久化操作之前,先执行prePersist事件。应当注意,此事件仅在实体的初始持久存在时触发(即,它不会在将来的更新时触发)。
  • postPersist – 实体持久化后,实体将发生postPersist事件。数据库插入操作后将调用它。生成的主键值在postPersist事件中可用。
  • preUpdate – 对实体数据进行数据库更新操作之前执行。DQL UPDATE语句不调用它。
  • postUpdate – 在数据库对Entity数据进行更新操作之后,将发生postUpdate事件。DQL UPDATE语句不调用它。
  • postLoad – 在Entity从数据库加载到当前的EntityManager中或对其执行刷新操作之后,该实体就会发生postLoad事件。
  • loadClassMetadata – 在从映射源(annotations / xml / yaml)加载了类的映射元数据之后,执行loadClassMetadata事件。此事件不是生命周期回调。
  • preFlush – preFlush事件在Flush操作开始时发生。此事件不是生命周期回调
  • onFlush –计算所有管理实体的change-sets之后,将执行onFlush事件。此事件不是生命周期回调
  • postFlush – postFlush事件在Flush操作结束时发生。此事件不是生命周期回调
  • onClear –在从工作单元中删除了对实体的所有引用之后,调用EntityManager#clear()操作时将执行onClear事件。此事件不是生命周期回调。

四种事件类型:

  • Lifecycle callbacks,定义在实体类(Entity 或 Orm)上的方法,并在事件触发时被调用;
  • Lifecycle listeners 和 subscribers,监听事件和订阅事件是带有一个或多个事件的回调方法的类,并且为所有实体调用;
  • Entity listeners,类似于Lifecycle callbacks,但仅针对特定类的实体调用它们。

缺点和优点

  • Lifecycle callbacks(生命周期回调) 具有更好的性能,因为它们仅适用于单个实体类,但是您不能为不同的实体重用逻辑,并且它们无权访问Symfony服务;
  • Lifecycle listeners 和 subscribers (生命周期监听订阅) 可以在不同实体之间重用逻辑,并且可以访问Symfony服务,但是它们的性能较差,因为它们被所有实体调用。
  • Entity listeners(对象监听) 与生命周期侦听器具有相同的优点,并且它们具有更好的性能,因为它们仅适用于单个实体类。

Lifecycle Callbacks(生命周期回调)

示例

# config/doctrine/Product.orm.yml
App\Entity\Product:
    type: entity
    # ...
    lifecycleCallbacks:
        prePersist: ['setCreatedAtValue']

Product Entity

// src/Entity/Product.php
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Event\LifecycleEventArgs;
class Product
{

    // ...

    public function setCreatedAtValue(LifecycleEventArgs $args)
    {
        //当前对象
        $entity = $args->getEntity();
        //对象实例化前处理的逻辑
        $this->createdAt = new \DateTime();
    }
}

Lifecycle Listeners(生命周期监听事件)

# config/services.yaml

services:
    # ...
    App\EventListener\ProductChange:
        tags:
            -
                name: 'doctrine.event_listener'
                # 指定监听事件的类型
                event: 'postPersist'
                # 监听事件优先级
                # 同一事件 (默认优先级 = 0; 数值越大运行的越早)
                priority: 500
                # 指定数据库连接
                connection: 'default'

PHP 监听器

// src/EventListener/ProductChange
namespace App\EventListener;
use App\Entity\Product;
use Doctrine\Persistence\Event\LifecycleEventArgs;
class ProductChange
{
    // 参数 $args 可获取到监听对象和 entityManager
    public function postPersist(LifecycleEventArgs $args)
    {
        $entity = $args->getObject();
        // 必须判断对象类型,因为此监听事件是框架级的,任何的对象实体化后调用此监听事件
        if ($entity instanceof Product) {
            $entityManager = $args->getObjectManager();
            // ... do something 可以处理非当前对象
        }
        return;
    }
}

Entity Listeners(对象实体监听事件)

YAML

# config/services.yaml
services:
    # ...
    App\EventListener\ProductChange:
        tags:
            -
                name: 'doctrine.orm.entity_listener'
                event: 'postUpdate'
                entity: 'App\Entity\Product'
                # 将 "lazy "选项设置为 "TRUE"时,只有在使用监听器时才会实例化它们。
                lazy: true
                # 指定entityManager
                entity_manager: 'custom'
                # 默认 Symfony 会寻找在事件后调用的方法(例如postUpdate())
                # 如果它不存在,它就会尝试执行'__invoke()'方法,"method" 选项自定义方法
                method: 'checkUserChanges'

监听器

// src/EventListener/ProductChange.php

namespace App\EventListener;
use App\Entity\Product;
use Doctrine\Persistence\Event\LifecycleEventArgs;
class ProductChange
{
    // 参数:当前对象实体和生命周期事件
    public function postUpdate(Product $product, LifecycleEventArgs $event)
    {
        // ... do something to notify the changes
    }

}

Lifecycle Subscribers(生命周期订阅事件)

YAML

# config/services.yaml

services:
    # ...
    App\EventListener\ProductChangeSubscriber:
        tags:
            - { name: 'doctrine.event_subscriber', connection: 'default' }

订阅器

namespace App\EventListener;

use App\Entity\Product;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Events;
use Doctrine\Persistence\Event\LifecycleEventArgs;
class ProductChangeSubscriber implements EventSubscriber
{
    // 只能返回事件名称,不能自定义
    public function getSubscribedEvents()
    {
        return [
            Events::postPersist,
            Events::postRemove,
            Events::postUpdate,
        ];
    }

    // 回调方法的调用必须与它们所监听的事件完全相同。
    // 通过 LifecycleEventArgs 访问实体对象,调用EntityManager
    public function postPersist(LifecycleEventArgs $args)
    {
        $this->logActivity('persist', $args);
    }

    public function postRemove(LifecycleEventArgs $args)
    {
        $this->logActivity('remove', $args);
    }

    public function postUpdate(LifecycleEventArgs $args)
    {
        $this->logActivity('update', $args);
    }

    private function logActivity(string $action, LifecycleEventArgs $args)
    {
        $entity = $args->getObject();
        // ... do somthing
        if (!$entity instanceof Product) {
            return;
        }
        // ... get the entity information and log it somehow
    }
}

参考文章:

https://symfony.com/doc/current/doctrine/events.html#doctrine-lifecycle-callbacks
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/events.html#lifecycle-callbacks-event-argument
https://culttt.com/2014/08/04/understanding-doctrine-2-lifecycle-events/

发表评论