Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Events;
4
 
5
use Closure;
6
use Exception;
7
use Illuminate\Container\Container;
8
use Illuminate\Contracts\Broadcasting\Factory as BroadcastFactory;
9
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
10
use Illuminate\Contracts\Container\Container as ContainerContract;
11
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
12
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
13
use Illuminate\Contracts\Queue\ShouldQueue;
14
use Illuminate\Support\Arr;
15
use Illuminate\Support\Str;
16
use Illuminate\Support\Traits\Macroable;
17
use Illuminate\Support\Traits\ReflectsClosures;
18
use ReflectionClass;
19
 
20
class Dispatcher implements DispatcherContract
21
{
22
    use Macroable, ReflectsClosures;
23
 
24
    /**
25
     * The IoC container instance.
26
     *
27
     * @var \Illuminate\Contracts\Container\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
     * The queue resolver instance.
54
     *
55
     * @var callable
56
     */
57
    protected $queueResolver;
58
 
59
    /**
60
     * Create a new event dispatcher instance.
61
     *
62
     * @param  \Illuminate\Contracts\Container\Container|null  $container
63
     * @return void
64
     */
65
    public function __construct(ContainerContract $container = null)
66
    {
67
        $this->container = $container ?: new Container;
68
    }
69
 
70
    /**
71
     * Register an event listener with the dispatcher.
72
     *
73
     * @param  \Closure|string|array  $events
74
     * @param  \Closure|string|array|null  $listener
75
     * @return void
76
     */
77
    public function listen($events, $listener = null)
78
    {
79
        if ($events instanceof Closure) {
80
            return collect($this->firstClosureParameterTypes($events))
81
                ->each(function ($event) use ($events) {
82
                    $this->listen($event, $events);
83
                });
84
        } elseif ($events instanceof QueuedClosure) {
85
            return collect($this->firstClosureParameterTypes($events->closure))
86
                ->each(function ($event) use ($events) {
87
                    $this->listen($event, $events->resolve());
88
                });
89
        } elseif ($listener instanceof QueuedClosure) {
90
            $listener = $listener->resolve();
91
        }
92
 
93
        foreach ((array) $events as $event) {
94
            if (str_contains($event, '*')) {
95
                $this->setupWildcardListen($event, $listener);
96
            } else {
97
                $this->listeners[$event][] = $listener;
98
            }
99
        }
100
    }
101
 
102
    /**
103
     * Setup a wildcard listener callback.
104
     *
105
     * @param  string  $event
106
     * @param  \Closure|string  $listener
107
     * @return void
108
     */
109
    protected function setupWildcardListen($event, $listener)
110
    {
111
        $this->wildcards[$event][] = $listener;
112
 
113
        $this->wildcardsCache = [];
114
    }
115
 
116
    /**
117
     * Determine if a given event has listeners.
118
     *
119
     * @param  string  $eventName
120
     * @return bool
121
     */
122
    public function hasListeners($eventName)
123
    {
124
        return isset($this->listeners[$eventName]) ||
125
               isset($this->wildcards[$eventName]) ||
126
               $this->hasWildcardListeners($eventName);
127
    }
128
 
129
    /**
130
     * Determine if the given event has any wildcard listeners.
131
     *
132
     * @param  string  $eventName
133
     * @return bool
134
     */
135
    public function hasWildcardListeners($eventName)
136
    {
137
        foreach ($this->wildcards as $key => $listeners) {
138
            if (Str::is($key, $eventName)) {
139
                return true;
140
            }
141
        }
142
 
143
        return false;
144
    }
145
 
146
    /**
147
     * Register an event and payload to be fired later.
148
     *
149
     * @param  string  $event
150
     * @param  object|array  $payload
151
     * @return void
152
     */
153
    public function push($event, $payload = [])
154
    {
155
        $this->listen($event.'_pushed', function () use ($event, $payload) {
156
            $this->dispatch($event, $payload);
157
        });
158
    }
159
 
160
    /**
161
     * Flush a set of pushed events.
162
     *
163
     * @param  string  $event
164
     * @return void
165
     */
166
    public function flush($event)
167
    {
168
        $this->dispatch($event.'_pushed');
169
    }
170
 
171
    /**
172
     * Register an event subscriber with the dispatcher.
173
     *
174
     * @param  object|string  $subscriber
175
     * @return void
176
     */
177
    public function subscribe($subscriber)
178
    {
179
        $subscriber = $this->resolveSubscriber($subscriber);
180
 
181
        $events = $subscriber->subscribe($this);
182
 
183
        if (is_array($events)) {
184
            foreach ($events as $event => $listeners) {
185
                foreach (Arr::wrap($listeners) as $listener) {
186
                    if (is_string($listener) && method_exists($subscriber, $listener)) {
187
                        $this->listen($event, [get_class($subscriber), $listener]);
188
 
189
                        continue;
190
                    }
191
 
192
                    $this->listen($event, $listener);
193
                }
194
            }
195
        }
196
    }
197
 
198
    /**
199
     * Resolve the subscriber instance.
200
     *
201
     * @param  object|string  $subscriber
202
     * @return mixed
203
     */
204
    protected function resolveSubscriber($subscriber)
205
    {
206
        if (is_string($subscriber)) {
207
            return $this->container->make($subscriber);
208
        }
209
 
210
        return $subscriber;
211
    }
212
 
213
    /**
214
     * Fire an event until the first non-null response is returned.
215
     *
216
     * @param  string|object  $event
217
     * @param  mixed  $payload
218
     * @return array|null
219
     */
220
    public function until($event, $payload = [])
221
    {
222
        return $this->dispatch($event, $payload, true);
223
    }
224
 
225
    /**
226
     * Fire an event and call the listeners.
227
     *
228
     * @param  string|object  $event
229
     * @param  mixed  $payload
230
     * @param  bool  $halt
231
     * @return array|null
232
     */
233
    public function dispatch($event, $payload = [], $halt = false)
234
    {
235
        // When the given "event" is actually an object we will assume it is an event
236
        // object and use the class as the event name and this event itself as the
237
        // payload to the handler, which makes object based events quite simple.
238
        [$event, $payload] = $this->parseEventAndPayload(
239
            $event, $payload
240
        );
241
 
242
        if ($this->shouldBroadcast($payload)) {
243
            $this->broadcastEvent($payload[0]);
244
        }
245
 
246
        $responses = [];
247
 
248
        foreach ($this->getListeners($event) as $listener) {
249
            $response = $listener($event, $payload);
250
 
251
            // If a response is returned from the listener and event halting is enabled
252
            // we will just return this response, and not call the rest of the event
253
            // listeners. Otherwise we will add the response on the response list.
254
            if ($halt && ! is_null($response)) {
255
                return $response;
256
            }
257
 
258
            // If a boolean false is returned from a listener, we will stop propagating
259
            // the event to any further listeners down in the chain, else we keep on
260
            // looping through the listeners and firing every one in our sequence.
261
            if ($response === false) {
262
                break;
263
            }
264
 
265
            $responses[] = $response;
266
        }
267
 
268
        return $halt ? null : $responses;
269
    }
270
 
271
    /**
272
     * Parse the given event and payload and prepare them for dispatching.
273
     *
274
     * @param  mixed  $event
275
     * @param  mixed  $payload
276
     * @return array
277
     */
278
    protected function parseEventAndPayload($event, $payload)
279
    {
280
        if (is_object($event)) {
281
            [$payload, $event] = [[$event], get_class($event)];
282
        }
283
 
284
        return [$event, Arr::wrap($payload)];
285
    }
286
 
287
    /**
288
     * Determine if the payload has a broadcastable event.
289
     *
290
     * @param  array  $payload
291
     * @return bool
292
     */
293
    protected function shouldBroadcast(array $payload)
294
    {
295
        return isset($payload[0]) &&
296
               $payload[0] instanceof ShouldBroadcast &&
297
               $this->broadcastWhen($payload[0]);
298
    }
299
 
300
    /**
301
     * Check if the event should be broadcasted by the condition.
302
     *
303
     * @param  mixed  $event
304
     * @return bool
305
     */
306
    protected function broadcastWhen($event)
307
    {
308
        return method_exists($event, 'broadcastWhen')
309
                ? $event->broadcastWhen() : true;
310
    }
311
 
312
    /**
313
     * Broadcast the given event class.
314
     *
315
     * @param  \Illuminate\Contracts\Broadcasting\ShouldBroadcast  $event
316
     * @return void
317
     */
318
    protected function broadcastEvent($event)
319
    {
320
        $this->container->make(BroadcastFactory::class)->queue($event);
321
    }
322
 
323
    /**
324
     * Get all of the listeners for a given event name.
325
     *
326
     * @param  string  $eventName
327
     * @return array
328
     */
329
    public function getListeners($eventName)
330
    {
331
        $listeners = array_merge(
332
            $this->prepareListeners($eventName),
333
            $this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName)
334
        );
335
 
336
        return class_exists($eventName, false)
337
                    ? $this->addInterfaceListeners($eventName, $listeners)
338
                    : $listeners;
339
    }
340
 
341
    /**
342
     * Get the wildcard listeners for the event.
343
     *
344
     * @param  string  $eventName
345
     * @return array
346
     */
347
    protected function getWildcardListeners($eventName)
348
    {
349
        $wildcards = [];
350
 
351
        foreach ($this->wildcards as $key => $listeners) {
352
            if (Str::is($key, $eventName)) {
353
                foreach ($listeners as $listener) {
354
                    $wildcards[] = $this->makeListener($listener, true);
355
                }
356
            }
357
        }
358
 
359
        return $this->wildcardsCache[$eventName] = $wildcards;
360
    }
361
 
362
    /**
363
     * Add the listeners for the event's interfaces to the given array.
364
     *
365
     * @param  string  $eventName
366
     * @param  array  $listeners
367
     * @return array
368
     */
369
    protected function addInterfaceListeners($eventName, array $listeners = [])
370
    {
371
        foreach (class_implements($eventName) as $interface) {
372
            if (isset($this->listeners[$interface])) {
373
                foreach ($this->prepareListeners($interface) as $names) {
374
                    $listeners = array_merge($listeners, (array) $names);
375
                }
376
            }
377
        }
378
 
379
        return $listeners;
380
    }
381
 
382
    /**
383
     * Prepare the listeners for a given event.
384
     *
385
     * @param  string  $eventName
386
     * @return \Closure[]
387
     */
388
    protected function prepareListeners(string $eventName)
389
    {
390
        $listeners = [];
391
 
392
        foreach ($this->listeners[$eventName] ?? [] as $listener) {
393
            $listeners[] = $this->makeListener($listener);
394
        }
395
 
396
        return $listeners;
397
    }
398
 
399
    /**
400
     * Register an event listener with the dispatcher.
401
     *
402
     * @param  \Closure|string|array  $listener
403
     * @param  bool  $wildcard
404
     * @return \Closure
405
     */
406
    public function makeListener($listener, $wildcard = false)
407
    {
408
        if (is_string($listener)) {
409
            return $this->createClassListener($listener, $wildcard);
410
        }
411
 
412
        if (is_array($listener) && isset($listener[0]) && is_string($listener[0])) {
413
            return $this->createClassListener($listener, $wildcard);
414
        }
415
 
416
        return function ($event, $payload) use ($listener, $wildcard) {
417
            if ($wildcard) {
418
                return $listener($event, $payload);
419
            }
420
 
421
            return $listener(...array_values($payload));
422
        };
423
    }
424
 
425
    /**
426
     * Create a class based listener using the IoC container.
427
     *
428
     * @param  string  $listener
429
     * @param  bool  $wildcard
430
     * @return \Closure
431
     */
432
    public function createClassListener($listener, $wildcard = false)
433
    {
434
        return function ($event, $payload) use ($listener, $wildcard) {
435
            if ($wildcard) {
436
                return call_user_func($this->createClassCallable($listener), $event, $payload);
437
            }
438
 
439
            $callable = $this->createClassCallable($listener);
440
 
441
            return $callable(...array_values($payload));
442
        };
443
    }
444
 
445
    /**
446
     * Create the class based event callable.
447
     *
448
     * @param  array|string  $listener
449
     * @return callable
450
     */
451
    protected function createClassCallable($listener)
452
    {
453
        [$class, $method] = is_array($listener)
454
                            ? $listener
455
                            : $this->parseClassCallable($listener);
456
 
457
        if (! method_exists($class, $method)) {
458
            $method = '__invoke';
459
        }
460
 
461
        if ($this->handlerShouldBeQueued($class)) {
462
            return $this->createQueuedHandlerCallable($class, $method);
463
        }
464
 
465
        $listener = $this->container->make($class);
466
 
467
        return $this->handlerShouldBeDispatchedAfterDatabaseTransactions($listener)
468
                    ? $this->createCallbackForListenerRunningAfterCommits($listener, $method)
469
                    : [$listener, $method];
470
    }
471
 
472
    /**
473
     * Parse the class listener into class and method.
474
     *
475
     * @param  string  $listener
476
     * @return array
477
     */
478
    protected function parseClassCallable($listener)
479
    {
480
        return Str::parseCallback($listener, 'handle');
481
    }
482
 
483
    /**
484
     * Determine if the event handler class should be queued.
485
     *
486
     * @param  string  $class
487
     * @return bool
488
     */
489
    protected function handlerShouldBeQueued($class)
490
    {
491
        try {
492
            return (new ReflectionClass($class))->implementsInterface(
493
                ShouldQueue::class
494
            );
495
        } catch (Exception $e) {
496
            return false;
497
        }
498
    }
499
 
500
    /**
501
     * Create a callable for putting an event handler on the queue.
502
     *
503
     * @param  string  $class
504
     * @param  string  $method
505
     * @return \Closure
506
     */
507
    protected function createQueuedHandlerCallable($class, $method)
508
    {
509
        return function () use ($class, $method) {
510
            $arguments = array_map(function ($a) {
511
                return is_object($a) ? clone $a : $a;
512
            }, func_get_args());
513
 
514
            if ($this->handlerWantsToBeQueued($class, $arguments)) {
515
                $this->queueHandler($class, $method, $arguments);
516
            }
517
        };
518
    }
519
 
520
    /**
521
     * Determine if the given event handler should be dispatched after all database transactions have committed.
522
     *
523
     * @param  object|mixed  $listener
524
     * @return bool
525
     */
526
    protected function handlerShouldBeDispatchedAfterDatabaseTransactions($listener)
527
    {
528
        return ($listener->afterCommit ?? null) && $this->container->bound('db.transactions');
529
    }
530
 
531
    /**
532
     * Create a callable for dispatching a listener after database transactions.
533
     *
534
     * @param  mixed  $listener
535
     * @param  string  $method
536
     * @return \Closure
537
     */
538
    protected function createCallbackForListenerRunningAfterCommits($listener, $method)
539
    {
540
        return function () use ($method, $listener) {
541
            $payload = func_get_args();
542
 
543
            $this->container->make('db.transactions')->addCallback(
544
                function () use ($listener, $method, $payload) {
545
                    $listener->$method(...$payload);
546
                }
547
            );
548
        };
549
    }
550
 
551
    /**
552
     * Determine if the event handler wants to be queued.
553
     *
554
     * @param  string  $class
555
     * @param  array  $arguments
556
     * @return bool
557
     */
558
    protected function handlerWantsToBeQueued($class, $arguments)
559
    {
560
        $instance = $this->container->make($class);
561
 
562
        if (method_exists($instance, 'shouldQueue')) {
563
            return $instance->shouldQueue($arguments[0]);
564
        }
565
 
566
        return true;
567
    }
568
 
569
    /**
570
     * Queue the handler class.
571
     *
572
     * @param  string  $class
573
     * @param  string  $method
574
     * @param  array  $arguments
575
     * @return void
576
     */
577
    protected function queueHandler($class, $method, $arguments)
578
    {
579
        [$listener, $job] = $this->createListenerAndJob($class, $method, $arguments);
580
 
581
        $connection = $this->resolveQueue()->connection(method_exists($listener, 'viaConnection')
582
                    ? (isset($arguments[0]) ? $listener->viaConnection($arguments[0]) : $listener->viaConnection())
583
                    : $listener->connection ?? null);
584
 
585
        $queue = method_exists($listener, 'viaQueue')
586
                    ? (isset($arguments[0]) ? $listener->viaQueue($arguments[0]) : $listener->viaQueue())
587
                    : $listener->queue ?? null;
588
 
589
        isset($listener->delay)
590
                    ? $connection->laterOn($queue, $listener->delay, $job)
591
                    : $connection->pushOn($queue, $job);
592
    }
593
 
594
    /**
595
     * Create the listener and job for a queued listener.
596
     *
597
     * @param  string  $class
598
     * @param  string  $method
599
     * @param  array  $arguments
600
     * @return array
601
     */
602
    protected function createListenerAndJob($class, $method, $arguments)
603
    {
604
        $listener = (new ReflectionClass($class))->newInstanceWithoutConstructor();
605
 
606
        return [$listener, $this->propagateListenerOptions(
607
            $listener, new CallQueuedListener($class, $method, $arguments)
608
        )];
609
    }
610
 
611
    /**
612
     * Propagate listener options to the job.
613
     *
614
     * @param  mixed  $listener
615
     * @param  \Illuminate\Events\CallQueuedListener  $job
616
     * @return mixed
617
     */
618
    protected function propagateListenerOptions($listener, $job)
619
    {
620
        return tap($job, function ($job) use ($listener) {
621
            $data = array_values($job->data);
622
 
623
            $job->afterCommit = property_exists($listener, 'afterCommit') ? $listener->afterCommit : null;
624
            $job->backoff = method_exists($listener, 'backoff') ? $listener->backoff(...$data) : ($listener->backoff ?? null);
625
            $job->maxExceptions = $listener->maxExceptions ?? null;
626
            $job->retryUntil = method_exists($listener, 'retryUntil') ? $listener->retryUntil(...$data) : null;
627
            $job->shouldBeEncrypted = $listener instanceof ShouldBeEncrypted;
628
            $job->timeout = $listener->timeout ?? null;
629
            $job->tries = $listener->tries ?? null;
630
 
631
            $job->through(array_merge(
632
                method_exists($listener, 'middleware') ? $listener->middleware(...$data) : [],
633
                $listener->middleware ?? []
634
            ));
635
        });
636
    }
637
 
638
    /**
639
     * Remove a set of listeners from the dispatcher.
640
     *
641
     * @param  string  $event
642
     * @return void
643
     */
644
    public function forget($event)
645
    {
646
        if (str_contains($event, '*')) {
647
            unset($this->wildcards[$event]);
648
        } else {
649
            unset($this->listeners[$event]);
650
        }
651
 
652
        foreach ($this->wildcardsCache as $key => $listeners) {
653
            if (Str::is($event, $key)) {
654
                unset($this->wildcardsCache[$key]);
655
            }
656
        }
657
    }
658
 
659
    /**
660
     * Forget all of the pushed listeners.
661
     *
662
     * @return void
663
     */
664
    public function forgetPushed()
665
    {
666
        foreach ($this->listeners as $key => $value) {
667
            if (str_ends_with($key, '_pushed')) {
668
                $this->forget($key);
669
            }
670
        }
671
    }
672
 
673
    /**
674
     * Get the queue implementation from the resolver.
675
     *
676
     * @return \Illuminate\Contracts\Queue\Queue
677
     */
678
    protected function resolveQueue()
679
    {
680
        return call_user_func($this->queueResolver);
681
    }
682
 
683
    /**
684
     * Set the queue resolver implementation.
685
     *
686
     * @param  callable  $resolver
687
     * @return $this
688
     */
689
    public function setQueueResolver(callable $resolver)
690
    {
691
        $this->queueResolver = $resolver;
692
 
693
        return $this;
694
    }
695
 
696
    /**
697
     * Gets the raw, unprepared listeners.
698
     *
699
     * @return array
700
     */
701
    public function getRawListeners()
702
    {
703
        return $this->listeners;
704
    }
705
}