当前位置:网站首页>[laravel series 7.5] event system

[laravel series 7.5] event system

2022-06-11 19:27:00 Ma Nong Lao Zhang ZY

The event system

Speaking of events , What do you think ?JS The callback function in , Button callback event ? you 're right , These are all applications of events . But in the Laravel in , Events are a decoupling mechanism , yes The observer A manifestation of the pattern . It allows you to subscribe to and listen to events that occur in your application . The most typical example , When you complete the order , Need to send SMS 、 Email or in app notification , We usually use the observer pattern to implement . Events , Is the encapsulation of this operation , Very convenient and easy to use .

Register events and listeners

First, we need to create events and listeners corresponding to events . You can take event As a subscriber , Then use the listener to process the subscribed content . Generally speaking , Event at app/Events Under the table of contents , and Monitor be located app/Listeners Under the table of contents . If you are newly installed Laravel Environmental Science , These two directories may not exist , Then we can manually create , You can also directly use the command line to generate the corresponding file , These directories will be created automatically .

If you create event related file classes yourself , You need to implement some fixed methods by yourself , relatively speaking , It will be more convenient to create the command line .

php artisan make:event TestEvent 

php artisan make:listener TestListener --event=TestEvent

ad locum , We use the command line make:event Create an event class , And then use make:listener Create a listener . We specified the event object for the listener to handle , That is, the parameters passed later . The contents of the generated file are as follows :

// app/Events/TestEvent.php
class TestEvent
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct()
    {
        //
    }

    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

// app/Listeners/TestListener.php
class TestListener
{
    public function __construct()
    {
        //
    }

    public function handle(TestEvent $event)
    {
        //
    }
}

Simple events and listeners are done , You can see in the listener , our handle() The parameter received by the method is a TestEvent object . Next we need to go app/Providers/EventServiceProvider.php Of listen Register this event and listener in the array variable .

protected $listen = [

    TestEvent::class => [
        TestListener::class
    ],

    // …………
];

such , The whole event and the listener are set .

Test call events

To invoke an event , Let's start by giving events and listeners something to do . Then we can simply output something . You can add a variable to the constructor of the event .

// app/Events/TestEvent.php
public $a = '';

public function __construct($a)
{
    //
    $this->a = $a;
}

Then output the value of this variable in the listener .

// app/Listeners/TestListener.php
public function handle(TestEvent $event)
{
    //
    echo $event->a;
}

Then we trigger this event in the route .

\App\Events\TestEvent::dispatch('aaa');
\Illuminate\Support\Facades\Event::dispatch(new \App\Events\TestEvent('bbb'));
event(new \App\Events\TestEvent('ccc'));

Here we use three forms of triggering events . Using the event class itself dispatch() Method , Use Event Facade dispatch() Method , And the use of event() Auxiliary function . They all work the same , For event distribution . Pay attention to this dispatch keyword , It is a distribution , Instead of triggering . What is the meaning here ?

We said before , Event systems are used for decoupling , That is to say , You can have multiple listeners listen to the same event ( Just like Redis Medium Pub/Sub equally ), So if the event is triggered by the call , In fact, it is also distributed to multiple listeners for processing . Just like in observer mode The observer equally . our Subject Class can hold multiple Observer , When calling Subject Of notify() After method , Multiple observers can perform subsequent operations . If you are not familiar with observer mode , Or have forgotten what we said about the observer model , You can move  PHP Observer mode of design mode https://mp.weixin.qq.com/s/SlSToMIGNBtU06BWNCwWvg  Learn more about it .

Event subscribers

subscriber , What is this ? We have already seen , When invoking event distribution , Our listener will respond to the event , Then you can proceed with the subsequent processing . Generally, an event corresponds to a listener , Of course , We can also use multiple listeners to listen to the same event . So in turn , Can a listener listen to all the events ? Of course, no problem. , This is the role of event subscribers .

Event subscribers are classes that can subscribe to multiple events from the subscriber class itself , Allows you to define multiple event handlers in a single class . We need to manually create the event subscriber class , This class needs to have a subscribe() Method .

namespace App\Listeners;


use App\Events\TestEvent;
use App\Events\TestEvent2;
use App\Events\TestEvent3;

class TestSubscriber
{
    public function handleTestEvent1($event){
        echo 'This is TestEvent1', $event->a, "<br/>";
    }

    public function handleTestEvent2($event){
        echo 'This is TestEvent2', "<br/>";
    }

    public function handleTestEvent3($event){
        echo 'This is TestEvent3', "<br/>";
    }

    public function handleTestEventAll($event){
        echo "This is AllTestEvent";
        if(isset($event->a)){
            echo $event->a;
        }
        echo "<br/>";
    }


    public function subscribe($events)
    {
        $events->listen(
            [TestEvent::class, TestEvent2::class, TestEvent3::class,],
            [TestSubscriber::class, 'handleTestEventAll']
        );

        $events->listen(
            TestEvent::class,
            [TestSubscriber::class, 'handleTestEvent1']
        );

        $events->listen(
            TestEvent2::class,
            [TestSubscriber::class, 'handleTestEvent2']
        );

        $events->listen(
            TestEvent3::class,
            [TestSubscriber::class, 'handleTestEvent3']
        );
    }

}

From the command line , We created two more test event classes , Namely TestEvent2 and TestEvent3 . Then in the newly created TestSubscriber Class subscribe() Method to set their listening . adopt $events Of listen() Method , We can specify the object and method to handle these events . Be careful , We can specify multiple events to handle one event at the same time , It can also be specified individually . This event subscriber we also put on app/Listener Under the table of contents , Because the event subscriber itself is actually a big listener .

then , You need to go app/Providers/EventServiceProvider.php Add the registration of this event subscriber to the , It uses $subscribe Variable .

protected $subscribe = [
    TestSubscriber::class,
];

The rest is to test directly in the routing .

Route::get('event/test2', function(){
    \App\Events\TestEvent::dispatch('aaa');
    \App\Events\TestEvent2::dispatch();
    \App\Events\TestEvent3::dispatch();

//    aaaThis is AllTestEventaaa
//    This is TestEvent1aaa
//    This is AllTestEvent
//    This is TestEvent2
//    This is AllTestEvent
//    This is TestEvent3
});

Is the test result what you expected ? first aaa It is output by our ordinary listener above . then handleTestEventAll() Method output This is AllTestEventaaa , Then there are the event subscribers handleTestEvent1() Method output This is TestEvent1aaa . The following content is TestEvent2 and TestEvent3 Output. .

Event running process

For the running process of events , Let's start with the distribution method . By looking at the source code , You'll find that , Whether using the event class itself dispatch() Or use Event Facade dispatch() , The final execution is event() This auxiliary method , This method actually instantiates a events Alias objects . By finding the source code , We found that this method corresponds to vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php class . The final call is also in this class dispatch() Method .

public function dispatch($event, $payload = [], $halt = false)
{
    [$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;
        }

        if ($response === false) {
            break;
        }

        $responses[] = $response;
    }

    return $halt ? null : $responses;
}

From the code of foreach() Part can be easily seen , This is traversing all Monitor Then directly call the listener instance to get response result . When calling the listener , Is to pass the event class to the listener as a parameter . So we are on the monitor handle() Method to get the event object . So how does our listener load ? Of course, when the framework starts running , adopt EventServiceProvider To provide .

stay config/app.php in ,providers Array variables are configured with App\Providers\RouteServiceProvider::class , That is, the event we configure and the service provider we subscribe to , It actually inherits vendor/laravel/framework/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php This service provider . In the parent class ,register() Method internal invocation Event Facade listen() Method , This method is still vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php The method in .

public function listen($events, $listener = null)
{
    if ($events instanceof Closure) {
        return $this->listen($this->firstClosureParameterType($events), $events);
    } elseif ($events instanceof QueuedClosure) {
        return $this->listen($this->firstClosureParameterType($events->closure), $events->resolve());
    } elseif ($listener instanceof QueuedClosure) {
        $listener = $listener->resolve();
    }

    foreach ((array) $events as $event) {
        if (Str::contains($event, '*')) {
            $this->setupWildcardListen($event, $listener);
        } else {
            $this->listeners[$event][] = $this->makeListener($listener);
        }
    }
}

In this method , Through the final makeListener() Method , Create a listener and put it on listeners Array , The listener array traversed during event distribution comes from here . stay makeListener() In the method , Finally, a closure callback function is returned .

public function makeListener($listener, $wildcard = false)
{
    if (is_string($listener)) {
        return $this->createClassListener($listener, $wildcard);
    }

    if (is_array($listener) && isset($listener[0]) && is_string($listener[0])) {
        return $this->createClassListener($listener, $wildcard);
    }

    return function ($event, $payload) use ($listener, $wildcard) {
        if ($wildcard) {
            return $listener($event, $payload);
        }

        return $listener(...array_values($payload));
    };
}

public function createClassListener($listener, $wildcard = false)
{
    return function ($event, $payload) use ($listener, $wildcard) {
        if ($wildcard) {
            return call_user_func($this->createClassCallable($listener), $event, $payload);
        }

        $callable = $this->createClassCallable($listener);

        return $callable(...array_values($payload));
    };
}

Now you should know , Why is it dispatch() Methodical foreach() in , This is how we get response Of course. .

$response = $listener($event, $payload);

Keep going makeListener() Look down , Because the events and listeners we define are in string form , That is to say

TestEvent::class => [
    TestListener::class
],

Such a definition , So it will go is_string() In judgment createClassListener() Method , The internal return of this method is also a callback function , The actual listener creation ends here . Why? ? Because the callback method is only entered when we officially use it . Current listeners It is stored in the . Then when the event is distributed , We will come to this again createClassListener() Internal callback function , At this time, we will continue to look at the callback function , Its interior will continue to call createClassCallable() Method .

protected function createClassCallable($listener)
{
    [$class, $method] = is_array($listener)
                        ? $listener
                        : $this->parseClassCallable($listener);

    if (! method_exists($class, $method)) {
        $method = '__invoke';
    }

    if ($this->handlerShouldBeQueued($class)) {
        return $this->createQueuedHandlerCallable($class, $method);
    }

    $listener = $this->container->make($class);

    return $this->handlerShouldBeDispatchedAfterDatabaseTransactions($listener)
                ? $this->createCallbackForListenerRunningAfterCommits($listener, $method)
                : [$listener, $method];
}

protected function  parseClassCallable($listener)
{
    return Str::parseCallback($listener, 'handle');
}

see parseClassCallable() Method , We will find that there is handle The appearance of characters , adopt Str::parseCallback() This method will return an array . Now you must know $class, $method What do they represent .$class It is our listener class ,$method That's it. handle() Method . therefore , Finally, what we call is actually the... In the listener handle() Method .

The process of calling events and loading listeners is introduced here . The event system itself is very large , The source code is also more complex . You can see from the names of many methods in this object , The method names of the so-called elegant framework in this module are all so long , You can imagine the complexity of this component . The rest , You can further study and learn by yourself , It's better to use XDebug Debugging tools to debug it well !

summary

In addition to the simplest event operation we demonstrated , You can also use the event listener queue to handle events , This allows complete call decoupling , For example, send a text message after placing an order 、 Slow operations such as notification information , The queue can be processed slowly in the background . You can learn more about these contents by yourself , Of course , It's easy to use , But we haven't talked about the queue yet , Not much here , After learning about the queue, you can try the queue processing in the event by yourself .

Actually speaking of this , You can also see ,Laravel No embedded hook sub function is required in , This is because similar functions are implemented through events . On the whole , The event function is very easy to use , It is also very convenient to use . In your application, can you also consider applying it immediately !

Reference documents :

https://learnku.com/docs/laravel/8.5/events/10387

原网站

版权声明
本文为[Ma Nong Lao Zhang ZY]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/162/202206111923258917.html