Dispatcher.php 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  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 fire($event, $payload = [], $halt = false)
  161. {
  162. return $this->dispatch($event, $payload, $halt);
  163. }
  164. /**
  165. * Fire an event and call the listeners.
  166. *
  167. * @param string|object $event
  168. * @param mixed $payload
  169. * @param bool $halt
  170. * @return array|null
  171. */
  172. public function dispatch($event, $payload = [], $halt = false)
  173. {
  174. // When the given "event" is actually an object we will assume it is an event
  175. // object and use the class as the event name and this event itself as the
  176. // payload to the handler, which makes object based events quite simple.
  177. list($event, $payload) = $this->parseEventAndPayload(
  178. $event, $payload
  179. );
  180. if ($this->shouldBroadcast($payload)) {
  181. $this->broadcastEvent($payload[0]);
  182. }
  183. $responses = [];
  184. foreach ($this->getListeners($event) as $listener) {
  185. $response = $listener($event, $payload);
  186. // If a response is returned from the listener and event halting is enabled
  187. // we will just return this response, and not call the rest of the event
  188. // listeners. Otherwise we will add the response on the response list.
  189. if ($halt && ! is_null($response)) {
  190. return $response;
  191. }
  192. // If a boolean false is returned from a listener, we will stop propagating
  193. // the event to any further listeners down in the chain, else we keep on
  194. // looping through the listeners and firing every one in our sequence.
  195. if ($response === false) {
  196. break;
  197. }
  198. $responses[] = $response;
  199. }
  200. return $halt ? null : $responses;
  201. }
  202. /**
  203. * Parse the given event and payload and prepare them for dispatching.
  204. *
  205. * @param mixed $event
  206. * @param mixed $payload
  207. * @return array
  208. */
  209. protected function parseEventAndPayload($event, $payload)
  210. {
  211. if (is_object($event)) {
  212. list($payload, $event) = [[$event], get_class($event)];
  213. }
  214. return [$event, Arr::wrap($payload)];
  215. }
  216. /**
  217. * Determine if the payload has a broadcastable event.
  218. *
  219. * @param array $payload
  220. * @return bool
  221. */
  222. protected function shouldBroadcast(array $payload)
  223. {
  224. return isset($payload[0]) &&
  225. $payload[0] instanceof ShouldBroadcast &&
  226. $this->broadcastWhen($payload[0]);
  227. }
  228. /**
  229. * Check if event should be broadcasted by condition.
  230. *
  231. * @param mixed $event
  232. * @return bool
  233. */
  234. protected function broadcastWhen($event)
  235. {
  236. return method_exists($event, 'broadcastWhen')
  237. ? $event->broadcastWhen() : true;
  238. }
  239. /**
  240. * Broadcast the given event class.
  241. *
  242. * @param \Illuminate\Contracts\Broadcasting\ShouldBroadcast $event
  243. * @return void
  244. */
  245. protected function broadcastEvent($event)
  246. {
  247. $this->container->make(BroadcastFactory::class)->queue($event);
  248. }
  249. /**
  250. * Get all of the listeners for a given event name.
  251. *
  252. * @param string $eventName
  253. * @return array
  254. */
  255. public function getListeners($eventName)
  256. {
  257. $listeners = $this->listeners[$eventName] ?? [];
  258. $listeners = array_merge(
  259. $listeners,
  260. $this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName)
  261. );
  262. return class_exists($eventName, false)
  263. ? $this->addInterfaceListeners($eventName, $listeners)
  264. : $listeners;
  265. }
  266. /**
  267. * Get the wildcard listeners for the event.
  268. *
  269. * @param string $eventName
  270. * @return array
  271. */
  272. protected function getWildcardListeners($eventName)
  273. {
  274. $wildcards = [];
  275. foreach ($this->wildcards as $key => $listeners) {
  276. if (Str::is($key, $eventName)) {
  277. $wildcards = array_merge($wildcards, $listeners);
  278. }
  279. }
  280. return $this->wildcardsCache[$eventName] = $wildcards;
  281. }
  282. /**
  283. * Add the listeners for the event's interfaces to the given array.
  284. *
  285. * @param string $eventName
  286. * @param array $listeners
  287. * @return array
  288. */
  289. protected function addInterfaceListeners($eventName, array $listeners = [])
  290. {
  291. foreach (class_implements($eventName) as $interface) {
  292. if (isset($this->listeners[$interface])) {
  293. foreach ($this->listeners[$interface] as $names) {
  294. $listeners = array_merge($listeners, (array) $names);
  295. }
  296. }
  297. }
  298. return $listeners;
  299. }
  300. /**
  301. * Register an event listener with the dispatcher.
  302. *
  303. * @param \Closure|string $listener
  304. * @param bool $wildcard
  305. * @return \Closure
  306. */
  307. public function makeListener($listener, $wildcard = false)
  308. {
  309. if (is_string($listener)) {
  310. return $this->createClassListener($listener, $wildcard);
  311. }
  312. return function ($event, $payload) use ($listener, $wildcard) {
  313. if ($wildcard) {
  314. return $listener($event, $payload);
  315. }
  316. return $listener(...array_values($payload));
  317. };
  318. }
  319. /**
  320. * Create a class based listener using the IoC container.
  321. *
  322. * @param string $listener
  323. * @param bool $wildcard
  324. * @return \Closure
  325. */
  326. public function createClassListener($listener, $wildcard = false)
  327. {
  328. return function ($event, $payload) use ($listener, $wildcard) {
  329. if ($wildcard) {
  330. return call_user_func($this->createClassCallable($listener), $event, $payload);
  331. }
  332. return call_user_func_array(
  333. $this->createClassCallable($listener), $payload
  334. );
  335. };
  336. }
  337. /**
  338. * Create the class based event callable.
  339. *
  340. * @param string $listener
  341. * @return callable
  342. */
  343. protected function createClassCallable($listener)
  344. {
  345. list($class, $method) = $this->parseClassCallable($listener);
  346. if ($this->handlerShouldBeQueued($class)) {
  347. return $this->createQueuedHandlerCallable($class, $method);
  348. }
  349. return [$this->container->make($class), $method];
  350. }
  351. /**
  352. * Parse the class listener into class and method.
  353. *
  354. * @param string $listener
  355. * @return array
  356. */
  357. protected function parseClassCallable($listener)
  358. {
  359. return Str::parseCallback($listener, 'handle');
  360. }
  361. /**
  362. * Determine if the event handler class should be queued.
  363. *
  364. * @param string $class
  365. * @return bool
  366. */
  367. protected function handlerShouldBeQueued($class)
  368. {
  369. try {
  370. return (new ReflectionClass($class))->implementsInterface(
  371. ShouldQueue::class
  372. );
  373. } catch (Exception $e) {
  374. return false;
  375. }
  376. }
  377. /**
  378. * Create a callable for putting an event handler on the queue.
  379. *
  380. * @param string $class
  381. * @param string $method
  382. * @return \Closure
  383. */
  384. protected function createQueuedHandlerCallable($class, $method)
  385. {
  386. return function () use ($class, $method) {
  387. $arguments = array_map(function ($a) {
  388. return is_object($a) ? clone $a : $a;
  389. }, func_get_args());
  390. if ($this->handlerWantsToBeQueued($class, $arguments)) {
  391. $this->queueHandler($class, $method, $arguments);
  392. }
  393. };
  394. }
  395. /**
  396. * Determine if the event handler wants to be queued.
  397. *
  398. * @param string $class
  399. * @param array $arguments
  400. * @return bool
  401. */
  402. protected function handlerWantsToBeQueued($class, $arguments)
  403. {
  404. if (method_exists($class, 'shouldQueue')) {
  405. return $this->container->make($class)->shouldQueue($arguments[0]);
  406. }
  407. return true;
  408. }
  409. /**
  410. * Queue the handler class.
  411. *
  412. * @param string $class
  413. * @param string $method
  414. * @param array $arguments
  415. * @return void
  416. */
  417. protected function queueHandler($class, $method, $arguments)
  418. {
  419. list($listener, $job) = $this->createListenerAndJob($class, $method, $arguments);
  420. $connection = $this->resolveQueue()->connection(
  421. $listener->connection ?? null
  422. );
  423. $queue = $listener->queue ?? null;
  424. isset($listener->delay)
  425. ? $connection->laterOn($queue, $listener->delay, $job)
  426. : $connection->pushOn($queue, $job);
  427. }
  428. /**
  429. * Create the listener and job for a queued listener.
  430. *
  431. * @param string $class
  432. * @param string $method
  433. * @param array $arguments
  434. * @return array
  435. */
  436. protected function createListenerAndJob($class, $method, $arguments)
  437. {
  438. $listener = (new ReflectionClass($class))->newInstanceWithoutConstructor();
  439. return [$listener, $this->propagateListenerOptions(
  440. $listener, new CallQueuedListener($class, $method, $arguments)
  441. )];
  442. }
  443. /**
  444. * Propagate listener options to the job.
  445. *
  446. * @param mixed $listener
  447. * @param mixed $job
  448. * @return mixed
  449. */
  450. protected function propagateListenerOptions($listener, $job)
  451. {
  452. return tap($job, function ($job) use ($listener) {
  453. $job->tries = $listener->tries ?? null;
  454. $job->timeout = $listener->timeout ?? null;
  455. $job->timeoutAt = method_exists($listener, 'retryUntil')
  456. ? $listener->retryUntil() : null;
  457. });
  458. }
  459. /**
  460. * Remove a set of listeners from the dispatcher.
  461. *
  462. * @param string $event
  463. * @return void
  464. */
  465. public function forget($event)
  466. {
  467. if (Str::contains($event, '*')) {
  468. unset($this->wildcards[$event]);
  469. } else {
  470. unset($this->listeners[$event]);
  471. }
  472. }
  473. /**
  474. * Forget all of the pushed listeners.
  475. *
  476. * @return void
  477. */
  478. public function forgetPushed()
  479. {
  480. foreach ($this->listeners as $key => $value) {
  481. if (Str::endsWith($key, '_pushed')) {
  482. $this->forget($key);
  483. }
  484. }
  485. }
  486. /**
  487. * Get the queue implementation from the resolver.
  488. *
  489. * @return \Illuminate\Contracts\Queue\Queue
  490. */
  491. protected function resolveQueue()
  492. {
  493. return call_user_func($this->queueResolver);
  494. }
  495. /**
  496. * Set the queue resolver implementation.
  497. *
  498. * @param callable $resolver
  499. * @return $this
  500. */
  501. public function setQueueResolver(callable $resolver)
  502. {
  503. $this->queueResolver = $resolver;
  504. return $this;
  505. }
  506. }