Laravel Event的分析和使用

第二部分 追源码
事件服务的注册

laravel中每个服务,需要先注册再启动,其中注册是一定的,启动过程可以没有。事件服务也不例外。但事件服务的注册位置较为特殊,

位于Application.php

protected function registerBaseServiceProviders()
{
# 事件服务就是在此注册的
# 注意application的register方法实际上调用了服务提供者的register方法
t h i s − > r e g i s t e r ( n e w E v e n t S e r v i c e P r o v i d e r ( this->register(new EventServiceProvider( this>register(newEventServiceProvider(this));
t h i s − > r e g i s t e r ( n e w L o g S e r v i c e P r o v i d e r ( this->register(new LogServiceProvider( this>register(newLogServiceProvider(this));
t h i s − > r e g i s t e r ( n e w R o u t i n g S e r v i c e P r o v i d e r ( this->register(new RoutingServiceProvider( this>register(newRoutingServiceProvider(this));
}

事件服务提供者 Illuminate\Events\EventServiceProvider

public function register()
{
# 注意此处的singleton绑定 后面会使用到
t h i s − > a p p − > s i n g l e t o n ( ′ e v e n t s ′ , f u n c t i o n ( this->app->singleton('events', function ( this>app>singleton(events,function(app) {
// 绑定的是一个disaptcher实例 并为事件服务设置了队列解析器
// 注意此闭包是在我们尝试从容器中解析事件服务的时候才会执行
return (new Dispatcher( a p p ) ) − > s e t Q u e u e R e s o l v e r ( f u n c t i o n ( ) u s e ( app))->setQueueResolver(function () use ( app))>setQueueResolver(function()use(app) {
return $app->make(QueueFactoryContract::class);
});
});
}

看Illuminate\Events\Dispatcher类

简单的构造方法

public function __construct(ContainerContract $container = null)
{
$this->container = $container ?: new Container;
}

setQueueResolver方法 一个简单的set

public function setQueueResolver(callable $resolver)
{
$this->queueResolver = $resolver;

return $this;

}

可以看到事件服务的注册实际上是向容器中注册了一个事件的分发器

事件服务的启动一(获取所有的事件和监听者)

框架启动的过程中会调用app/Providers下所有服务提供者的boot方法,事件服务也不例外。

App\Providers\EventServiceProvider文件
class EventServiceProvider extends ServiceProvider
{
# 此数组键为事件名,值为事件的监听者
# 事件服务的启动阶段会读取此配置,将所有的事件和事件监听者对应起来并挂载到事件分发器Dispatcher上
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
];

# 事件服务启动真正调用的方法 可以看到调用了父类的boot方法
# 也可以在boot方法中向事件分发器中自行绑定事件和监听者
public function boot()
{
    parent::boot();

    //
}

}

EventServiceProvider的父类

注册事件监听器

public function boot()
{
// getEvents方法 获取事件和监听器
$events = $this->getEvents();

foreach ($events as $event => $listeners) {
    foreach (array_unique($listeners) as $listener) {
        // 此处Event facade对应的是Dispatcher的listen方法
        // facade的原理和使用之前介绍过
        Event::listen($event, $listener);
    }
}

foreach ($this->subscribe as $subscriber) {
    // 调用的是Dispatcher的subscribe方法
    Event::subscribe($subscriber);
}

}

getEvents方法

public function getEvents()
{
if ($this->app->eventsAreCached()) {
$cache = require $this->app->getCachedEventsPath();

    return $cache[get_class($this)] ?? [];
} else {
    return array_merge_recursive(
        // 如果事件非常多,也可以设置事件和监听者的目录,让框架自行帮助查找
        // 如果需要开启discoveredEvents功能,需要在App\Providers\EventServiceProvider中
       	// 重写shouldDiscoverEvents方法 并返回true 代表开启事件自动发现
       	// 如果需要指定事件和监听者的目录,需要重写discoverEventsWithin方法,其中返回目录数组
       	// 当然你也可以全部写在listen属性中
        // 当重写了以上两个方法的时候 返回的数组和$listen属性的格式是完全一致的 以事件名称为key 监听者为value
        $this->discoveredEvents(),
        
        // 返回的就是App\Providers\EventServiceProvider下的listen数组
        $this->listens()
    );
}

}

discoveredEvents方法 此方法触发的前提是重写了shouldDiscoverEvents方法

public function discoverEvents()
{
// 使用了laravel提供的collect辅助函数 文档有详细章节介绍
// collect函数返回collection集合实例方便我们链式操作
// reject方法的作用是 回调函数返回 true 就会把对应的集合项从集合中移除
// reduce方法的作用是 将每次迭代的结果传递给下一次迭代直到集合减少为单个值
return collect($this->discoverEventsWithin())
// discoverEventsWithin方法返回查找事件监听者的目录数组
// 默认返回 (array) t h i s − > a p p − > p a t h ( ′ L i s t e n e r s ′ ) / / 我 们 自 然 可 以 重 写 d i s c o v e r E v e n t s W i t h i n 方 法 , 返 回 我 们 指 定 的 监 听 者 目 录 − > r e j e c t ( f u n c t i o n ( this->app->path('Listeners') // 我们自然可以重写discoverEventsWithin方法,返回我们指定的监听者目录 ->reject(function ( this>app>path(Listeners)//discoverEventsWithin>reject(function(directory) {
// 移除集合中不是目录的元素
return ! is_dir(KaTeX parse error: Expected 'EOF', got '}' at position 21: …tory); }̲) ->red…discovered, $directory) {
return array_merge_recursive(
d i s c o v e r e d , / / 使 用 S y m f o n y 的 F i n d e r 组 件 查 找 L i s t e n e r 文 件 D i s c o v e r E v e n t s : : w i t h i n ( discovered, // 使用Symfony的Finder组件查找Listener文件 DiscoverEvents::within( discovered,//使SymfonyFinderListenerDiscoverEvents::within(directory, base_path())
);
}, []);
}

Illuminate\Foundation\Events\DiscoverEvents::within方法

提取给定目录中的全部监听者

public static function within($listenerPath, KaTeX parse error: Expected '}', got 'EOF' at end of input: …)->files()->in(listenerPath), b a s e P a t h ) ) − > m a p T o D i c t i o n a r y ( f u n c t i o n ( basePath ))->mapToDictionary(function ( basePath))>mapToDictionary(function(event, KaTeX parse error: Expected '}', got 'EOF' at end of input: … return [event => $listener];
})->all();
}

protected static function getListenerEvents($listeners, $basePath)
{
$listenerEvents = [];
// l i s t e n e r s 是 F i n d e r 组 件 返 回 指 定 目 录 下 的 迭 代 器 , 遍 历 可 以 拿 到 目 录 下 的 所 有 文 件 f o r e a c h ( listeners是Finder组件返回指定目录下的迭代器,遍历可以拿到目录下的所有文件 foreach ( listenersFinderforeach(listeners as $listener) {
try {
l i s t e n e r = n e w R e f l e c t i o n C l a s s ( / / 将 绝 对 路 径 转 换 为 类 名 s t a t i c : : c l a s s F r o m F i l e ( listener = new ReflectionClass( // 将绝对路径转换为类名 static::classFromFile( listener=newReflectionClass(//static::classFromFile(listener, $basePath)
);
} catch (ReflectionException $e) {
continue;
}

    if (! $listener->isInstantiable()) {
        continue;
    }

    // dump($listener->getMethods(ReflectionMethod::IS_PUBLIC));

    foreach ($listener->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
        // 代表着一个监听者类中 可以设置多个监听器
        if (! Str::is('handle*', $method->name) ||
            ! isset($method->getParameters()[0])) {
            continue;
        }
		
        $listenerEvents[$listener->name.'@'.$method->name] =
            // 可以认为此处返回的是事件名
            // 写在handle*方法中的参数 我建议一定要加上类型提示,并且将类型名参数作为第一个参数传入
            Reflector::getParameterClassName($method->getParameters()[0]);
    }
}

// 过滤事件参数名为空的监听器并返回
return array_filter($listenerEvents);

}
事件服务的启动二(注册事件监听者)

上面获取了全部的事件监听者 下面就要注册这些事件监听者了

继续看EventServiceProvider::boot方法

使用php artisan event:list 可以查看框架中已经注册的事件

php artisan event:cache php artisan event:clear

public function boot()
{
# 拿到了$listen属性和要求自动发现的所有事件(如果开启了自动发现的话)
$events = t h i s − > g e t E v e n t s ( ) ; / / d u m p ( this->getEvents(); // dump( this>getEvents();//dump(events);

foreach ($events as $event => $listeners) {
    foreach (array_unique($listeners) as $listener) {
        // 调用dispatcher的listen方法
        // 事件名为key 事件监听者为value 进行事件的注册监听
        Event::listen($event, $listener);
    }
}

foreach ($this->subscribe as $subscriber) {
    // subscribe方法请自行查看
    Event::subscribe($subscriber);
}

}

Dispatcher::listen方法

遍历getEvents中所有的事件和监听者 通过实际调用Dispatcher的makeListener创建监听者

以event名为键 创建的监听者闭包为值 保存在数组属性中 供事件触发的时候查找调用

// 向调度器注册事件监听器
public function listen($events, KaTeX parse error: Expected '}', got 'EOF' at end of input: … { // dump(events, $listener);
foreach ((array) $events as KaTeX parse error: Expected '}', got 'EOF' at end of input: …(Str::contains(event, ‘*’)) {
t h i s − > s e t u p W i l d c a r d L i s t e n ( this->setupWildcardListen( this>setupWildcardListen(event, $listener);
} else {
// 正常事件名
t h i s − > l i s t e n e r s [ this->listeners[ this>listeners[event][] = t h i s − > m a k e L i s t e n e r ( this->makeListener( this>makeListener(listener);
}
}
}

绑定事件和监听者闭包的映射

protected function setupWildcardListen($event, $listener)
{
// 当一系列的事件都想触发指定的监听者的时候 就可以使用*进行匹配
t h i s − > w i l d c a r d s [ this->wildcards[ this>wildcards[event][] = t h i s − > m a k e L i s t e n e r ( this->makeListener( this>makeListener(listener, true);
// 每次更新了通配事件后 都清除缓存
$this->wildcardsCache = [];
}

官方注释表名此方法向事件分发器中注册一个事件监听者

其实就是返回事件触发时执行的监听者闭包

传入的listener可以使App\Listener\MyListener 或 App\Listener\MyListener@myHandle这种字符串

或者是一个接收两个参数的闭包

public function makeListener($listener, KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (is_string(listener)) {
// 如果传递的是一个字符串的话 调用createClassListener放回闭包
return t h i s − > c r e a t e C l a s s L i s t e n e r ( this->createClassListener( this>createClassListener(listener, $wildcard);
}

// 如果listener是个闭包 那么直接将事件对象作为参数传入监听者
// 事件触发的时候 直接执行此闭包
return function ($event, $payload) use ($listener, $wildcard) {
    if ($wildcard) {
        return $listener($event, $payload);
    }

    // 可变数量的参数列表
    return $listener(...array_values($payload));
};

}

createClassListener方法

public function createClassListener($listener, KaTeX parse error: Expected '}', got 'EOF' at end of input: …turn function (event, p a y l o a d ) u s e ( payload) use ( payload)use(listener, KaTeX parse error: Expected '}', got 'EOF' at end of input: … { if (wildcard) {
// createClassCallable返回一个数组 第一个参数是 l i s t e n e r 的 实 例 第 二 个 参 数 是 m e t h o d r e t u r n c a l l u s e r f u n c ( listener的实例 第二个参数是method return call_user_func( listenermethodreturncalluserfunc(this->createClassCallable($listener), $event, $payload);
}

    return call_user_func_array(
        $this->createClassCallable($listener), $payload
    );
};

}

createClassCallable方法

protected function createClassCallable(KaTeX parse error: Expected '}', got 'EOF' at end of input: …中获取类名和方法名 [class, $method] = t h i s − > p a r s e C l a s s C a l l a b l e ( this->parseClassCallable( this>parseClassCallable(listener);

// 判断是否需要队列化监听器
if ($this->handlerShouldBeQueued($class)) {
    // class类名 method 方法名
    return $this->createQueuedHandlerCallable($class, $method);
}

// 如果不需要异步化执行监听者 直接返回[$listener, 'method']数组
// class通过container获得 意味着我们可以利用容器方便的注入listner需要的依赖
// 注意此处返回的是listener的实例 和 调用监听者时执行的方法名
return [$this->container->make($class), $method];

}

handlerShouldBeQueued方法 判断如果一个监听者实现了ShouldQueue接口 就认为此监听者需要队列化执行

protected function handlerShouldBeQueued(KaTeX parse error: Expected '}', got 'EOF' at end of input: …eflectionClass(class))->implementsInterface(
ShouldQueue::class
);
} catch (Exception $e) {
return false;
}
}

createQueuedHandlerCallable方法

protected function createQueuedHandlerCallable($class, KaTeX parse error: Expected '}', got 'EOF' at end of input: …nction () use (class, $method) {
a r g u m e n t s = a r r a y m a p ( f u n c t i o n ( arguments = array_map(function ( arguments=arraymap(function(a) {
return is_object($a) ? clone $a : KaTeX parse error: Expected 'EOF', got '}' at position 12: a; }̲, func_get_args…this->handlerWantsToBeQueued($class, $arguments)) {
t h i s − > q u e u e H a n d l e r ( this->queueHandler( this>queueHandler(class, $method, $arguments);
}
};
}

handlerWantsToBeQueued

protected function handlerWantsToBeQueued($class, $arguments)
{
$instance = t h i s − > c o n t a i n e r − > m a k e ( this->container->make( this>container>make(class);

// 动态判断是否需要异步化事件处理
// 需要我们在监听器shouldQueue方法中return bool值
if (method_exists($instance, 'shouldQueue')) {
    // 可以在监听者的shouldQueue方法中返回bool值 动态判断是否需要异步化
    return $instance->shouldQueue($arguments[0]);
}

return true;

}

queueHandler方法

// 判断listener的各种属性 将监听者投递到队列
// laravel 队列以后会单独讲解 此篇先到这里
protected function queueHandler($class, $method, KaTeX parse error: Expected '}', got 'EOF' at end of input: …uments) { [listener, $job] = t h i s − > c r e a t e L i s t e n e r A n d J o b ( this->createListenerAndJob( this>createListenerAndJob(class, $method, $arguments);
// resolveQueue获取注册事件服务时设置的queueResolver
$connection = $this->resolveQueue()->connection(
$listener->connection ?? null
);

$queue = $listener->queue ?? null;

isset($listener->delay)
    ? $connection->laterOn($queue, $listener->delay, $job)
    : $connection->pushOn($queue, $job);

}

以上便是事件注册的基本代码 总体来说 我们看到调用Dispatcher的listen方法 可以注册监听者和事件的绑定

监听者都已闭包的形式进行包裹 这样的好处是可以保存上下文变量

涉及到的异步处理 其他文章会进行讲解

值得注意的是 注册好的闭包 并不会执行 当触发相应的事件时才会执行

事件的触发

业务代码中调用event()方法就可以触发一个事件了 执行的就是Dispatch::dispatch方法

public function dispatch($event, $payload = [], KaTeX parse error: Expected '}', got 'EOF' at end of input: …eEvent()) [event, $payload] = $this->parseEventAndPayload(
$event, $payload
);

if ($this->shouldBroadcast($payload)) {
    $this->broadcastEvent($payload[0]);
}

$responses = [];

foreach ($this->getListeners($event) as $listener) {
    // 执行每个监听者闭包
    $response = $listener($event, $payload);

    if ($halt && ! is_null($response)) {
        // 直接返回结果给事件触发
        return $response;
    }

    // 如果某个监听者返回了false 那么终止后续监听者的执行
    if ($response === false) {
        break;
    }

    $responses[] = $response;
}
// 返回结果给事件触发
return $halt ? null : $responses;

}

parseEventAndPayload方法

protected function parseEventAndPayload($event, KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (is_object(event)) {
[$payload, e v e n t ] = [ [ event] = [[ event]=[[event], get_class($event)];
}

// 如果event是一个字符串 那么直接包装payload
return [$event, Arr::wrap($payload)];

}

// 获取所有事件监听者
public function getListeners($eventName)
{
$listeners = t h i s − > l i s t e n e r s [ this->listeners[ this>listeners[eventName] ?? [];

$listeners = array_merge(
    $listeners,
    $this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName)
);

// 如果插入的event类存在
return class_exists($eventName, false)
    ? $this->addInterfaceListeners($eventName, $listeners)
    : $listeners;

}

addInterfaceListeners方法

protected function addInterfaceListeners($eventName, array KaTeX parse error: Expected '}', got 'EOF' at end of input: …ass_implements(eventName) as KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (isset(this->listeners[KaTeX parse error: Expected '}', got 'EOF' at end of input: … foreach (this->listeners[$interface] as $names) {
l i s t e n e r s = a r r a y m e r g e ( listeners = array_merge( listeners=arraymerge(listeners, (array) $names);
}
}
}
// 返回合并后的监听者
return $listeners;
}

部分其他方法

until方法

触发事件 并返回第一个不为null的监听器结果

public function until($event, $payload = [])
{
return t h i s − > d i s p a t c h ( this->dispatch( this>dispatch(event, $payload, true);
}

push方法

调用的还是listen方法 只不过指定了payload参数

public function push($event, $payload = [])
{
t h i s − > l i s t e n ( this->listen( this>listen(event.’_pushed’, function () use ($event, $payload) {
t h i s − > d i s p a t c h ( this->dispatch( this>dispatch(event, $payload);
});
}

flush方法 调用push注册的监听者

public function flush($event)
{
t h i s − > d i s p a t c h ( this->dispatch( this>dispatch(event.’_pushed’);
}
第三部分 使用
使用一 通过触发事件给监听者传参

1 在App\Porviders\EventServiceProvider的listen属性中绑定事件和监听者映射关系

use App\Events\TestEvent1;
use App\Listeners\TestListener1;
use App\Listeners\TestListener2;

protected $listen = [

TestEvent1::class => [
TestListener1::class,
// 自定义监听者闭包调用的方法myHandle
TestListener2::class . ‘@myHandle’
]
];

2 php artisan event:generate 按照listen数组的事件监听者映射生成

3 我们不在TestEvent1事件中做过多处理 在本示例中保持原样即可

4 编写TestListener1文件

<?php namespace App\Listeners; use App\Components\Log\LogManager; use App\Events\TestEvent1; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; use App\Providers\LogManagerServiceProvider; class TestListener1 { protected $logger; // 通过上面的分析 我们知道监听者时通过容器解析出来的 所以可以尽情的注入 public function __construct(LogManager $logger) { $this->logger = $logger; } // 自定义传参给事件监听者 public function handle(TestEvent1 $event, string $type) { // dump($type); // dump(debug_backtrace()); $this->logger->driver($type)->logCertains('emergency', 'something emergency'); } } 5 编写TestListener2文件 <?php namespace App\Listeners; use App\Events\TestEvent1; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; class TestListener2 { /** * Create the event listener. * * @return void */ public function __construct() { // } // 其实指定了myHandle方法之后 原生的handle方法就可以注释或者什么都不写了 // 通过上面的分析我们知道 框架会执行监听者中所有以handle开头的方法 // public function handle(TestEvent1 $event) // { // echo '345'; // } // 此方法也是会执行的 // public function handleabc(TestEvent1 $event) // { // echo 'abc'; // } public function myHandle(TestEvent1 $event) { dump('do whatever you like'); } } 6 编写测试路由 触发事件 Route::get('event1', function () { // 情况一 传递的是事件实例 如果你需要在事件实例中注入依赖 当然可以使用容器解析事件对象 // 当注入的是一个事件对象的时候 会触发事件类名这个事件 并且将事件对象作为payload传递给handle方法 // Event::dispatch(new \App\Events\TestEvent1()); // 情况二 传递的是事件名 第二个参数生效 // 演示如何不依赖事件对象 传递参数 Event::dispatch(TestEvent1::class, [new TestEvent1(), 'stream']); }); 使用二 设置事件自动发现 1 在App\Porviders\EventServiceProvider中重写shouldDiscoverEvents方法 启用事件发现 // 启用事件自动发现 public function shouldDiscoverEvents() { return true; } 2 我们更近一步 设置自动监听者所在目录 重写discoverEventsWithin方法 指定自动发现目录 // 指定自动发现目录 // 默认的就是app_path('Listeners') public function discoverEventsWithin() { return [ // 这里可以注释掉Listeners目录为了避免和上面的listen属性重复 导致所有的监听者都会执行两遍 // app_path('Listeners'), app_path('MyListeners') ]; } 3 编写事件App\Events\TestEvent2文件 我这里的代码没有任何的实际意义 <?php namespace App\Events; class TestEvent2 { protected $name = 'xy'; public function __construct() { // 随意发挥 } public function getName() { return $this->name; } } 4 手动创建App\MyListeners\Listener1文件 # 通过上面的源码分析 我们知道laravel会将所有以handle开头的方法参数遍历 # 然后将第一个参数的类名作为要触发的事件名,将事件参数作为payload传入 <?php namespace App\MyListeners; use App\Events\TestEvent2; class MyListener1 { public function handle(TestEvent2 $evt) { dump($evt->getName(), 'abc'); return false; // 如果不注释掉此行代码,事件的调用链到此终结 } public function handleAbc(TestEvent2 $evt) { dump($evt->getName()); } } 5 手动创建App\MyListeners\Listener2文件 <?php namespace App\MyListeners; use App\Events\TestEvent2; class MyListener2 { public function handle(TestEvent2 $evt) { dump($evt->getName()); } } 6 创建事件自动发现路由 // 测试自动发现 Route::get('event2', function(){ Event::dispatch(new TestEvent2()); }); 使用三 implement的使用 当事件实现了其他事件接口,会自动触发其他事件绑定的监听者 对应的方法为Dispatcher::addInterfaceListeners 请看第二部分 1 创建事件接口 <?php namespace App\Events; interface TestEvent3 { } <?php namespace App\Events; interface TestEvent4 { } 2 创建监听者 <?php namespace App\Listeners; class TestListener3 { public function handle() { dump('listener3 added by event interface3'); } } <?php namespace App\Listeners; class TestListener4 { public function handle() { dump('listener3 added by event interface4'); } } <?php namespace App\Listeners; class TestListener5 implements TestEvent3, TestEvent4 { public function handle() { dump('five own listener'); } } 3 事件实现上面的两个接口 <?php namespace App\Events; class TestEvent5 implements TestEvent3, TestEvent4 { } 4 注册事件监听者 protected $listen = [ ... TestEvent3::class => [ TestListener3::class ], TestEvent4::class => [ TestListener4::class ], # 甚至可以注释掉下面3行 只需要TestEvent5实现上面两个接口即可触发上面注册的监听者 TestEvent5::class => [ TestListener5::class ] ]; 5 最重要的一步 force and brutal 改源码 没错 就是改源码 # Dispatcher::getListeners方法 ... // return class_exists($eventName, false) return class_exists($eventName) ? $this->addInterfaceListeners($eventName, $listeners) : $listeners; ... 6 创建测试路由 Route::get('event5', function () { Event::dispatch(TestEvent5::class); }); 使用四 until和flush until方法默认调用dispatch方法 当时间监听者返回不为null则停止执行后面的监听者 并返回结果给事件触发位置 1 配置时间监听者 protected $listen = [ ... TestEvent6::class => [ TestListener6::class, TestListener7::class, TestListener8::class ] ]; 2 php artisan event:generate 3 简单编写事件监听者 # listener6 public function handle(TestEvent6 $event) { dump('return null'); } # listener7 public function handle(TestEvent6 $event) { // 注意此监听者是有返回值的 return 123; } # listener8 public function handle(TestEvent6 $event) { // 并不会执行7后面的监听者 根本就不会执行 return 'return something in vain'; } 4 编写测试路由 Route::get('event6', function () { $res = Event::until(new TestEvent6()); // 可以看到监听者8并没有执行 因为7返回的不是null dump($res); }); 使用五 push&flush 请查看上面的源码分析 push方法就是提前将event和payload注册好 供flush调用 1 在App\Providers\EventServiceProvider::boot方法中注册(这里只是举例在boot中进行注册,你可以在你喜欢的任何地方注册) public function boot() { parent::boot(); // 注册一个保存了payload的事件监听者 Event::push('longAssEventName', ['type' => 'redis']); Event::listen('longAssEventName', function ($type) { dump($type); }); } 2 创建测试路由 Route::get('event7', function () { Event::flush('longAssEventName'); }); 龙华大道1号 http://www.kinghill.cn/Dynamics/2106.html
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的Laravel事件案例,以便更好地了解Laravel事件的使用: 1. 定义事件 在Laravel中,您可以使用`make:event` Artisan命令来创建一个事件类。例如,我们将创建一个名为`OrderShipped`的事件类: ``` php artisan make:event OrderShipped ``` 这将在`app/Events`目录中创建一个名为`OrderShipped`的事件类。 2. 定义事件发布者 事件发布者是触发事件的类。在我们的案例中,让我们假设`Order`类在订单发货时触发`OrderShipped`事件。我们可以在`Order`类中使用Laravel的`dispatch`方法来发布事件: ```php use App\Events\OrderShipped; class Order { public function ship() { // 订单发货逻辑 // 发布事件 event(new OrderShipped($this)); } } ``` 3. 定义事件订阅者 事件订阅者是响应事件的类。在我们的案例中,让我们定义一个类来记录日志并发送电子邮件: ```php namespace App\Listeners; use App\Events\OrderShipped; class SendShipmentNotification { public function handle(OrderShipped $event) { // 记录日志 Log::info('Order shipped with ID: ' . $event->order->id); // 发送电子邮件 Mail::to($event->order->customer->email)->send(new OrderShippedEmail($event->order)); } } ``` 在上面的代码中,我们定义了一个`SendShipmentNotification`类来订阅`OrderShipped`事件。当事件被触发时,`handle`方法将被调用,记录日志并发送电子邮件。 4. 注册事件订阅者 最后,我们需要在Laravel应用程序中注册事件订阅者。我们可以在`EventServiceProvider`类中使用`$listen`属性来注册订阅者: ```php namespace App\Providers; use Illuminate\Support\Facades\Event; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider { protected $listen = [ OrderShipped::class => [ SendShipmentNotification::class, ], ]; public function boot() { parent::boot(); } } ``` 在上面的代码中,我们将`OrderShipped`事件订阅到`SendShipmentNotification`类。这将导致`SendShipmentNotification`类在`OrderShipped`事件被触发时执行其`handle`方法。 现在,当我们在`Order`类中调用`ship`方法时,它将触发`OrderShipped`事件并导致`SendShipmentNotification`类执行其`handle`方法,记录日志并发送电子邮件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值