EventFake.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  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\Str;
  9. use Illuminate\Support\Traits\ForwardsCalls;
  10. use Illuminate\Support\Traits\ReflectsClosures;
  11. use PHPUnit\Framework\Assert as PHPUnit;
  12. use ReflectionFunction;
  13. class EventFake implements Dispatcher, Fake
  14. {
  15. use ForwardsCalls, ReflectsClosures;
  16. /**
  17. * The original event dispatcher.
  18. *
  19. * @var \Illuminate\Contracts\Events\Dispatcher
  20. */
  21. public $dispatcher;
  22. /**
  23. * The event types that should be intercepted instead of dispatched.
  24. *
  25. * @var array
  26. */
  27. protected $eventsToFake = [];
  28. /**
  29. * The event types that should be dispatched instead of intercepted.
  30. *
  31. * @var array
  32. */
  33. protected $eventsToDispatch = [];
  34. /**
  35. * All of the events that have been intercepted keyed by type.
  36. *
  37. * @var array
  38. */
  39. protected $events = [];
  40. /**
  41. * Create a new event fake instance.
  42. *
  43. * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
  44. * @param array|string $eventsToFake
  45. * @return void
  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 a number of times.
  130. *
  131. * @param string $event
  132. * @param int $times
  133. * @return void
  134. */
  135. public function assertDispatchedTimes($event, $times = 1)
  136. {
  137. $count = $this->dispatched($event)->count();
  138. PHPUnit::assertSame(
  139. $times, $count,
  140. "The expected [{$event}] event was dispatched {$count} times instead of {$times} times."
  141. );
  142. }
  143. /**
  144. * Determine if an event was dispatched based on a truth-test callback.
  145. *
  146. * @param string|\Closure $event
  147. * @param callable|null $callback
  148. * @return void
  149. */
  150. public function assertNotDispatched($event, $callback = null)
  151. {
  152. if ($event instanceof Closure) {
  153. [$event, $callback] = [$this->firstClosureParameterType($event), $event];
  154. }
  155. PHPUnit::assertCount(
  156. 0, $this->dispatched($event, $callback),
  157. "The unexpected [{$event}] event was dispatched."
  158. );
  159. }
  160. /**
  161. * Assert that no events were dispatched.
  162. *
  163. * @return void
  164. */
  165. public function assertNothingDispatched()
  166. {
  167. $count = count(Arr::flatten($this->events));
  168. $eventNames = collect($this->events)
  169. ->map(fn ($events, $eventName) => sprintf(
  170. '%s dispatched %s %s',
  171. $eventName,
  172. count($events),
  173. Str::plural('time', count($events)),
  174. ))
  175. ->join("\n- ");
  176. PHPUnit::assertSame(
  177. 0, $count,
  178. "{$count} unexpected events were dispatched:\n\n- $eventNames\n"
  179. );
  180. }
  181. /**
  182. * Get all of the events matching a truth-test callback.
  183. *
  184. * @param string $event
  185. * @param callable|null $callback
  186. * @return \Illuminate\Support\Collection
  187. */
  188. public function dispatched($event, $callback = null)
  189. {
  190. if (! $this->hasDispatched($event)) {
  191. return collect();
  192. }
  193. $callback = $callback ?: fn () => true;
  194. return collect($this->events[$event])->filter(
  195. fn ($arguments) => $callback(...$arguments)
  196. );
  197. }
  198. /**
  199. * Determine if the given event has been dispatched.
  200. *
  201. * @param string $event
  202. * @return bool
  203. */
  204. public function hasDispatched($event)
  205. {
  206. return isset($this->events[$event]) && ! empty($this->events[$event]);
  207. }
  208. /**
  209. * Register an event listener with the dispatcher.
  210. *
  211. * @param \Closure|string|array $events
  212. * @param mixed $listener
  213. * @return void
  214. */
  215. public function listen($events, $listener = null)
  216. {
  217. $this->dispatcher->listen($events, $listener);
  218. }
  219. /**
  220. * Determine if a given event has listeners.
  221. *
  222. * @param string $eventName
  223. * @return bool
  224. */
  225. public function hasListeners($eventName)
  226. {
  227. return $this->dispatcher->hasListeners($eventName);
  228. }
  229. /**
  230. * Register an event and payload to be dispatched later.
  231. *
  232. * @param string $event
  233. * @param array $payload
  234. * @return void
  235. */
  236. public function push($event, $payload = [])
  237. {
  238. //
  239. }
  240. /**
  241. * Register an event subscriber with the dispatcher.
  242. *
  243. * @param object|string $subscriber
  244. * @return void
  245. */
  246. public function subscribe($subscriber)
  247. {
  248. $this->dispatcher->subscribe($subscriber);
  249. }
  250. /**
  251. * Flush a set of pushed events.
  252. *
  253. * @param string $event
  254. * @return void
  255. */
  256. public function flush($event)
  257. {
  258. //
  259. }
  260. /**
  261. * Fire an event and call the listeners.
  262. *
  263. * @param string|object $event
  264. * @param mixed $payload
  265. * @param bool $halt
  266. * @return array|null
  267. */
  268. public function dispatch($event, $payload = [], $halt = false)
  269. {
  270. $name = is_object($event) ? get_class($event) : (string) $event;
  271. if ($this->shouldFakeEvent($name, $payload)) {
  272. $this->fakeEvent($event, $name, func_get_args());
  273. } else {
  274. return $this->dispatcher->dispatch($event, $payload, $halt);
  275. }
  276. }
  277. /**
  278. * Determine if an event should be faked or actually dispatched.
  279. *
  280. * @param string $eventName
  281. * @param mixed $payload
  282. * @return bool
  283. */
  284. protected function shouldFakeEvent($eventName, $payload)
  285. {
  286. if ($this->shouldDispatchEvent($eventName, $payload)) {
  287. return false;
  288. }
  289. if (empty($this->eventsToFake)) {
  290. return true;
  291. }
  292. return collect($this->eventsToFake)
  293. ->filter(function ($event) use ($eventName, $payload) {
  294. return $event instanceof Closure
  295. ? $event($eventName, $payload)
  296. : $event === $eventName;
  297. })
  298. ->isNotEmpty();
  299. }
  300. /**
  301. * Push the event onto the fake events array immediately or after the next database transaction.
  302. *
  303. * @param string|object $event
  304. * @param string $name
  305. * @param array $arguments
  306. * @return void
  307. */
  308. protected function fakeEvent($event, $name, $arguments)
  309. {
  310. if ($event instanceof ShouldDispatchAfterCommit && Container::getInstance()->bound('db.transactions')) {
  311. return Container::getInstance()->make('db.transactions')
  312. ->addCallback(fn () => $this->events[$name][] = $arguments);
  313. }
  314. $this->events[$name][] = $arguments;
  315. }
  316. /**
  317. * Determine whether an event should be dispatched or not.
  318. *
  319. * @param string $eventName
  320. * @param mixed $payload
  321. * @return bool
  322. */
  323. protected function shouldDispatchEvent($eventName, $payload)
  324. {
  325. if (empty($this->eventsToDispatch)) {
  326. return false;
  327. }
  328. return collect($this->eventsToDispatch)
  329. ->filter(function ($event) use ($eventName, $payload) {
  330. return $event instanceof Closure
  331. ? $event($eventName, $payload)
  332. : $event === $eventName;
  333. })
  334. ->isNotEmpty();
  335. }
  336. /**
  337. * Remove a set of listeners from the dispatcher.
  338. *
  339. * @param string $event
  340. * @return void
  341. */
  342. public function forget($event)
  343. {
  344. //
  345. }
  346. /**
  347. * Forget all of the queued listeners.
  348. *
  349. * @return void
  350. */
  351. public function forgetPushed()
  352. {
  353. //
  354. }
  355. /**
  356. * Dispatch an event and call the listeners.
  357. *
  358. * @param string|object $event
  359. * @param mixed $payload
  360. * @return mixed
  361. */
  362. public function until($event, $payload = [])
  363. {
  364. return $this->dispatch($event, $payload, true);
  365. }
  366. /**
  367. * Get the events that have been dispatched.
  368. *
  369. * @return array
  370. */
  371. public function dispatchedEvents()
  372. {
  373. return $this->events;
  374. }
  375. /**
  376. * Handle dynamic method calls to the dispatcher.
  377. *
  378. * @param string $method
  379. * @param array $parameters
  380. * @return mixed
  381. */
  382. public function __call($method, $parameters)
  383. {
  384. return $this->forwardCallTo($this->dispatcher, $method, $parameters);
  385. }
  386. }