EventFake.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. <?php
  2. namespace Illuminate\Support\Testing\Fakes;
  3. use Closure;
  4. use Illuminate\Container\Container;
  5. use Illuminate\Contracts\Events\Dispatcher;
  6. use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
  7. use Illuminate\Support\Arr;
  8. use Illuminate\Support\Collection;
  9. use Illuminate\Support\Str;
  10. use Illuminate\Support\Traits\ForwardsCalls;
  11. use Illuminate\Support\Traits\ReflectsClosures;
  12. use PHPUnit\Framework\Assert as PHPUnit;
  13. use ReflectionFunction;
  14. class EventFake implements Dispatcher, Fake
  15. {
  16. use ForwardsCalls, ReflectsClosures;
  17. /**
  18. * The original event dispatcher.
  19. *
  20. * @var \Illuminate\Contracts\Events\Dispatcher
  21. */
  22. public $dispatcher;
  23. /**
  24. * The event types that should be intercepted instead of dispatched.
  25. *
  26. * @var array
  27. */
  28. protected $eventsToFake = [];
  29. /**
  30. * The event types that should be dispatched instead of intercepted.
  31. *
  32. * @var array
  33. */
  34. protected $eventsToDispatch = [];
  35. /**
  36. * All of the events that have been intercepted keyed by type.
  37. *
  38. * @var array
  39. */
  40. protected $events = [];
  41. /**
  42. * Create a new event fake instance.
  43. *
  44. * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
  45. * @param array|string $eventsToFake
  46. */
  47. public function __construct(Dispatcher $dispatcher, $eventsToFake = [])
  48. {
  49. $this->dispatcher = $dispatcher;
  50. $this->eventsToFake = Arr::wrap($eventsToFake);
  51. }
  52. /**
  53. * Specify the events that should be dispatched instead of faked.
  54. *
  55. * @param array|string $eventsToDispatch
  56. * @return $this
  57. */
  58. public function except($eventsToDispatch)
  59. {
  60. $this->eventsToDispatch = array_merge(
  61. $this->eventsToDispatch,
  62. Arr::wrap($eventsToDispatch)
  63. );
  64. return $this;
  65. }
  66. /**
  67. * Assert if an event has a listener attached to it.
  68. *
  69. * @param string $expectedEvent
  70. * @param string|array $expectedListener
  71. * @return void
  72. */
  73. public function assertListening($expectedEvent, $expectedListener)
  74. {
  75. foreach ($this->dispatcher->getListeners($expectedEvent) as $listenerClosure) {
  76. $actualListener = (new ReflectionFunction($listenerClosure))
  77. ->getStaticVariables()['listener'];
  78. $normalizedListener = $expectedListener;
  79. if (is_string($actualListener) && Str::contains($actualListener, '@')) {
  80. $actualListener = Str::parseCallback($actualListener);
  81. if (is_string($expectedListener)) {
  82. if (Str::contains($expectedListener, '@')) {
  83. $normalizedListener = Str::parseCallback($expectedListener);
  84. } else {
  85. $normalizedListener = [
  86. $expectedListener,
  87. method_exists($expectedListener, 'handle') ? 'handle' : '__invoke',
  88. ];
  89. }
  90. }
  91. }
  92. if ($actualListener === $normalizedListener ||
  93. ($actualListener instanceof Closure &&
  94. $normalizedListener === Closure::class)) {
  95. PHPUnit::assertTrue(true);
  96. return;
  97. }
  98. }
  99. PHPUnit::assertTrue(
  100. false,
  101. sprintf(
  102. 'Event [%s] does not have the [%s] listener attached to it',
  103. $expectedEvent,
  104. print_r($expectedListener, true)
  105. )
  106. );
  107. }
  108. /**
  109. * Assert if an event was dispatched based on a truth-test callback.
  110. *
  111. * @param string|\Closure $event
  112. * @param callable|int|null $callback
  113. * @return void
  114. */
  115. public function assertDispatched($event, $callback = null)
  116. {
  117. if ($event instanceof Closure) {
  118. [$event, $callback] = [$this->firstClosureParameterType($event), $event];
  119. }
  120. if (is_int($callback)) {
  121. return $this->assertDispatchedTimes($event, $callback);
  122. }
  123. PHPUnit::assertTrue(
  124. $this->dispatched($event, $callback)->count() > 0,
  125. "The expected [{$event}] event was not dispatched."
  126. );
  127. }
  128. /**
  129. * Assert if an event was dispatched exactly once.
  130. *
  131. * @param string $event
  132. * @param int $times
  133. * @return void
  134. */
  135. public function assertDispatchedOnce($event)
  136. {
  137. $this->assertDispatchedTimes($event, 1);
  138. }
  139. /**
  140. * Assert if an event was dispatched a number of times.
  141. *
  142. * @param string $event
  143. * @param int $times
  144. * @return void
  145. */
  146. public function assertDispatchedTimes($event, $times = 1)
  147. {
  148. $count = $this->dispatched($event)->count();
  149. PHPUnit::assertSame(
  150. $times, $count,
  151. sprintf(
  152. "The expected [{$event}] event was dispatched {$count} %s instead of {$times} %s.",
  153. Str::plural('time', $count),
  154. Str::plural('time', $times)
  155. )
  156. );
  157. }
  158. /**
  159. * Determine if an event was dispatched based on a truth-test callback.
  160. *
  161. * @param string|\Closure $event
  162. * @param callable|null $callback
  163. * @return void
  164. */
  165. public function assertNotDispatched($event, $callback = null)
  166. {
  167. if ($event instanceof Closure) {
  168. [$event, $callback] = [$this->firstClosureParameterType($event), $event];
  169. }
  170. PHPUnit::assertCount(
  171. 0, $this->dispatched($event, $callback),
  172. "The unexpected [{$event}] event was dispatched."
  173. );
  174. }
  175. /**
  176. * Assert that no events were dispatched.
  177. *
  178. * @return void
  179. */
  180. public function assertNothingDispatched()
  181. {
  182. $count = count(Arr::flatten($this->events));
  183. $eventNames = (new Collection($this->events))
  184. ->map(fn ($events, $eventName) => sprintf(
  185. '%s dispatched %s %s',
  186. $eventName,
  187. count($events),
  188. Str::plural('time', count($events)),
  189. ))
  190. ->join("\n- ");
  191. PHPUnit::assertSame(
  192. 0, $count,
  193. "{$count} unexpected events were dispatched:\n\n- $eventNames\n"
  194. );
  195. }
  196. /**
  197. * Get all of the events matching a truth-test callback.
  198. *
  199. * @param string $event
  200. * @param callable|null $callback
  201. * @return \Illuminate\Support\Collection
  202. */
  203. public function dispatched($event, $callback = null)
  204. {
  205. if (! $this->hasDispatched($event)) {
  206. return new Collection;
  207. }
  208. $callback = $callback ?: fn () => true;
  209. return (new Collection($this->events[$event]))->filter(
  210. fn ($arguments) => $callback(...$arguments)
  211. );
  212. }
  213. /**
  214. * Determine if the given event has been dispatched.
  215. *
  216. * @param string $event
  217. * @return bool
  218. */
  219. public function hasDispatched($event)
  220. {
  221. return isset($this->events[$event]) && ! empty($this->events[$event]);
  222. }
  223. /**
  224. * Register an event listener with the dispatcher.
  225. *
  226. * @param \Closure|string|array $events
  227. * @param mixed $listener
  228. * @return void
  229. */
  230. public function listen($events, $listener = null)
  231. {
  232. $this->dispatcher->listen($events, $listener);
  233. }
  234. /**
  235. * Determine if a given event has listeners.
  236. *
  237. * @param string $eventName
  238. * @return bool
  239. */
  240. public function hasListeners($eventName)
  241. {
  242. return $this->dispatcher->hasListeners($eventName);
  243. }
  244. /**
  245. * Register an event and payload to be dispatched later.
  246. *
  247. * @param string $event
  248. * @param array $payload
  249. * @return void
  250. */
  251. public function push($event, $payload = [])
  252. {
  253. //
  254. }
  255. /**
  256. * Register an event subscriber with the dispatcher.
  257. *
  258. * @param object|string $subscriber
  259. * @return void
  260. */
  261. public function subscribe($subscriber)
  262. {
  263. $this->dispatcher->subscribe($subscriber);
  264. }
  265. /**
  266. * Flush a set of pushed events.
  267. *
  268. * @param string $event
  269. * @return void
  270. */
  271. public function flush($event)
  272. {
  273. //
  274. }
  275. /**
  276. * Fire an event and call the listeners.
  277. *
  278. * @param string|object $event
  279. * @param mixed $payload
  280. * @param bool $halt
  281. * @return array|null
  282. */
  283. public function dispatch($event, $payload = [], $halt = false)
  284. {
  285. $name = is_object($event) ? get_class($event) : (string) $event;
  286. if ($this->shouldFakeEvent($name, $payload)) {
  287. $this->fakeEvent($event, $name, func_get_args());
  288. } else {
  289. return $this->dispatcher->dispatch($event, $payload, $halt);
  290. }
  291. }
  292. /**
  293. * Determine if an event should be faked or actually dispatched.
  294. *
  295. * @param string $eventName
  296. * @param mixed $payload
  297. * @return bool
  298. */
  299. protected function shouldFakeEvent($eventName, $payload)
  300. {
  301. if ($this->shouldDispatchEvent($eventName, $payload)) {
  302. return false;
  303. }
  304. if (empty($this->eventsToFake)) {
  305. return true;
  306. }
  307. return (new Collection($this->eventsToFake))
  308. ->filter(function ($event) use ($eventName, $payload) {
  309. return $event instanceof Closure
  310. ? $event($eventName, $payload)
  311. : $event === $eventName;
  312. })
  313. ->isNotEmpty();
  314. }
  315. /**
  316. * Push the event onto the fake events array immediately or after the next database transaction.
  317. *
  318. * @param string|object $event
  319. * @param string $name
  320. * @param array $arguments
  321. * @return void
  322. */
  323. protected function fakeEvent($event, $name, $arguments)
  324. {
  325. if ($event instanceof ShouldDispatchAfterCommit && Container::getInstance()->bound('db.transactions')) {
  326. return Container::getInstance()->make('db.transactions')
  327. ->addCallback(fn () => $this->events[$name][] = $arguments);
  328. }
  329. $this->events[$name][] = $arguments;
  330. }
  331. /**
  332. * Determine whether an event should be dispatched or not.
  333. *
  334. * @param string $eventName
  335. * @param mixed $payload
  336. * @return bool
  337. */
  338. protected function shouldDispatchEvent($eventName, $payload)
  339. {
  340. if (empty($this->eventsToDispatch)) {
  341. return false;
  342. }
  343. return (new Collection($this->eventsToDispatch))
  344. ->filter(function ($event) use ($eventName, $payload) {
  345. return $event instanceof Closure
  346. ? $event($eventName, $payload)
  347. : $event === $eventName;
  348. })
  349. ->isNotEmpty();
  350. }
  351. /**
  352. * Remove a set of listeners from the dispatcher.
  353. *
  354. * @param string $event
  355. * @return void
  356. */
  357. public function forget($event)
  358. {
  359. //
  360. }
  361. /**
  362. * Forget all of the queued listeners.
  363. *
  364. * @return void
  365. */
  366. public function forgetPushed()
  367. {
  368. //
  369. }
  370. /**
  371. * Dispatch an event and call the listeners.
  372. *
  373. * @param string|object $event
  374. * @param mixed $payload
  375. * @return mixed
  376. */
  377. public function until($event, $payload = [])
  378. {
  379. return $this->dispatch($event, $payload, true);
  380. }
  381. /**
  382. * Get the events that have been dispatched.
  383. *
  384. * @return array
  385. */
  386. public function dispatchedEvents()
  387. {
  388. return $this->events;
  389. }
  390. /**
  391. * Handle dynamic method calls to the dispatcher.
  392. *
  393. * @param string $method
  394. * @param array $parameters
  395. * @return mixed
  396. */
  397. public function __call($method, $parameters)
  398. {
  399. return $this->forwardCallTo($this->dispatcher, $method, $parameters);
  400. }
  401. }