123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574 |
- <?php
-
- namespace Illuminate\Events;
-
- use Exception;
- use ReflectionClass;
- use Illuminate\Support\Arr;
- use Illuminate\Support\Str;
- use Illuminate\Container\Container;
- use Illuminate\Contracts\Queue\ShouldQueue;
- use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
- 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 cached wildcard listeners.
- *
- * @var array
- */
- protected $wildcardsCache = [];
-
- /**
- * The queue resolver instance.
- *
- * @var callable
- */
- protected $queueResolver;
-
- /**
- * Create a new event dispatcher instance.
- *
- * @param \Illuminate\Contracts\Container\Container|null $container
- * @return void
- */
- public function __construct(ContainerContract $container = null)
- {
- $this->container = $container ?: new Container;
- }
-
- /**
- * Register an event listener with the dispatcher.
- *
- * @param string|array $events
- * @param mixed $listener
- * @return void
- */
- public function listen($events, $listener)
- {
- foreach ((array) $events as $event) {
- if (Str::contains($event, '*')) {
- $this->setupWildcardListen($event, $listener);
- } else {
- $this->listeners[$event][] = $this->makeListener($listener);
- }
- }
- }
-
- /**
- * Setup a wildcard listener callback.
- *
- * @param string $event
- * @param mixed $listener
- * @return void
- */
- protected function setupWildcardListen($event, $listener)
- {
- $this->wildcards[$event][] = $this->makeListener($listener, true);
-
- $this->wildcardsCache = [];
- }
-
- /**
- * 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]);
- }
-
- /**
- * 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');
- }
-
- /**
- * Register an event subscriber with the dispatcher.
- *
- * @param object|string $subscriber
- * @return void
- */
- public function subscribe($subscriber)
- {
- $subscriber = $this->resolveSubscriber($subscriber);
-
- $subscriber->subscribe($this);
- }
-
- /**
- * Resolve the subscriber instance.
- *
- * @param object|string $subscriber
- * @return mixed
- */
- protected function resolveSubscriber($subscriber)
- {
- if (is_string($subscriber)) {
- return $this->container->make($subscriber);
- }
-
- return $subscriber;
- }
-
- /**
- * 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);
- }
-
- /**
- * Fire an event and call the listeners.
- *
- * @param string|object $event
- * @param mixed $payload
- * @param bool $halt
- * @return array|null
- */
- public function fire($event, $payload = [], $halt = false)
- {
- return $this->dispatch($event, $payload, $halt);
- }
-
- /**
- * 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;
- }
-
- /**
- * Parse the given event and payload and prepare them for dispatching.
- *
- * @param mixed $event
- * @param mixed $payload
- * @return array
- */
- protected function parseEventAndPayload($event, $payload)
- {
- if (is_object($event)) {
- list($payload, $event) = [[$event], get_class($event)];
- }
-
- return [$event, Arr::wrap($payload)];
- }
-
- /**
- * Determine if the payload has a broadcastable event.
- *
- * @param array $payload
- * @return bool
- */
- protected function shouldBroadcast(array $payload)
- {
- return isset($payload[0]) &&
- $payload[0] instanceof ShouldBroadcast &&
- $this->broadcastWhen($payload[0]);
- }
-
- /**
- * 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;
- }
-
- /**
- * Broadcast the given event class.
- *
- * @param \Illuminate\Contracts\Broadcasting\ShouldBroadcast $event
- * @return void
- */
- protected function broadcastEvent($event)
- {
- $this->container->make(BroadcastFactory::class)->queue($event);
- }
-
- /**
- * 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->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName)
- );
-
- return class_exists($eventName, false)
- ? $this->addInterfaceListeners($eventName, $listeners)
- : $listeners;
- }
-
- /**
- * Get the wildcard listeners for the event.
- *
- * @param string $eventName
- * @return array
- */
- protected function getWildcardListeners($eventName)
- {
- $wildcards = [];
-
- foreach ($this->wildcards as $key => $listeners) {
- if (Str::is($key, $eventName)) {
- $wildcards = array_merge($wildcards, $listeners);
- }
- }
-
- return $this->wildcardsCache[$eventName] = $wildcards;
- }
-
- /**
- * Add the listeners for the event's interfaces to the given array.
- *
- * @param string $eventName
- * @param array $listeners
- * @return array
- */
- 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;
- }
-
- /**
- * Register an event listener with the dispatcher.
- *
- * @param \Closure|string $listener
- * @param bool $wildcard
- * @return \Closure
- */
- 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));
- };
- }
-
- /**
- * Create a class based listener using the IoC container.
- *
- * @param string $listener
- * @param bool $wildcard
- * @return \Closure
- */
- 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
- );
- };
- }
-
- /**
- * Create the class based event callable.
- *
- * @param string $listener
- * @return callable
- */
- protected function createClassCallable($listener)
- {
- list($class, $method) = $this->parseClassCallable($listener);
-
- if ($this->handlerShouldBeQueued($class)) {
- return $this->createQueuedHandlerCallable($class, $method);
- }
-
- return [$this->container->make($class), $method];
- }
-
- /**
- * Parse the class listener into class and method.
- *
- * @param string $listener
- * @return array
- */
- protected function parseClassCallable($listener)
- {
- return Str::parseCallback($listener, 'handle');
- }
-
- /**
- * Determine if the event handler class should be queued.
- *
- * @param string $class
- * @return bool
- */
- protected function handlerShouldBeQueued($class)
- {
- try {
- return (new ReflectionClass($class))->implementsInterface(
- ShouldQueue::class
- );
- } catch (Exception $e) {
- return false;
- }
- }
-
- /**
- * Create a callable for putting an event handler on the queue.
- *
- * @param string $class
- * @param string $method
- * @return \Closure
- */
- 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);
- }
- };
- }
-
- /**
- * Determine if the event handler wants to be queued.
- *
- * @param string $class
- * @param array $arguments
- * @return bool
- */
- protected function handlerWantsToBeQueued($class, $arguments)
- {
- if (method_exists($class, 'shouldQueue')) {
- return $this->container->make($class)->shouldQueue($arguments[0]);
- }
-
- return true;
- }
-
- /**
- * Queue the handler class.
- *
- * @param string $class
- * @param string $method
- * @param array $arguments
- * @return void
- */
- 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);
- }
-
- /**
- * Create the listener and job for a queued listener.
- *
- * @param string $class
- * @param string $method
- * @param array $arguments
- * @return array
- */
- protected function createListenerAndJob($class, $method, $arguments)
- {
- $listener = (new ReflectionClass($class))->newInstanceWithoutConstructor();
-
- return [$listener, $this->propagateListenerOptions(
- $listener, new CallQueuedListener($class, $method, $arguments)
- )];
- }
-
- /**
- * Propagate listener options to the job.
- *
- * @param mixed $listener
- * @param mixed $job
- * @return mixed
- */
- protected function propagateListenerOptions($listener, $job)
- {
- return tap($job, function ($job) use ($listener) {
- $job->tries = $listener->tries ?? null;
- $job->timeout = $listener->timeout ?? null;
- $job->timeoutAt = method_exists($listener, 'retryUntil')
- ? $listener->retryUntil() : null;
- });
- }
-
- /**
- * 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);
- }
- }
- }
-
- /**
- * Get the queue implementation from the resolver.
- *
- * @return \Illuminate\Contracts\Queue\Queue
- */
- protected function resolveQueue()
- {
- return call_user_func($this->queueResolver);
- }
-
- /**
- * Set the queue resolver implementation.
- *
- * @param callable $resolver
- * @return $this
- */
- public function setQueueResolver(callable $resolver)
- {
- $this->queueResolver = $resolver;
-
- return $this;
- }
- }
|