PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.3
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.3
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
fluentform / vendor / wpfluent / framework / src / WPFluent / Events / Dispatcher.php

Dispatcher.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.3, at vendor/wpfluent/framework/src/WPFluent/Events/Dispatcher.php

646 lines 16.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentForm\Framework\Events;
4
5 use Closure;
6 use Exception;
7 use ReflectionClass;
8 use FluentForm\Framework\Support\Arr;
9 use FluentForm\Framework\Support\Str;
10 use FluentForm\Framework\Support\Helper;
11 use FluentForm\Framework\Container\Container;
12 use FluentForm\Framework\Support\MacroableTrait;
13 use FluentForm\Framework\Support\ReflectsClosures;
14 use FluentForm\Framework\Events\DispatcherInterface;
15 use FluentForm\Framework\Events\ShouldDispatchAfterCommit;
16 use FluentForm\Framework\Events\ShouldHandleEventsAfterCommit;
17 use FluentForm\Framework\Container\Contracts\Container as ContainerContract;
18
19
20 class Dispatcher implements DispatcherInterface
21 {
22 use MacroableTrait, ReflectsClosures;
23
24 /**
25 * The IoC container instance.
26 *
27 * @var \FluentForm\Framework\Container\Contracts\Container
28 */
29 protected $container;
30
31 /**
32 * The registered event listeners.
33 *
34 * @var array
35 */
36 protected $listeners = [];
37
38 /**
39 * The wildcard listeners.
40 *
41 * @var array
42 */
43 protected $wildcards = [];
44
45 /**
46 * The cached wildcard listeners.
47 *
48 * @var array
49 */
50 protected $wildcardsCache = [];
51
52
53 /**
54 * The stack of listeners being deferred.
55 *
56 * @var integer
57 */
58 protected $deferDepth = 0;
59
60 /**
61 * The currently deferred events.
62 *
63 * @var array
64 */
65 protected $deferredEvents = [];
66
67 /**
68 * Indicates if events should be deferred.
69 *
70 * @var bool
71 */
72 protected $deferringEvents = false;
73
74 /**
75 * The specific events to defer (null means defer all events).
76 *
77 * @var array|null
78 */
79 protected $eventsToDefer = null;
80
81 /**
82 * The transaction manager instance.
83 *
84 * @var \FluentForm\Framework\Database\DatabaseTransactionsManager|null
85 */
86 protected $transactionManagerResolver = null;
87
88 /**
89 * Create a new event dispatcher instance.
90 *
91 * @param \FluentForm\Framework\Container\Contracts\Container|null $container
92 * @return void
93 */
94 public function __construct(?ContainerContract $container = null)
95 {
96 $this->container = $container ?: new Container;
97 }
98
99 /**
100 * Register an event listener with the dispatcher.
101 *
102 * @param \Closure|string|array $events
103 * @param \Closure|string|array|null $listener
104 * @return void
105 */
106 public function listen($events, $listener = null)
107 {
108 if (class_exists('ReflectionUnionType')) {
109 if ($events instanceof Closure) {
110 return Helper::collect($this->firstClosureParameterTypes($events))
111 ->each(function ($event) use ($events) {
112 $this->listen($event, $events);
113 });
114 }
115 }
116
117 foreach ((array) $events as $event) {
118 if (Str::contains($event, '*')) {
119 $this->setupWildcardListen($event, $listener);
120 } else {
121 $this->listeners[$event][] = $this->makeListener($listener);
122 }
123 }
124 }
125
126 /**
127 * Setup a wildcard listener callback.
128 *
129 * @param string $event
130 * @param \Closure|string $listener
131 * @return void
132 */
133 protected function setupWildcardListen($event, $listener)
134 {
135 $this->wildcards[$event][] = $this->makeListener($listener, true);
136
137 $this->wildcardsCache = [];
138 }
139
140 /**
141 * Determine if a given event has listeners.
142 *
143 * @param string $eventName
144 * @return bool
145 */
146 public function hasListeners($eventName)
147 {
148 return isset($this->listeners[$eventName]) ||
149 isset($this->wildcards[$eventName]) ||
150 $this->hasWildcardListeners($eventName);
151 }
152
153 /**
154 * Determine if the given event has any wildcard listeners.
155 *
156 * @param string $eventName
157 * @return bool
158 */
159 public function hasWildcardListeners($eventName)
160 {
161 foreach ($this->wildcards as $key => $listeners) {
162 if (Str::is($key, $eventName)) {
163 return true;
164 }
165 }
166
167 return false;
168 }
169
170 /**
171 * Register an event and payload to be fired later.
172 *
173 * @param string $event
174 * @param array $payload
175 * @return void
176 */
177 public function push($event, $payload = [])
178 {
179 $this->listen($event.'_pushed', function () use ($event, $payload) {
180 $this->dispatch($event, $payload);
181 });
182 }
183
184 /**
185 * Flush a set of pushed events.
186 *
187 * @param string $event
188 * @return void
189 */
190 public function flush($event)
191 {
192 $this->dispatch($event.'_pushed');
193 }
194
195 /**
196 * Register an event subscriber with the dispatcher.
197 *
198 * @param object|string $subscriber
199 * @return void
200 */
201 public function subscribe($subscriber)
202 {
203 $subscriber = $this->resolveSubscriber($subscriber);
204
205 $events = $subscriber->subscribe($this);
206
207 if (is_array($events)) {
208 foreach ($events as $event => $listeners) {
209 foreach (Arr::wrap($listeners) as $listener) {
210 if (is_string($listener) && method_exists($subscriber, $listener)) {
211 $this->listen($event, [get_class($subscriber), $listener]);
212
213 continue;
214 }
215
216 $this->listen($event, $listener);
217 }
218 }
219 }
220 }
221
222 /**
223 * Resolve the subscriber instance.
224 *
225 * @param object|string $subscriber
226 * @return mixed
227 */
228 protected function resolveSubscriber($subscriber)
229 {
230 if (is_string($subscriber)) {
231 return $this->container->make($subscriber);
232 }
233
234 return $subscriber;
235 }
236
237 /**
238 * Execute the given callback while deferring events,
239 * then dispatch all the deferred events.
240 *
241 * @param callable $callback
242 * @param array|null $events
243 * @return mixed
244 */
245 public function defer(callable $callback, ?array $events = null)
246 {
247 $this->deferDepth++;
248
249 $previousEventsToDefer = $this->eventsToDefer;
250
251 if ($events !== null) {
252 $this->eventsToDefer = $events;
253 }
254
255 try {
256 return $callback();
257 } finally {
258 $this->deferDepth--;
259
260 if ($this->deferDepth === 0) {
261 $events = $this->deferredEvents;
262 $this->deferredEvents = [];
263 $this->eventsToDefer = null;
264
265 foreach ($events as $args) {
266 $this->dispatch(...$args);
267 }
268 } else {
269 $this->eventsToDefer = $previousEventsToDefer;
270 }
271 }
272 }
273
274 /**
275 * Determine if the given event should be deferred.
276 *
277 * @param string $event
278 * @return bool
279 */
280 protected function shouldDeferEvent($event)
281 {
282 if ($this->deferDepth === 0) {
283 return false;
284 }
285
286 if ($this->eventsToDefer === null) {
287 return true;
288 }
289
290 return in_array($event, $this->eventsToDefer, true);
291 }
292
293 /**
294 * Fire an event until the first non-null response is returned.
295 *
296 * @param string|object $event
297 * @param mixed $payload
298 * @return array|null
299 */
300 public function until($event, $payload = [])
301 {
302 return $this->dispatch($event, $payload, true);
303 }
304
305 /**
306 * Fire an event and call the listeners.
307 *
308 * @param string|object $event
309 * @param mixed $payload
310 * @param bool $halt
311 * @return array|null
312 */
313 public function dispatch($event, $payload = [], $halt = false)
314 {
315 // When the given "event" is actually an object we will assume it is
316 // an event object and use the class as the event name and this
317 // event itself as the payload to the handler, which makes
318 // object based events quite simple.
319
320 [$isEventObject, $event, $payload] = [
321 is_object($event),
322 ...$this->parseEventAndPayload($event, $payload),
323 ];
324
325 if ($this->shouldDeferEvent($event)) {
326 $this->deferredEvents[] = func_get_args();
327
328 return null;
329 }
330
331 // If the event is not intended to be dispatched unless the current
332 // database transaction is successful, we'll register a callback
333 // which will handle dispatching this event on the next
334 // successful DB transaction commit.
335 if ($isEventObject &&
336 $payload[0] instanceof ShouldDispatchAfterCommit &&
337 ! is_null($transactions = $this->resolveTransactionManager())) {
338 $transactions->addCallback(
339 fn () => $this->invokeListeners($event, $payload, $halt)
340 );
341
342 return null;
343 }
344
345 return $this->invokeListeners($event, $payload, $halt);
346 }
347
348 /**
349 * Broadcast an event and call its listeners.
350 *
351 * @param string|object $event
352 * @param mixed $payload
353 * @param bool $halt
354 * @return array|null
355 */
356 protected function invokeListeners($event, $payload, $halt = false)
357 {
358 $responses = [];
359
360 foreach ($this->getListeners($event) as $listener) {
361 $response = $listener($event, $payload);
362
363 // If a response is returned from the listener and event halting is
364 // enabled we will just return this response, and not call the
365 // rest of the event listeners. Otherwise we will add the
366 // response on the response list.
367 if ($halt && ! is_null($response)) {
368 return $response;
369 }
370
371 // If a boolean false is returned from a listener, we will stop
372 // propagating the event to any further listeners down in the
373 // chain, else we keep on looping through the listeners
374 // and firing every one in our sequence.
375 if ($response === false) {
376 break;
377 }
378
379 $responses[] = $response;
380 }
381
382 return $halt ? null : $responses;
383 }
384
385 /**
386 * Parse the given event and payload and prepare them for dispatching.
387 *
388 * @param mixed $event
389 * @param mixed $payload
390 * @return array
391 */
392 protected function parseEventAndPayload($event, $payload)
393 {
394 if (is_object($event)) {
395 [$payload, $event] = [[$event], get_class($event)];
396 }
397
398 return [$event, Arr::wrap($payload)];
399 }
400
401 /**
402 * Get all of the listeners for a given event name.
403 *
404 * @param string $eventName
405 * @return array
406 */
407 public function getListeners($eventName)
408 {
409 $listeners = $this->listeners[$eventName] ?? [];
410
411 $listeners = array_merge(
412 $listeners,
413 $this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName)
414 );
415
416 return class_exists($eventName, false)
417 ? $this->addInterfaceListeners($eventName, $listeners)
418 : $listeners;
419 }
420
421 /**
422 * Get the wildcard listeners for the event.
423 *
424 * @param string $eventName
425 * @return array
426 */
427 protected function getWildcardListeners($eventName)
428 {
429 $wildcards = [];
430
431 foreach ($this->wildcards as $key => $listeners) {
432 if (Str::is($key, $eventName)) {
433 $wildcards = array_merge($wildcards, $listeners);
434 }
435 }
436
437 return $this->wildcardsCache[$eventName] = $wildcards;
438 }
439
440 /**
441 * Add the listeners for the event's interfaces to the given array.
442 *
443 * @param string $eventName
444 * @param array $listeners
445 * @return array
446 */
447 protected function addInterfaceListeners($eventName, array $listeners = [])
448 {
449 foreach (class_implements($eventName) as $interface) {
450 if (isset($this->listeners[$interface])) {
451 foreach ($this->listeners[$interface] as $names) {
452 $listeners = array_merge($listeners, (array) $names);
453 }
454 }
455 }
456
457 return $listeners;
458 }
459
460 /**
461 * Register an event listener with the dispatcher.
462 *
463 * @param \Closure|string|array $listener
464 * @param bool $wildcard
465 * @return \Closure
466 */
467 public function makeListener($listener, $wildcard = false)
468 {
469 if (is_string($listener)) {
470 return $this->createClassListener($listener, $wildcard);
471 }
472
473 if (
474 is_array($listener) &&
475 isset($listener[0]) &&
476 is_string($listener[0])
477 ) {
478 return $this->createClassListener($listener, $wildcard);
479 }
480
481 if (is_object($listener) && !$listener instanceof Closure) {
482 return $this->createClassListener($listener, $wildcard);
483 }
484
485 return function ($event, $payload) use ($listener, $wildcard) {
486 if ($wildcard) {
487 return $listener($event, $payload);
488 }
489
490 return $listener(...array_values($payload));
491 };
492 }
493
494 /**
495 * Create a class based listener using the IoC container.
496 *
497 * @param string $listener
498 * @param bool $wildcard
499 * @return \Closure
500 */
501 public function createClassListener($listener, $wildcard = false)
502 {
503 return function ($event, $payload) use ($listener, $wildcard) {
504 if ($wildcard) {
505 return call_user_func(
506 $this->createClassCallable($listener), $event, $payload
507 );
508 }
509
510 $callable = $this->createClassCallable($listener);
511
512 return $callable(...array_values($payload));
513 };
514 }
515
516 /**
517 * Create the class based event callable.
518 *
519 * @param array|string $listener
520 * @return callable
521 */
522 protected function createClassCallable($listener)
523 {
524 [$class, $method] = is_array($listener)
525 ? $listener
526 : $this->parseClassCallable($listener);
527
528 if (!method_exists($class, $method)) {
529 $method = '__invoke';
530 }
531
532 if (!is_object($class)) {
533 $class = $this->container->make($class);
534 }
535
536 return $this->ShouldBeDispatchedAfterTransactions($class)
537 ? $this->createCallbackToRunAfterCommits($class, $method)
538 : [$class, $method];
539 }
540
541 /**
542 * Parse the class listener into class and method.
543 *
544 * @param string $listener
545 * @return array
546 */
547 protected function parseClassCallable($listener)
548 {
549 if (is_object($listener)) {
550 return [$listener, '__invoke'];
551 }
552
553 return Str::parseCallback($listener, 'handle');
554 }
555
556 /**
557 * Determine if the given event handler should be dispatched after
558 * all database transactions have committed.
559 *
560 * @param object|mixed $listener
561 * @return bool
562 */
563 protected function ShouldBeDispatchedAfterTransactions($listener)
564 {
565 return (($listener->afterCommit ?? null) ||
566 $listener instanceof ShouldDispatchAfterCommit
567 ) && $this->resolveTransactionManager();
568 }
569
570 /**
571 * Create a callable for dispatching a listener after database transactions.
572 *
573 * @param mixed $listener
574 * @param string $method
575 * @return \Closure
576 */
577 protected function createCallbackToRunAfterCommits($listener, $method)
578 {
579 return function () use ($method, $listener) {
580 $payload = func_get_args();
581
582 $this->resolveTransactionManager()->addCallback(
583 fn() => $listener->$method(...$payload)
584 );
585 };
586 }
587
588 /**
589 * Remove a set of listeners from the dispatcher.
590 *
591 * @param string $event
592 * @return void
593 */
594 public function forget($event)
595 {
596 if (Str::contains($event, '*')) {
597 unset($this->wildcards[$event]);
598 } else {
599 unset($this->listeners[$event]);
600 }
601
602 foreach ($this->wildcardsCache as $key => $listeners) {
603 if (Str::is($event, $key)) {
604 unset($this->wildcardsCache[$key]);
605 }
606 }
607 }
608
609 /**
610 * Forget all of the pushed listeners.
611 *
612 * @return void
613 */
614 public function forgetPushed()
615 {
616 foreach ($this->listeners as $key => $value) {
617 if (Str::endsWith($key, '_pushed')) {
618 $this->forget($key);
619 }
620 }
621 }
622
623 /**
624 * Resolve the transaction manager instance.
625 *
626 * @return \FluentForm\Framework\Database\DatabaseTransactionsManager
627 */
628 protected function resolveTransactionManager()
629 {
630 return call_user_func($this->transactionManagerResolver);
631 }
632
633 /**
634 *
635 * Set the transaction manager resolver.
636 *
637 * @param callable $resolver
638 */
639 public function setTransactionManagerResolver(callable $resolver)
640 {
641 $this->transactionManagerResolver = $resolver;
642
643 return $this;
644 }
645 }
646