PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.5.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.5.0
2.5.0 2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 All 34 releases
← All changes | vendor/wpfluent/framework/src/WPFluent/Events/Dispatcher.php +202 -30 1.5.02 → 2.5.0 View file →
@@ -11,8 +11,10 @@
11 11 use FluentBooking\Framework\Container\Container;
12 12 use FluentBooking\Framework\Support\MacroableTrait;
13 13 use FluentBooking\Framework\Support\ReflectsClosures;
14 14 use FluentBooking\Framework\Events\DispatcherInterface;
15 +use FluentBooking\Framework\Events\ShouldDispatchAfterCommit;
16 +use FluentBooking\Framework\Events\ShouldHandleEventsAfterCommit;
15 17 use FluentBooking\Framework\Container\Contracts\Container as ContainerContract;
16 18
17 19
18 20 class Dispatcher implements DispatcherInterface
@@ -46,15 +48,51 @@
46 48 * @var array
47 49 */
48 50 protected $wildcardsCache = [];
49 51
52 +
50 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 \FluentBooking\Framework\Database\DatabaseTransactionsManager|null
85 + */
86 + protected $transactionManagerResolver = null;
87 +
88 + /**
51 89 * Create a new event dispatcher instance.
52 90 *
53 91 * @param \FluentBooking\Framework\Container\Contracts\Container|null $container
54 92 * @return void
55 93 */
56 - public function __construct(ContainerContract $container = null)
94 + public function __construct(?ContainerContract $container = null)
57 95 {
58 96 $this->container = $container ?: new Container;
59 97 }
60 98
@@ -196,8 +234,64 @@
196 234 return $subscriber;
197 235 }
198 236
199 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 + /**
200 294 * Fire an event until the first non-null response is returned.
201 295 *
202 296 * @param string|object $event
203 297 * @param mixed $payload
@@ -217,30 +311,68 @@
217 311 * @return array|null
218 312 */
219 313 public function dispatch($event, $payload = [], $halt = false)
220 314 {
221 - // When the given "event" is actually an object we will assume it is an event
222 - // object and use the class as the event name and this event itself as the
223 - // payload to the handler, which makes object based events quite simple.
224 - [$event, $payload] = $this->parseEventAndPayload(
225 - $event, $payload
226 - );
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 + ];
227 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 + {
228 358 $responses = [];
229 359
230 360 foreach ($this->getListeners($event) as $listener) {
231 361 $response = $listener($event, $payload);
232 362
233 - // If a response is returned from the listener and event halting is enabled
234 - // we will just return this response, and not call the rest of the event
235 - // listeners. Otherwise we will add the response on the response list.
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.
236 367 if ($halt && ! is_null($response)) {
237 368 return $response;
238 369 }
239 370
240 - // If a boolean false is returned from a listener, we will stop propagating
241 - // the event to any further listeners down in the chain, else we keep on
242 - // looping through the listeners and firing every one in our sequence.
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.
243 375 if ($response === false) {
244 376 break;
245 377 }
246 378
@@ -337,12 +469,20 @@
337 469 if (is_string($listener)) {
338 470 return $this->createClassListener($listener, $wildcard);
339 471 }
340 472
341 - if (is_array($listener) && isset($listener[0]) && is_string($listener[0])) {
473 + if (
474 + is_array($listener) &&
475 + isset($listener[0]) &&
476 + is_string($listener[0])
477 + ) {
342 478 return $this->createClassListener($listener, $wildcard);
343 479 }
344 480
481 + if (is_object($listener) && !$listener instanceof Closure) {
482 + return $this->createClassListener($listener, $wildcard);
483 + }
484 +
345 485 return function ($event, $payload) use ($listener, $wildcard) {
346 486 if ($wildcard) {
347 487 return $listener($event, $payload);
348 488 }
@@ -361,9 +501,11 @@
361 501 public function createClassListener($listener, $wildcard = false)
362 502 {
363 503 return function ($event, $payload) use ($listener, $wildcard) {
364 504 if ($wildcard) {
365 - return call_user_func($this->createClassCallable($listener), $event, $payload);
505 + return call_user_func(
506 + $this->createClassCallable($listener), $event, $payload
507 + );
366 508 }
367 509
368 510 $callable = $this->createClassCallable($listener);
369 511
@@ -379,20 +521,22 @@
379 521 */
380 522 protected function createClassCallable($listener)
381 523 {
382 524 [$class, $method] = is_array($listener)
383 - ? $listener
384 - : $this->parseClassCallable($listener);
525 + ? $listener
526 + : $this->parseClassCallable($listener);
385 527
386 - if (! method_exists($class, $method)) {
528 + if (!method_exists($class, $method)) {
387 529 $method = '__invoke';
388 530 }
389 531
390 - $listener = $this->container->make($class);
532 + if (!is_object($class)) {
533 + $class = $this->container->make($class);
534 + }
391 535
392 - return $this->handlerShouldBeDispatchedAfterDatabaseTransactions($listener)
393 - ? $this->createCallbackForListenerRunningAfterCommits($listener, $method)
394 - : [$listener, $method];
536 + return $this->ShouldBeDispatchedAfterTransactions($class)
537 + ? $this->createCallbackToRunAfterCommits($class, $method)
538 + : [$class, $method];
395 539 }
396 540
397 541 /**
398 542 * Parse the class listener into class and method.
@@ -401,20 +545,27 @@
401 545 * @return array
402 546 */
403 547 protected function parseClassCallable($listener)
404 548 {
549 + if (is_object($listener)) {
550 + return [$listener, '__invoke'];
551 + }
552 +
405 553 return Str::parseCallback($listener, 'handle');
406 554 }
407 555
408 556 /**
409 - * Determine if the given event handler should be dispatched after all database transactions have committed.
557 + * Determine if the given event handler should be dispatched after
558 + * all database transactions have committed.
410 559 *
411 560 * @param object|mixed $listener
412 561 * @return bool
413 562 */
414 - protected function handlerShouldBeDispatchedAfterDatabaseTransactions($listener)
563 + protected function ShouldBeDispatchedAfterTransactions($listener)
415 564 {
416 - return ($listener->afterCommit ?? null) && $this->container->bound('db.transactions');
565 + return (($listener->afterCommit ?? null) ||
566 + $listener instanceof ShouldDispatchAfterCommit
567 + ) && $this->resolveTransactionManager();
417 568 }
418 569
419 570 /**
420 571 * Create a callable for dispatching a listener after database transactions.
@@ -422,17 +573,15 @@
422 573 * @param mixed $listener
423 574 * @param string $method
424 575 * @return \Closure
425 576 */
426 - protected function createCallbackForListenerRunningAfterCommits($listener, $method)
577 + protected function createCallbackToRunAfterCommits($listener, $method)
427 578 {
428 579 return function () use ($method, $listener) {
429 580 $payload = func_get_args();
430 581
431 - $this->container->make('db.transactions')->addCallback(
432 - function () use ($listener, $method, $payload) {
433 - $listener->$method(...$payload);
434 - }
582 + $this->resolveTransactionManager()->addCallback(
583 + fn() => $listener->$method(...$payload)
435 584 );
436 585 };
437 586 }
438 587
@@ -468,6 +617,29 @@
468 617 if (Str::endsWith($key, '_pushed')) {
469 618 $this->forget($key);
470 619 }
471 620 }
621 + }
622 +
623 + /**
624 + * Resolve the transaction manager instance.
625 + *
626 + * @return \FluentBooking\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;
472 644 }
473 645 }