Dispatcher.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. <?php
  2. namespace Illuminate\Events;
  3. use Exception;
  4. use ReflectionClass;
  5. use Illuminate\Support\Arr;
  6. use Illuminate\Support\Str;
  7. use Illuminate\Container\Container;
  8. use Illuminate\Contracts\Queue\ShouldQueue;
  9. use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
  10. use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
  11. use Illuminate\Contracts\Broadcasting\Factory as BroadcastFactory;
  12. use Illuminate\Contracts\Container\Container as ContainerContract;
  13. class Dispatcher implements DispatcherContract
  14. {
  15. /**
  16. * The IoC container instance.
  17. *
  18. * @var \Illuminate\Contracts\Container\Container
  19. */
  20. protected $container;
  21. /**
  22. * The registered event listeners.
  23. *
  24. * @var array
  25. */
  26. protected $listeners = [];
  27. /**
  28. * The wildcard listeners.
  29. *
  30. * @var array
  31. */
  32. protected $wildcards = [];
  33. /**
  34. * The cached wildcard listeners.
  35. *
  36. * @var array
  37. */
  38. protected $wildcardsCache = [];
  39. /**
  40. * The queue resolver instance.
  41. *
  42. * @var callable
  43. */
  44. protected $queueResolver;
  45. /**
  46. * Create a new event dispatcher instance.
  47. *
  48. * @param \Illuminate\Contracts\Container\Container|null $container
  49. * @return void
  50. */
  51. public function __construct(ContainerContract $container = null)
  52. {
  53. $this->container = $container ?: new Container;
  54. }
  55. /**
  56. * Register an event listener with the dispatcher.
  57. *
  58. * @param string|array $events
  59. * @param mixed $listener
  60. * @return void
  61. */
  62. public function listen($events, $listener)
  63. {
  64. foreach ((array) $events as $event) {
  65. if (Str::contains($event, '*')) {
  66. $this->setupWildcardListen($event, $listener);
  67. } else {
  68. $this->listeners[$event][] = $this->makeListener($listener);
  69. }
  70. }
  71. }
  72. /**
  73. * Setup a wildcard listener callback.
  74. *
  75. * @param string $event
  76. * @param mixed $listener
  77. * @return void
  78. */
  79. protected function setupWildcardListen($event, $listener)
  80. {
  81. $this->wildcards[$event][] = $this->makeListener($listener, true);
  82. $this->wildcardsCache = [];
  83. }
  84. /**
  85. * Determine if a given event has listeners.
  86. *
  87. * @param string $eventName
  88. * @return bool
  89. */
  90. public function hasListeners($eventName)
  91. {
  92. return isset($this->listeners[$eventName]) || isset($this->wildcards[$eventName]);
  93. }
  94. /**
  95. * Register an event and payload to be fired later.
  96. *
  97. * @param string $event
  98. * @param array $payload
  99. * @return void
  100. */
  101. public function push($event, $payload = [])
  102. {
  103. $this->listen($event.'_pushed', function () use ($event, $payload) {
  104. $this->dispatch($event, $payload);
  105. });
  106. }
  107. /**
  108. * Flush a set of pushed events.
  109. *
  110. * @param string $event
  111. * @return void
  112. */
  113. public function flush($event)
  114. {
  115. $this->dispatch($event.'_pushed');
  116. }
  117. /**
  118. * Register an event subscriber with the dispatcher.
  119. *
  120. * @param object|string $subscriber
  121. * @return void
  122. */
  123. public function subscribe($subscriber)
  124. {
  125. $subscriber = $this->resolveSubscriber($subscriber);
  126. $subscriber->subscribe($this);
  127. }
  128. /**
  129. * Resolve the subscriber instance.
  130. *
  131. * @param object|string $subscriber
  132. * @return mixed
  133. */
  134. protected function resolveSubscriber($subscriber)
  135. {
  136. if (is_string($subscriber)) {
  137. return $this->container->make($subscriber);
  138. }
  139. return $subscriber;
  140. }
  141. /**
  142. * Fire an event until the first non-null response is returned.
  143. *
  144. * @param string|object $event
  145. * @param mixed $payload
  146. * @return array|null
  147. */
  148. public function until($event, $payload = [])
  149. {
  150. return $this->dispatch($event, $payload, true);
  151. }
  152. /**
  153. * Fire an event and call the listeners.
  154. *
  155. * @param string|object $event
  156. * @param mixed $payload
  157. * @param bool $halt
  158. * @return array|null
  159. */
  160. public function dispatch($event, $payload = [], $halt = false)
  161. {
  162. // When the given "event" is actually an object we will assume it is an event
  163. // object and use the class as the event name and this event itself as the
  164. // payload to the handler, which makes object based events quite simple.
  165. [$event, $payload] = $this->parseEventAndPayload(
  166. $event, $payload
  167. );
  168. if ($this->shouldBroadcast($payload)) {
  169. $this->broadcastEvent($payload[0]);
  170. }
  171. $responses = [];
  172. foreach ($this->getListeners($event) as $listener) {
  173. $response = $listener($event, $payload);
  174. // If a response is returned from the listener and event halting is enabled
  175. // we will just return this response, and not call the rest of the event
  176. // listeners. Otherwise we will add the response on the response list.
  177. if ($halt && ! is_null($response)) {
  178. return $response;
  179. }
  180. // If a boolean false is returned from a listener, we will stop propagating
  181. // the event to any further listeners down in the chain, else we keep on
  182. // looping through the listeners and firing every one in our sequence.
  183. if ($response === false) {
  184. break;
  185. }
  186. $responses[] = $response;
  187. }
  188. return $halt ? null : $responses;
  189. }
  190. /**
  191. * Parse the given event and payload and prepare them for dispatching.
  192. *
  193. * @param mixed $event
  194. * @param mixed $payload
  195. * @return array
  196. */
  197. protected function parseEventAndPayload($event, $payload)
  198. {
  199. if (is_object($event)) {
  200. [$payload, $event] = [[$event], get_class($event)];
  201. }
  202. return [$event, Arr::wrap($payload)];
  203. }
  204. /**
  205. * Determine if the payload has a broadcastable event.
  206. *
  207. * @param array $payload
  208. * @return bool
  209. */
  210. protected function shouldBroadcast(array $payload)
  211. {
  212. return isset($payload[0]) &&
  213. $payload[0] instanceof ShouldBroadcast &&
  214. $this->broadcastWhen($payload[0]);
  215. }
  216. /**
  217. * Check if event should be broadcasted by condition.
  218. *
  219. * @param mixed $event
  220. * @return bool
  221. */
  222. protected function broadcastWhen($event)
  223. {
  224. return method_exists($event, 'broadcastWhen')
  225. ? $event->broadcastWhen() : true;
  226. }
  227. /**
  228. * Broadcast the given event class.
  229. *
  230. * @param \Illuminate\Contracts\Broadcasting\ShouldBroadcast $event
  231. * @return void
  232. */
  233. protected function broadcastEvent($event)
  234. {
  235. $this->container->make(BroadcastFactory::class)->queue($event);
  236. }
  237. /**
  238. * Get all of the listeners for a given event name.
  239. *
  240. * @param string $eventName
  241. * @return array
  242. */
  243. public function getListeners($eventName)
  244. {
  245. $listeners = $this->listeners[$eventName] ?? [];
  246. $listeners = array_merge(
  247. $listeners,
  248. $this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName)
  249. );
  250. return class_exists($eventName, false)
  251. ? $this->addInterfaceListeners($eventName, $listeners)
  252. : $listeners;
  253. }
  254. /**
  255. * Get the wildcard listeners for the event.
  256. *
  257. * @param string $eventName
  258. * @return array
  259. */
  260. protected function getWildcardListeners($eventName)
  261. {
  262. $wildcards = [];
  263. foreach ($this->wildcards as $key => $listeners) {
  264. if (Str::is($key, $eventName)) {
  265. $wildcards = array_merge($wildcards, $listeners);
  266. }
  267. }
  268. return $this->wildcardsCache[$eventName] = $wildcards;
  269. }
  270. /**
  271. * Add the listeners for the event's interfaces to the given array.
  272. *
  273. * @param string $eventName
  274. * @param array $listeners
  275. * @return array
  276. */
  277. protected function addInterfaceListeners($eventName, array $listeners = [])
  278. {
  279. foreach (class_implements($eventName) as $interface) {
  280. if (isset($this->listeners[$interface])) {
  281. foreach ($this->listeners[$interface] as $names) {
  282. $listeners = array_merge($listeners, (array) $names);
  283. }
  284. }
  285. }
  286. return $listeners;
  287. }
  288. /**
  289. * Register an event listener with the dispatcher.
  290. *
  291. * @param \Closure|string $listener
  292. * @param bool $wildcard
  293. * @return \Closure
  294. */
  295. public function makeListener($listener, $wildcard = false)
  296. {
  297. if (is_string($listener)) {
  298. return $this->createClassListener($listener, $wildcard);
  299. }
  300. return function ($event, $payload) use ($listener, $wildcard) {
  301. if ($wildcard) {
  302. return $listener($event, $payload);
  303. }
  304. return $listener(...array_values($payload));
  305. };
  306. }
  307. /**
  308. * Create a class based listener using the IoC container.
  309. *
  310. * @param string $listener
  311. * @param bool $wildcard
  312. * @return \Closure
  313. */
  314. public function createClassListener($listener, $wildcard = false)
  315. {
  316. return function ($event, $payload) use ($listener, $wildcard) {
  317. if ($wildcard) {
  318. return call_user_func($this->createClassCallable($listener), $event, $payload);
  319. }
  320. return call_user_func_array(
  321. $this->createClassCallable($listener), $payload
  322. );
  323. };
  324. }
  325. /**
  326. * Create the class based event callable.
  327. *
  328. * @param string $listener
  329. * @return callable
  330. */
  331. protected function createClassCallable($listener)
  332. {
  333. [$class, $method] = $this->parseClassCallable($listener);
  334. if ($this->handlerShouldBeQueued($class)) {
  335. return $this->createQueuedHandlerCallable($class, $method);
  336. }
  337. return [$this->container->make($class), $method];
  338. }
  339. /**
  340. * Parse the class listener into class and method.
  341. *
  342. * @param string $listener
  343. * @return array
  344. */
  345. protected function parseClassCallable($listener)
  346. {
  347. return Str::parseCallback($listener, 'handle');
  348. }
  349. /**
  350. * Determine if the event handler class should be queued.
  351. *
  352. * @param string $class
  353. * @return bool
  354. */
  355. protected function handlerShouldBeQueued($class)
  356. {
  357. try {
  358. return (new ReflectionClass($class))->implementsInterface(
  359. ShouldQueue::class
  360. );
  361. } catch (Exception $e) {
  362. return false;
  363. }
  364. }
  365. /**
  366. * Create a callable for putting an event handler on the queue.
  367. *
  368. * @param string $class
  369. * @param string $method
  370. * @return \Closure
  371. */
  372. protected function createQueuedHandlerCallable($class, $method)
  373. {
  374. return function () use ($class, $method) {
  375. $arguments = array_map(function ($a) {
  376. return is_object($a) ? clone $a : $a;
  377. }, func_get_args());
  378. if ($this->handlerWantsToBeQueued($class, $arguments)) {
  379. $this->queueHandler($class, $method, $arguments);
  380. }
  381. };
  382. }
  383. /**
  384. * Determine if the event handler wants to be queued.
  385. *
  386. * @param string $class
  387. * @param array $arguments
  388. * @return bool
  389. */
  390. protected function handlerWantsToBeQueued($class, $arguments)
  391. {
  392. if (method_exists($class, 'shouldQueue')) {
  393. return $this->container->make($class)->shouldQueue($arguments[0]);
  394. }
  395. return true;
  396. }
  397. /**
  398. * Queue the handler class.
  399. *
  400. * @param string $class
  401. * @param string $method
  402. * @param array $arguments
  403. * @return void
  404. */
  405. protected function queueHandler($class, $method, $arguments)
  406. {
  407. [$listener, $job] = $this->createListenerAndJob($class, $method, $arguments);
  408. $connection = $this->resolveQueue()->connection(
  409. $listener->connection ?? null
  410. );
  411. $queue = $listener->queue ?? null;
  412. isset($listener->delay)
  413. ? $connection->laterOn($queue, $listener->delay, $job)
  414. : $connection->pushOn($queue, $job);
  415. }
  416. /**
  417. * Create the listener and job for a queued listener.
  418. *
  419. * @param string $class
  420. * @param string $method
  421. * @param array $arguments
  422. * @return array
  423. */
  424. protected function createListenerAndJob($class, $method, $arguments)
  425. {
  426. $listener = (new ReflectionClass($class))->newInstanceWithoutConstructor();
  427. return [$listener, $this->propagateListenerOptions(
  428. $listener, new CallQueuedListener($class, $method, $arguments)
  429. )];
  430. }
  431. /**
  432. * Propagate listener options to the job.
  433. *
  434. * @param mixed $listener
  435. * @param mixed $job
  436. * @return mixed
  437. */
  438. protected function propagateListenerOptions($listener, $job)
  439. {
  440. return tap($job, function ($job) use ($listener) {
  441. $job->tries = $listener->tries ?? null;
  442. $job->timeout = $listener->timeout ?? null;
  443. $job->timeoutAt = method_exists($listener, 'retryUntil')
  444. ? $listener->retryUntil() : null;
  445. });
  446. }
  447. /**
  448. * Remove a set of listeners from the dispatcher.
  449. *
  450. * @param string $event
  451. * @return void
  452. */
  453. public function forget($event)
  454. {
  455. if (Str::contains($event, '*')) {
  456. unset($this->wildcards[$event]);
  457. } else {
  458. unset($this->listeners[$event]);
  459. }
  460. }
  461. /**
  462. * Forget all of the pushed listeners.
  463. *
  464. * @return void
  465. */
  466. public function forgetPushed()
  467. {
  468. foreach ($this->listeners as $key => $value) {
  469. if (Str::endsWith($key, '_pushed')) {
  470. $this->forget($key);
  471. }
  472. }
  473. }
  474. /**
  475. * Get the queue implementation from the resolver.
  476. *
  477. * @return \Illuminate\Contracts\Queue\Queue
  478. */
  479. protected function resolveQueue()
  480. {
  481. return call_user_func($this->queueResolver);
  482. }
  483. /**
  484. * Set the queue resolver implementation.
  485. *
  486. * @param callable $resolver
  487. * @return $this
  488. */
  489. public function setQueueResolver(callable $resolver)
  490. {
  491. $this->queueResolver = $resolver;
  492. return $this;
  493. }
  494. }