EventFake.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  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. PHPUnit::assertSame(
  169. 0, $count,
  170. "{$count} unexpected events were dispatched."
  171. );
  172. }
  173. /**
  174. * Get all of the events matching a truth-test callback.
  175. *
  176. * @param string $event
  177. * @param callable|null $callback
  178. * @return \Illuminate\Support\Collection
  179. */
  180. public function dispatched($event, $callback = null)
  181. {
  182. if (! $this->hasDispatched($event)) {
  183. return collect();
  184. }
  185. $callback = $callback ?: fn () => true;
  186. return collect($this->events[$event])->filter(
  187. fn ($arguments) => $callback(...$arguments)
  188. );
  189. }
  190. /**
  191. * Determine if the given event has been dispatched.
  192. *
  193. * @param string $event
  194. * @return bool
  195. */
  196. public function hasDispatched($event)
  197. {
  198. return isset($this->events[$event]) && ! empty($this->events[$event]);
  199. }
  200. /**
  201. * Register an event listener with the dispatcher.
  202. *
  203. * @param \Closure|string|array $events
  204. * @param mixed $listener
  205. * @return void
  206. */
  207. public function listen($events, $listener = null)
  208. {
  209. $this->dispatcher->listen($events, $listener);
  210. }
  211. /**
  212. * Determine if a given event has listeners.
  213. *
  214. * @param string $eventName
  215. * @return bool
  216. */
  217. public function hasListeners($eventName)
  218. {
  219. return $this->dispatcher->hasListeners($eventName);
  220. }
  221. /**
  222. * Register an event and payload to be dispatched later.
  223. *
  224. * @param string $event
  225. * @param array $payload
  226. * @return void
  227. */
  228. public function push($event, $payload = [])
  229. {
  230. //
  231. }
  232. /**
  233. * Register an event subscriber with the dispatcher.
  234. *
  235. * @param object|string $subscriber
  236. * @return void
  237. */
  238. public function subscribe($subscriber)
  239. {
  240. $this->dispatcher->subscribe($subscriber);
  241. }
  242. /**
  243. * Flush a set of pushed events.
  244. *
  245. * @param string $event
  246. * @return void
  247. */
  248. public function flush($event)
  249. {
  250. //
  251. }
  252. /**
  253. * Fire an event and call the listeners.
  254. *
  255. * @param string|object $event
  256. * @param mixed $payload
  257. * @param bool $halt
  258. * @return array|null
  259. */
  260. public function dispatch($event, $payload = [], $halt = false)
  261. {
  262. $name = is_object($event) ? get_class($event) : (string) $event;
  263. if ($this->shouldFakeEvent($name, $payload)) {
  264. $this->fakeEvent($event, $name, func_get_args());
  265. } else {
  266. return $this->dispatcher->dispatch($event, $payload, $halt);
  267. }
  268. }
  269. /**
  270. * Determine if an event should be faked or actually dispatched.
  271. *
  272. * @param string $eventName
  273. * @param mixed $payload
  274. * @return bool
  275. */
  276. protected function shouldFakeEvent($eventName, $payload)
  277. {
  278. if ($this->shouldDispatchEvent($eventName, $payload)) {
  279. return false;
  280. }
  281. if (empty($this->eventsToFake)) {
  282. return true;
  283. }
  284. return collect($this->eventsToFake)
  285. ->filter(function ($event) use ($eventName, $payload) {
  286. return $event instanceof Closure
  287. ? $event($eventName, $payload)
  288. : $event === $eventName;
  289. })
  290. ->isNotEmpty();
  291. }
  292. /**
  293. * Push the event onto the fake events array immediately or after the next database transaction.
  294. *
  295. * @param string|object $event
  296. * @param string $name
  297. * @param array $arguments
  298. * @return void
  299. */
  300. protected function fakeEvent($event, $name, $arguments)
  301. {
  302. if ($event instanceof ShouldDispatchAfterCommit && Container::getInstance()->bound('db.transactions')) {
  303. return Container::getInstance()->make('db.transactions')
  304. ->addCallback(fn () => $this->events[$name][] = $arguments);
  305. }
  306. $this->events[$name][] = $arguments;
  307. }
  308. /**
  309. * Determine whether an event should be dispatched or not.
  310. *
  311. * @param string $eventName
  312. * @param mixed $payload
  313. * @return bool
  314. */
  315. protected function shouldDispatchEvent($eventName, $payload)
  316. {
  317. if (empty($this->eventsToDispatch)) {
  318. return false;
  319. }
  320. return collect($this->eventsToDispatch)
  321. ->filter(function ($event) use ($eventName, $payload) {
  322. return $event instanceof Closure
  323. ? $event($eventName, $payload)
  324. : $event === $eventName;
  325. })
  326. ->isNotEmpty();
  327. }
  328. /**
  329. * Remove a set of listeners from the dispatcher.
  330. *
  331. * @param string $event
  332. * @return void
  333. */
  334. public function forget($event)
  335. {
  336. //
  337. }
  338. /**
  339. * Forget all of the queued listeners.
  340. *
  341. * @return void
  342. */
  343. public function forgetPushed()
  344. {
  345. //
  346. }
  347. /**
  348. * Dispatch an event and call the listeners.
  349. *
  350. * @param string|object $event
  351. * @param mixed $payload
  352. * @return mixed
  353. */
  354. public function until($event, $payload = [])
  355. {
  356. return $this->dispatch($event, $payload, true);
  357. }
  358. /**
  359. * Handle dynamic method calls to the dispatcher.
  360. *
  361. * @param string $method
  362. * @param array $parameters
  363. * @return mixed
  364. */
  365. public function __call($method, $parameters)
  366. {
  367. return $this->forwardCallTo($this->dispatcher, $method, $parameters);
  368. }
  369. }