Pick up the source code of eventserviceprovider

Time:2021-2-22

With the previous “Introduction to the use of laravel model events”https://mp.weixin.qq.com/s/XrhDq1S5RC9UdeULVVksoAI have a general ideaEventThe use of.

Today we’re going to have a grillEventSource code of.

Before we start, we need to say the next twoEventServiceProviderThe differences between them are as follows:

  • App\Providers\EventServiceProvider
  • Illuminate\Events\EventServiceProvider

firstApp\Providers\EventServiceProviderMainly the definitioneventandlistenerA second connection; a second connectionIlluminate\Events\EventServiceProvideryesLaravelThe three foundations of the constructionServiceProviderOne is mainly responsible for “assignment”.

OK, let’s start the specific analysis.

App\Providers\EventServiceProvider

Mainly the definitioneventandlistenerFor example:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        'App\Events\RssPublicEvent' => [
            'App\Listeners\RssPublicListener1',
        ],
        'App\Events\*Event' => [
            'App\Listeners\RssPublicListener2',
            'App\Listeners\RssPublicListener3',
        ],
        'Illuminate\Contracts\Broadcasting\ShouldBroadcast' => [
            'App\Listeners\RssPublicListener4',
            'App\Listeners\RssPublicListener5',
        ],
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot()
    {
        parent::boot();
    }
}

Main inheritanceIlluminate\Foundation\Support\Providers\EventServiceProvider

<?php

namespace Illuminate\Foundation\Support\Providers;

use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event handler mappings for the application.
     *
     * @var array
     */
    protected $listen = [];

    /**
     * The subscriber classes to register.
     *
     * @var array
     */
    protected $subscribe = [];

    /**
     * Register the application's event listeners.
     *
     * @return void
     */
    public function boot()
    {
        foreach ($this->listens() as $event => $listeners) {
            foreach ($listeners as $listener) {
                Event::listen($event, $listener);
            }
        }

        foreach ($this->subscribe as $subscriber) {
            Event::subscribe($subscriber);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function register()
    {
        //
    }

    /**
     * Get the events and handlers.
     *
     * @return array
     */
    public function listens()
    {
        return $this->listen;
    }
}

DefineeventandlistenerLet the user do the association, and then the parent classEventServiceProviderIt’s just related work, in theboot()Medium:

public function boot()
{
    foreach ($this->listens() as $event => $listeners) {
        foreach ($listeners as $listener) {
            Event::listen($event, $listener);
        }
    }

    foreach ($this->subscribe as $subscriber) {
        Event::subscribe($subscriber);
    }
}

Here are two functions:

  • Event::listen($event, $listener);
  • Event::subscribe($subscriber);

That’s it. Let’s finish the first oneEventServiceProviderLet’s start the second one.

Illuminate\Events\EventServiceProvider

I read the previous article,EventThere is a global function:

Artisan::command('public_echo', function () {
    event(new RssPublicEvent());
})->describe('echo demo');

...

if (! function_exists('event')) {
    /**
     * Dispatch an event and call the listeners.
     *
     * @param  string|object  $event
     * @param  mixed  $payload
     * @param  bool  $halt
     * @return array|null
     */
    function event(...$args)
    {
        return app('events')->dispatch(...$args);
    }
}

andIlluminate\Events\EventServiceProviderYesLaravelThree foundationsServiceProviderone of:

/**
 * Register all of the base service providers.
 *
 * @return void
 */
protected function registerBaseServiceProviders()
{
    $this->register(new EventServiceProvider($this));

    $this->register(new LogServiceProvider($this));

    $this->register(new RoutingServiceProvider($this));
}

Let’s go onIlluminate\Events\EventServiceProvider

<?php

namespace Illuminate\Events;

use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Queue\Factory as QueueFactoryContract;

class EventServiceProvider extends ServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('events', function ($app) {
            return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
                return $app->make(QueueFactoryContract::class);
            });
        });
    }
}

It registers the singleton form and creates and returns itDispatcherParticipants:

use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Contracts\Broadcasting\Factory as BroadcastFactory;
use Illuminate\Contracts\Container\Container as ContainerContract;
class Dispatcher implements DispatcherContract
{
    /**
     * The IoC container instance.
     *
     * @var \Illuminate\Contracts\Container\Container
     */
    protected $container;

    /**
     * The registered event listeners.
     *
     * @var array
     */
    protected $listeners = [];

    /**
     * The wildcard listeners.
     *
     * @var array
     */
    protected $wildcards = [];

    /**
     * The queue resolver instance.
     *
     * @var callable
     */
    protected $queueResolver;
    
...
}    

Main implementationDispatcherInterface:

<?php

namespace Illuminate\Contracts\Events;

interface Dispatcher
{
    /**
     * Register an event listener with the dispatcher.
     *
     * @param  string|array  $events
     * @param  mixed  $listener
     * @return void
     */
    public function listen($events, $listener);

    /**
     * Determine if a given event has listeners.
     *
     * @param  string  $eventName
     * @return bool
     */
    public function hasListeners($eventName);

    /**
     * Register an event subscriber with the dispatcher.
     *
     * @param  object|string  $subscriber
     * @return void
     */
    public function subscribe($subscriber);

    /**
     * Dispatch an event until the first non-null response is returned.
     *
     * @param  string|object  $event
     * @param  mixed  $payload
     * @return array|null
     */
    public function until($event, $payload = []);

    /**
     * Dispatch an event and call the listeners.
     *
     * @param  string|object  $event
     * @param  mixed  $payload
     * @param  bool  $halt
     * @return array|null
     */
    public function dispatch($event, $payload = [], $halt = false);

    /**
     * Register an event and payload to be fired later.
     *
     * @param  string  $event
     * @param  array  $payload
     * @return void
     */
    public function push($event, $payload = []);

    /**
     * Flush a set of pushed events.
     *
     * @param  string  $event
     * @return void
     */
    public function flush($event);

    /**
     * Remove a set of listeners from the dispatcher.
     *
     * @param  string  $event
     * @return void
     */
    public function forget($event);

    /**
     * Forget all of the queued listeners.
     *
     * @return void
     */
    public function forgetPushed();
}

Now let’s explain each function.

listen()

Register an event listener with the dispatcher.

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

This is easy to understand, put the wildcard in thewildcardsArray, and the other one is placed in thelistenersArray. Next, let’s look at the functionmakeListener()

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

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

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

If the incoming$listenerIs a string, the function is executedcreateClassListener

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

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

Let’s look at the function firstcreateClassCallable()

protected function createClassCallable($listener)
{
    list($class, $method) = Str::parseCallback($listener, 'handle');

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

    return [$this->container->make($class), $method];
}

The first function is still easy to understand

public static function parseCallback($callback, $default = null)
{
    return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
}

Just look at what’s coming inlistenerRight?[email protected]Structure, if it is, use it@Split, otherwise the default isclassClass name, and thenmethodIt’s the defaulthandleFunction – that’s what we createdListenerClass.

Then we will see if it can be put into the queue:

protected function handlerShouldBeQueued($class)
{
    try {
        return (new ReflectionClass($class))->implementsInterface(
            ShouldQueue::class
        );
    } catch (Exception $e) {
        return false;
    }
}

That’s to say, what should we dolistenerDoes the class implement an interface classShouldQueue. If implemented, you can put the class in the queue (return the closure function)

protected function createQueuedHandlerCallable($class, $method)
{
    return function () use ($class, $method) {
        $arguments = array_map(function ($a) {
            return is_object($a) ? clone $a : $a;
        }, func_get_args());

        if ($this->handlerWantsToBeQueued($class, $arguments)) {
            $this->queueHandler($class, $method, $arguments);
        }
    };
}

Let’s go onhandlerWantsToBeQueued

protected function handlerWantsToBeQueued($class, $arguments)
{
    if (method_exists($class, 'shouldQueue')) {
        return $this->container->make($class)->shouldQueue($arguments[0]);
    }

    return true;
}

So, if in thelistenerClassshouldQueueMethod, it depends on whether the method returnstrueperhapsfalseTo decide whether to put in the queue:

protected function queueHandler($class, $method, $arguments)
{
    list($listener, $job) = $this->createListenerAndJob($class, $method, $arguments);

    $connection = $this->resolveQueue()->connection(
        $listener->connection ?? null
    );

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

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

Note:And the queue related to the analysis later, omitted here

Well, back to the beginning:

// createClassCallable($listener)
return [$this->container->make($class), $method];

At this point, I understand that if it iswildcardThe corresponding execution function (default is:handle)There are two parameters passed in:$eventEvent objects and$payloadOtherwise, only one parameter will be passed in for the corresponding execution function:$payload

Similarly, if the incominglistenerIf it is a function, the closure returned is as follows:

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

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

The whole process goes through,listenerThe function is: in theDispatcherIn$listenersand$wildcardsTo store the['event' => Callback]For execution.

That’s the first functionEvent::listen()The second function is:Event::subscribe()Keep it for later.

All right, the whole thingeventandlistenerIt’s connected. Next, let’s look at the execution method.

dispatch()

Dispatch an event and call the listeners.

As mentioned abovehelpersDefined, allEventIs associated with the “distribute” event and call through this functionlisteners

/**
 * Fire an event and call the listeners.
 *
 * @param  string|object  $event
 * @param  mixed  $payload
 * @param  bool  $halt
 * @return array|null
 */
public function dispatch($event, $payload = [], $halt = false)
{
    // When the given "event" is actually an object we will assume it is an event
    // object and use the class as the event name and this event itself as the
    // payload to the handler, which makes object based events quite simple.
    list($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 a response is returned from the listener and event halting is enabled
        // we will just return this response, and not call the rest of the event
        // listeners. Otherwise we will add the response on the response list.
        if ($halt && ! is_null($response)) {
            return $response;
        }

        // If a boolean false is returned from a listener, we will stop propagating
        // the event to any further listeners down in the chain, else we keep on
        // looping through the listeners and firing every one in our sequence.
        if ($response === false) {
            break;
        }

        $responses[] = $response;
    }

    return $halt ? null : $responses;
}

Understand the annotated function firstparseEventAndPayload()

When the given “event” is actually an object we will assume it is an event object and use the class as the event name and this event itself as the payload to the handler, which makes object based events quite simple.

protected function parseEventAndPayload($event, $payload)
{
    if (is_object($event)) {
        list($payload, $event) = [[$event], get_class($event)];
    }

    return [$event, Arr::wrap($payload)];
}

If$eventIs an object$eventClass name as the name of the event[$event]As$payload

Then judge$payloadIs it possible to broadcast? If so, broadcast directly:

protected function shouldBroadcast(array $payload)
{
    return isset($payload[0]) &&
           $payload[0] instanceof ShouldBroadcast &&
           $this->broadcastWhen($payload[0]);
}

Take the example above

<?php

namespace App\Events;

use Carbon\Carbon;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class RssPublicEvent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('public_channel');
    }

    /**
     *Specifies the broadcast data.
     *
     * @return array
     */
    public function broadcastWith()
    {
        //Return to current time
        return ['name' => 'public_channel_'.Carbon::now()->toDateTimeString()];
    }
}

First, it implements the interfaceShouldBroadcastAnd then see if there are any additional conditions to decide whether it can be broadcast:

/**
 * Check if event should be broadcasted by condition.
 *
 * @param  mixed  $event
 * @return bool
 */
protected function broadcastWhen($event)
{
    return method_exists($event, 'broadcastWhen')
            ? $event->broadcastWhen() : true;
}

Because this instance is not implementedbroadcastWhenMethod, so the default value is returnedtrue

So you can broadcast this instance:

/**
 * Broadcast the given event class.
 *
 * @param  \Illuminate\Contracts\Broadcasting\ShouldBroadcast  $event
 * @return void
 */
protected function broadcastEvent($event)
{
    $this->container->make(BroadcastFactory::class)->queue($event);
}

It’s up to youBroadcastManagerTo deal with, this article will not continue to dig.

notesLet’s pick up the next articleBroadcastManagerSource code

When the event is broadcast, we start to monitor the event. Through the previous article, we know that aEvent, more than oneListenerMonitor, so you need to go through aforeachLoop to traverse the executionListener, get these firstListener

/**
 * Get all of the listeners for a given event name.
 *
 * @param  string  $eventName
 * @return array
 */
public function getListeners($eventName)
{
    $listeners = $this->listeners[$eventName] ?? [];

    $listeners = array_merge(
        $listeners, $this->getWildcardListeners($eventName)
    );

    return class_exists($eventName, false)
                ? $this->addInterfaceListeners($eventName, $listeners)
                : $listeners;
}

In this method, all the data can be accumulated in three wayslisteners: properties in this class:$listenersand$wildcards, and if the$eventIs an object, including all the interfaces associated with the classlistenersArray.

protected function addInterfaceListeners($eventName, array $listeners = [])
{
    foreach (class_implements($eventName) as $interface) {
        if (isset($this->listeners[$interface])) {
            foreach ($this->listeners[$interface] as $names) {
                $listeners = array_merge($listeners, (array) $names);
            }
        }
    }

    return $listeners;
}

Note:class_ Implements – returns all interfaces implemented by the specified class.

The next step is to execute eachlistenerThe following is true:

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

// If a response is returned from the listener and event halting is enabled
// we will just return this response, and not call the rest of the event
// listeners. Otherwise we will add the response on the response list.
if ($halt && ! is_null($response)) {
    return $response;
}

// If a boolean false is returned from a listener, we will stop propagating
// the event to any further listeners down in the chain, else we keep on
// looping through the listeners and firing every one in our sequence.
if ($response === false) {
    break;
}

$responses[] = $response;

It can be seen from the above$listenerIs actually a closure function, and the final result is equivalent to the executionhandleFunction:

public function handle(RssPublicEvent $event)
{
    info('listener 1');
}

...

public function handle(RssPublicEvent $event, array $payload)
{
    info('listener 2');
}

Write ademo

Let’s write a storydemo, inEventServiceProviderOflistenArray, fill in the association of the three methods:

protected $listen = [
    'App\Events\RssPublicEvent' => [
        'App\Listeners\RssPublicListener1',
    ],
    'App\Events\*Event' => [
        'App\Listeners\RssPublicListener2',
        'App\Listeners\RssPublicListener3',
    ],
    'Illuminate\Contracts\Broadcasting\ShouldBroadcast' => [
        'App\Listeners\RssPublicListener4',
        'App\Listeners\RssPublicListener5',
    ],
];

And then in eachRssPublicListener*OfhandleMethod output the corresponding value, and finally runphp artisan public_echoSee the results:

[2018-10-06 20:05:57] local.INFO: listener 1  
[2018-10-06 20:05:58] local.INFO: listener 2  
[2018-10-06 20:05:59] local.INFO: listener 3  
[2018-10-06 20:05:59] local.INFO: listener 4  
[2018-10-06 20:06:00] local.INFO: listener 5

Other functions

When we finish executing the function, we basically finish the whole processEventThe flow of events has changed. There are only some auxiliary functions left, which can be understood at a glance

/**
 * Register an event and payload to be fired later.
 *
 * @param  string  $event
 * @param  array  $payload
 * @return void
 */
public function push($event, $payload = [])
{
    $this->listen($event.'_pushed', function () use ($event, $payload) {
        $this->dispatch($event, $payload);
    });
}

/**
 * Flush a set of pushed events.
 *
 * @param  string  $event
 * @return void
 */
public function flush($event)
{
    $this->dispatch($event.'_pushed');
}

/**
 * Determine if a given event has listeners.
 *
 * @param  string  $eventName
 * @return bool
 */
public function hasListeners($eventName)
{
    return isset($this->listeners[$eventName]) || isset($this->wildcards[$eventName]);
}

/**
 * Fire an event until the first non-null response is returned.
 *
 * @param  string|object  $event
 * @param  mixed  $payload
 * @return array|null
 */
public function until($event, $payload = [])
{
    return $this->dispatch($event, $payload, true);
}

/**
     * Remove a set of listeners from the dispatcher.
 *
 * @param  string  $event
 * @return void
 */
public function forget($event)
{
    if (Str::contains($event, '*')) {
        unset($this->wildcards[$event]);
    } else {
        unset($this->listeners[$event]);
    }
}

/**
 * Forget all of the pushed listeners.
 *
 * @return void
 */
public function forgetPushed()
{
    foreach ($this->listeners as $key => $value) {
        if (Str::endsWith($key, '_pushed')) {
            $this->forget($key);
        }
    }
}

summary

yesEventThe next step is to see how to combine with queue and use the code logic of “observer mode”.

Recommended Today

Redis design and implementation 4: Dictionary Dict

In redis, the dictionary is the infrastructure. Redis database data, expiration time and hash type all take the dictionary as the underlying structure. Structure of dictionary Hashtable The implementation code of hash table is as follows:dict.h/dictht The dictionary of redis is implemented in the form of hash table. typedef struct dictht { //Hash table array, […]