Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Mail;
4
 
5
use Illuminate\Container\Container;
6
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
7
use Illuminate\Contracts\Mail\Attachable;
8
use Illuminate\Contracts\Mail\Factory as MailFactory;
9
use Illuminate\Contracts\Mail\Mailable as MailableContract;
10
use Illuminate\Contracts\Queue\Factory as Queue;
11
use Illuminate\Contracts\Support\Htmlable;
12
use Illuminate\Contracts\Support\Renderable;
13
use Illuminate\Contracts\Translation\HasLocalePreference;
14
use Illuminate\Support\Collection;
15
use Illuminate\Support\HtmlString;
16
use Illuminate\Support\Str;
17
use Illuminate\Support\Traits\Conditionable;
18
use Illuminate\Support\Traits\ForwardsCalls;
19
use Illuminate\Support\Traits\Localizable;
20
use Illuminate\Support\Traits\Macroable;
21
use Illuminate\Testing\Constraints\SeeInOrder;
22
use PHPUnit\Framework\Assert as PHPUnit;
23
use ReflectionClass;
24
use ReflectionProperty;
25
use Symfony\Component\Mailer\Header\MetadataHeader;
26
use Symfony\Component\Mailer\Header\TagHeader;
27
use Symfony\Component\Mime\Address;
28
 
29
class Mailable implements MailableContract, Renderable
30
{
31
    use Conditionable, ForwardsCalls, Localizable, Macroable {
32
        __call as macroCall;
33
    }
34
 
35
    /**
36
     * The locale of the message.
37
     *
38
     * @var string
39
     */
40
    public $locale;
41
 
42
    /**
43
     * The person the message is from.
44
     *
45
     * @var array
46
     */
47
    public $from = [];
48
 
49
    /**
50
     * The "to" recipients of the message.
51
     *
52
     * @var array
53
     */
54
    public $to = [];
55
 
56
    /**
57
     * The "cc" recipients of the message.
58
     *
59
     * @var array
60
     */
61
    public $cc = [];
62
 
63
    /**
64
     * The "bcc" recipients of the message.
65
     *
66
     * @var array
67
     */
68
    public $bcc = [];
69
 
70
    /**
71
     * The "reply to" recipients of the message.
72
     *
73
     * @var array
74
     */
75
    public $replyTo = [];
76
 
77
    /**
78
     * The subject of the message.
79
     *
80
     * @var string
81
     */
82
    public $subject;
83
 
84
    /**
85
     * The Markdown template for the message (if applicable).
86
     *
87
     * @var string
88
     */
89
    public $markdown;
90
 
91
    /**
92
     * The HTML to use for the message.
93
     *
94
     * @var string
95
     */
96
    protected $html;
97
 
98
    /**
99
     * The view to use for the message.
100
     *
101
     * @var string
102
     */
103
    public $view;
104
 
105
    /**
106
     * The plain text view to use for the message.
107
     *
108
     * @var string
109
     */
110
    public $textView;
111
 
112
    /**
113
     * The view data for the message.
114
     *
115
     * @var array
116
     */
117
    public $viewData = [];
118
 
119
    /**
120
     * The attachments for the message.
121
     *
122
     * @var array
123
     */
124
    public $attachments = [];
125
 
126
    /**
127
     * The raw attachments for the message.
128
     *
129
     * @var array
130
     */
131
    public $rawAttachments = [];
132
 
133
    /**
134
     * The attachments from a storage disk.
135
     *
136
     * @var array
137
     */
138
    public $diskAttachments = [];
139
 
140
    /**
141
     * The tags for the message.
142
     *
143
     * @var array
144
     */
145
    protected $tags = [];
146
 
147
    /**
148
     * The metadata for the message.
149
     *
150
     * @var array
151
     */
152
    protected $metadata = [];
153
 
154
    /**
155
     * The callbacks for the message.
156
     *
157
     * @var array
158
     */
159
    public $callbacks = [];
160
 
161
    /**
162
     * The name of the theme that should be used when formatting the message.
163
     *
164
     * @var string|null
165
     */
166
    public $theme;
167
 
168
    /**
169
     * The name of the mailer that should send the message.
170
     *
171
     * @var string
172
     */
173
    public $mailer;
174
 
175
    /**
176
     * The rendered mailable views for testing / assertions.
177
     *
178
     * @var array
179
     */
180
    protected $assertionableRenderStrings;
181
 
182
    /**
183
     * The callback that should be invoked while building the view data.
184
     *
185
     * @var callable
186
     */
187
    public static $viewDataCallback;
188
 
189
    /**
190
     * Send the message using the given mailer.
191
     *
192
     * @param  \Illuminate\Contracts\Mail\Factory|\Illuminate\Contracts\Mail\Mailer  $mailer
193
     * @return \Illuminate\Mail\SentMessage|null
194
     */
195
    public function send($mailer)
196
    {
197
        return $this->withLocale($this->locale, function () use ($mailer) {
198
            $this->prepareMailableForDelivery();
199
 
200
            $mailer = $mailer instanceof MailFactory
201
                            ? $mailer->mailer($this->mailer)
202
                            : $mailer;
203
 
204
            return $mailer->send($this->buildView(), $this->buildViewData(), function ($message) {
205
                $this->buildFrom($message)
206
                     ->buildRecipients($message)
207
                     ->buildSubject($message)
208
                     ->buildTags($message)
209
                     ->buildMetadata($message)
210
                     ->runCallbacks($message)
211
                     ->buildAttachments($message);
212
            });
213
        });
214
    }
215
 
216
    /**
217
     * Queue the message for sending.
218
     *
219
     * @param  \Illuminate\Contracts\Queue\Factory  $queue
220
     * @return mixed
221
     */
222
    public function queue(Queue $queue)
223
    {
224
        if (isset($this->delay)) {
225
            return $this->later($this->delay, $queue);
226
        }
227
 
228
        $connection = property_exists($this, 'connection') ? $this->connection : null;
229
 
230
        $queueName = property_exists($this, 'queue') ? $this->queue : null;
231
 
232
        return $queue->connection($connection)->pushOn(
233
            $queueName ?: null, $this->newQueuedJob()
234
        );
235
    }
236
 
237
    /**
238
     * Deliver the queued message after (n) seconds.
239
     *
240
     * @param  \DateTimeInterface|\DateInterval|int  $delay
241
     * @param  \Illuminate\Contracts\Queue\Factory  $queue
242
     * @return mixed
243
     */
244
    public function later($delay, Queue $queue)
245
    {
246
        $connection = property_exists($this, 'connection') ? $this->connection : null;
247
 
248
        $queueName = property_exists($this, 'queue') ? $this->queue : null;
249
 
250
        return $queue->connection($connection)->laterOn(
251
            $queueName ?: null, $delay, $this->newQueuedJob()
252
        );
253
    }
254
 
255
    /**
256
     * Make the queued mailable job instance.
257
     *
258
     * @return mixed
259
     */
260
    protected function newQueuedJob()
261
    {
262
        return Container::getInstance()->make(SendQueuedMailable::class, ['mailable' => $this])
263
                    ->through(array_merge(
264
                        method_exists($this, 'middleware') ? $this->middleware() : [],
265
                        $this->middleware ?? []
266
                    ));
267
    }
268
 
269
    /**
270
     * Render the mailable into a view.
271
     *
272
     * @return string
273
     *
274
     * @throws \ReflectionException
275
     */
276
    public function render()
277
    {
278
        return $this->withLocale($this->locale, function () {
279
            $this->prepareMailableForDelivery();
280
 
281
            return Container::getInstance()->make('mailer')->render(
282
                $this->buildView(), $this->buildViewData()
283
            );
284
        });
285
    }
286
 
287
    /**
288
     * Build the view for the message.
289
     *
290
     * @return array|string
291
     *
292
     * @throws \ReflectionException
293
     */
294
    protected function buildView()
295
    {
296
        if (isset($this->html)) {
297
            return array_filter([
298
                'html' => new HtmlString($this->html),
299
                'text' => $this->textView ?? null,
300
            ]);
301
        }
302
 
303
        if (isset($this->markdown)) {
304
            return $this->buildMarkdownView();
305
        }
306
 
307
        if (isset($this->view, $this->textView)) {
308
            return [$this->view, $this->textView];
309
        } elseif (isset($this->textView)) {
310
            return ['text' => $this->textView];
311
        }
312
 
313
        return $this->view;
314
    }
315
 
316
    /**
317
     * Build the Markdown view for the message.
318
     *
319
     * @return array
320
     *
321
     * @throws \ReflectionException
322
     */
323
    protected function buildMarkdownView()
324
    {
325
        $markdown = Container::getInstance()->make(Markdown::class);
326
 
327
        if (isset($this->theme)) {
328
            $markdown->theme($this->theme);
329
        }
330
 
331
        $data = $this->buildViewData();
332
 
333
        return [
334
            'html' => $markdown->render($this->markdown, $data),
335
            'text' => $this->buildMarkdownText($markdown, $data),
336
        ];
337
    }
338
 
339
    /**
340
     * Build the view data for the message.
341
     *
342
     * @return array
343
     *
344
     * @throws \ReflectionException
345
     */
346
    public function buildViewData()
347
    {
348
        $data = $this->viewData;
349
 
350
        if (static::$viewDataCallback) {
351
            $data = array_merge($data, call_user_func(static::$viewDataCallback, $this));
352
        }
353
 
354
        foreach ((new ReflectionClass($this))->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
355
            if ($property->getDeclaringClass()->getName() !== self::class) {
356
                $data[$property->getName()] = $property->getValue($this);
357
            }
358
        }
359
 
360
        return $data;
361
    }
362
 
363
    /**
364
     * Build the text view for a Markdown message.
365
     *
366
     * @param  \Illuminate\Mail\Markdown  $markdown
367
     * @param  array  $data
368
     * @return string
369
     */
370
    protected function buildMarkdownText($markdown, $data)
371
    {
372
        return $this->textView
373
                ?? $markdown->renderText($this->markdown, $data);
374
    }
375
 
376
    /**
377
     * Add the sender to the message.
378
     *
379
     * @param  \Illuminate\Mail\Message  $message
380
     * @return $this
381
     */
382
    protected function buildFrom($message)
383
    {
384
        if (! empty($this->from)) {
385
            $message->from($this->from[0]['address'], $this->from[0]['name']);
386
        }
387
 
388
        return $this;
389
    }
390
 
391
    /**
392
     * Add all of the recipients to the message.
393
     *
394
     * @param  \Illuminate\Mail\Message  $message
395
     * @return $this
396
     */
397
    protected function buildRecipients($message)
398
    {
399
        foreach (['to', 'cc', 'bcc', 'replyTo'] as $type) {
400
            foreach ($this->{$type} as $recipient) {
401
                $message->{$type}($recipient['address'], $recipient['name']);
402
            }
403
        }
404
 
405
        return $this;
406
    }
407
 
408
    /**
409
     * Set the subject for the message.
410
     *
411
     * @param  \Illuminate\Mail\Message  $message
412
     * @return $this
413
     */
414
    protected function buildSubject($message)
415
    {
416
        if ($this->subject) {
417
            $message->subject($this->subject);
418
        } else {
419
            $message->subject(Str::title(Str::snake(class_basename($this), ' ')));
420
        }
421
 
422
        return $this;
423
    }
424
 
425
    /**
426
     * Add all of the attachments to the message.
427
     *
428
     * @param  \Illuminate\Mail\Message  $message
429
     * @return $this
430
     */
431
    protected function buildAttachments($message)
432
    {
433
        foreach ($this->attachments as $attachment) {
434
            $message->attach($attachment['file'], $attachment['options']);
435
        }
436
 
437
        foreach ($this->rawAttachments as $attachment) {
438
            $message->attachData(
439
                $attachment['data'], $attachment['name'], $attachment['options']
440
            );
441
        }
442
 
443
        $this->buildDiskAttachments($message);
444
 
445
        return $this;
446
    }
447
 
448
    /**
449
     * Add all of the disk attachments to the message.
450
     *
451
     * @param  \Illuminate\Mail\Message  $message
452
     * @return void
453
     */
454
    protected function buildDiskAttachments($message)
455
    {
456
        foreach ($this->diskAttachments as $attachment) {
457
            $storage = Container::getInstance()->make(
458
                FilesystemFactory::class
459
            )->disk($attachment['disk']);
460
 
461
            $message->attachData(
462
                $storage->get($attachment['path']),
463
                $attachment['name'] ?? basename($attachment['path']),
464
                array_merge(['mime' => $storage->mimeType($attachment['path'])], $attachment['options'])
465
            );
466
        }
467
    }
468
 
469
    /**
470
     * Add all defined tags to the message.
471
     *
472
     * @param  \Illuminate\Mail\Message  $message
473
     * @return $this
474
     */
475
    protected function buildTags($message)
476
    {
477
        if ($this->tags) {
478
            foreach ($this->tags as $tag) {
479
                $message->getHeaders()->add(new TagHeader($tag));
480
            }
481
        }
482
 
483
        return $this;
484
    }
485
 
486
    /**
487
     * Add all defined metadata to the message.
488
     *
489
     * @param  \Illuminate\Mail\Message  $message
490
     * @return $this
491
     */
492
    protected function buildMetadata($message)
493
    {
494
        if ($this->metadata) {
495
            foreach ($this->metadata as $key => $value) {
496
                $message->getHeaders()->add(new MetadataHeader($key, $value));
497
            }
498
        }
499
 
500
        return $this;
501
    }
502
 
503
    /**
504
     * Run the callbacks for the message.
505
     *
506
     * @param  \Illuminate\Mail\Message  $message
507
     * @return $this
508
     */
509
    protected function runCallbacks($message)
510
    {
511
        foreach ($this->callbacks as $callback) {
512
            $callback($message->getSymfonyMessage());
513
        }
514
 
515
        return $this;
516
    }
517
 
518
    /**
519
     * Set the locale of the message.
520
     *
521
     * @param  string  $locale
522
     * @return $this
523
     */
524
    public function locale($locale)
525
    {
526
        $this->locale = $locale;
527
 
528
        return $this;
529
    }
530
 
531
    /**
532
     * Set the priority of this message.
533
     *
534
     * The value is an integer where 1 is the highest priority and 5 is the lowest.
535
     *
536
     * @param  int  $level
537
     * @return $this
538
     */
539
    public function priority($level = 3)
540
    {
541
        $this->callbacks[] = function ($message) use ($level) {
542
            $message->priority($level);
543
        };
544
 
545
        return $this;
546
    }
547
 
548
    /**
549
     * Set the sender of the message.
550
     *
551
     * @param  object|array|string  $address
552
     * @param  string|null  $name
553
     * @return $this
554
     */
555
    public function from($address, $name = null)
556
    {
557
        return $this->setAddress($address, $name, 'from');
558
    }
559
 
560
    /**
561
     * Determine if the given recipient is set on the mailable.
562
     *
563
     * @param  object|array|string  $address
564
     * @param  string|null  $name
565
     * @return bool
566
     */
567
    public function hasFrom($address, $name = null)
568
    {
569
        return $this->hasRecipient($address, $name, 'from');
570
    }
571
 
572
    /**
573
     * Set the recipients of the message.
574
     *
575
     * @param  object|array|string  $address
576
     * @param  string|null  $name
577
     * @return $this
578
     */
579
    public function to($address, $name = null)
580
    {
581
        if (! $this->locale && $address instanceof HasLocalePreference) {
582
            $this->locale($address->preferredLocale());
583
        }
584
 
585
        return $this->setAddress($address, $name, 'to');
586
    }
587
 
588
    /**
589
     * Determine if the given recipient is set on the mailable.
590
     *
591
     * @param  object|array|string  $address
592
     * @param  string|null  $name
593
     * @return bool
594
     */
595
    public function hasTo($address, $name = null)
596
    {
597
        return $this->hasRecipient($address, $name, 'to');
598
    }
599
 
600
    /**
601
     * Set the recipients of the message.
602
     *
603
     * @param  object|array|string  $address
604
     * @param  string|null  $name
605
     * @return $this
606
     */
607
    public function cc($address, $name = null)
608
    {
609
        return $this->setAddress($address, $name, 'cc');
610
    }
611
 
612
    /**
613
     * Determine if the given recipient is set on the mailable.
614
     *
615
     * @param  object|array|string  $address
616
     * @param  string|null  $name
617
     * @return bool
618
     */
619
    public function hasCc($address, $name = null)
620
    {
621
        return $this->hasRecipient($address, $name, 'cc');
622
    }
623
 
624
    /**
625
     * Set the recipients of the message.
626
     *
627
     * @param  object|array|string  $address
628
     * @param  string|null  $name
629
     * @return $this
630
     */
631
    public function bcc($address, $name = null)
632
    {
633
        return $this->setAddress($address, $name, 'bcc');
634
    }
635
 
636
    /**
637
     * Determine if the given recipient is set on the mailable.
638
     *
639
     * @param  object|array|string  $address
640
     * @param  string|null  $name
641
     * @return bool
642
     */
643
    public function hasBcc($address, $name = null)
644
    {
645
        return $this->hasRecipient($address, $name, 'bcc');
646
    }
647
 
648
    /**
649
     * Set the "reply to" address of the message.
650
     *
651
     * @param  object|array|string  $address
652
     * @param  string|null  $name
653
     * @return $this
654
     */
655
    public function replyTo($address, $name = null)
656
    {
657
        return $this->setAddress($address, $name, 'replyTo');
658
    }
659
 
660
    /**
661
     * Determine if the given replyTo is set on the mailable.
662
     *
663
     * @param  object|array|string  $address
664
     * @param  string|null  $name
665
     * @return bool
666
     */
667
    public function hasReplyTo($address, $name = null)
668
    {
669
        return $this->hasRecipient($address, $name, 'replyTo');
670
    }
671
 
672
    /**
673
     * Set the recipients of the message.
674
     *
675
     * All recipients are stored internally as [['name' => ?, 'address' => ?]]
676
     *
677
     * @param  object|array|string  $address
678
     * @param  string|null  $name
679
     * @param  string  $property
680
     * @return $this
681
     */
682
    protected function setAddress($address, $name = null, $property = 'to')
683
    {
684
        if (empty($address)) {
685
            return $this;
686
        }
687
 
688
        foreach ($this->addressesToArray($address, $name) as $recipient) {
689
            $recipient = $this->normalizeRecipient($recipient);
690
 
691
            $this->{$property}[] = [
692
                'name' => $recipient->name ?? null,
693
                'address' => $recipient->email,
694
            ];
695
        }
696
 
697
        $this->{$property} = collect($this->{$property})
698
            ->unique('address')
699
            ->values()
700
            ->all();
701
 
702
        return $this;
703
    }
704
 
705
    /**
706
     * Convert the given recipient arguments to an array.
707
     *
708
     * @param  object|array|string  $address
709
     * @param  string|null  $name
710
     * @return array
711
     */
712
    protected function addressesToArray($address, $name)
713
    {
714
        if (! is_array($address) && ! $address instanceof Collection) {
715
            $address = is_string($name) ? [['name' => $name, 'email' => $address]] : [$address];
716
        }
717
 
718
        return $address;
719
    }
720
 
721
    /**
722
     * Convert the given recipient into an object.
723
     *
724
     * @param  mixed  $recipient
725
     * @return object
726
     */
727
    protected function normalizeRecipient($recipient)
728
    {
729
        if (is_array($recipient)) {
730
            if (array_values($recipient) === $recipient) {
731
                return (object) array_map(function ($email) {
732
                    return compact('email');
733
                }, $recipient);
734
            }
735
 
736
            return (object) $recipient;
737
        } elseif (is_string($recipient)) {
738
            return (object) ['email' => $recipient];
739
        } elseif ($recipient instanceof Address) {
740
            return (object) ['email' => $recipient->getAddress(), 'name' => $recipient->getName()];
741
        } elseif ($recipient instanceof Mailables\Address) {
742
            return (object) ['email' => $recipient->address, 'name' => $recipient->name];
743
        }
744
 
745
        return $recipient;
746
    }
747
 
748
    /**
749
     * Determine if the given recipient is set on the mailable.
750
     *
751
     * @param  object|array|string  $address
752
     * @param  string|null  $name
753
     * @param  string  $property
754
     * @return bool
755
     */
756
    protected function hasRecipient($address, $name = null, $property = 'to')
757
    {
758
        if (empty($address)) {
759
            return false;
760
        }
761
 
762
        $expected = $this->normalizeRecipient(
763
            $this->addressesToArray($address, $name)[0]
764
        );
765
 
766
        $expected = [
767
            'name' => $expected->name ?? null,
768
            'address' => $expected->email,
769
        ];
770
 
771
        if ($this->hasEnvelopeRecipient($expected['address'], $expected['name'], $property)) {
772
            return true;
773
        }
774
 
775
        return collect($this->{$property})->contains(function ($actual) use ($expected) {
776
            if (! isset($expected['name'])) {
777
                return $actual['address'] == $expected['address'];
778
            }
779
 
780
            return $actual == $expected;
781
        });
782
    }
783
 
784
    /**
785
     * Determine if the mailable "envelope" method defines a recipient.
786
     *
787
     * @param  string  $address
788
     * @param  string|null  $name
789
     * @param  string  $property
790
     * @return bool
791
     */
792
    private function hasEnvelopeRecipient($address, $name, $property)
793
    {
794
        return method_exists($this, 'envelope') && match ($property) {
795
            'from' => $this->envelope()->isFrom($address, $name),
796
            'to' => $this->envelope()->hasTo($address, $name),
797
            'cc' => $this->envelope()->hasCc($address, $name),
798
            'bcc' => $this->envelope()->hasBcc($address, $name),
799
            'replyTo' => $this->envelope()->hasReplyTo($address, $name),
800
        };
801
    }
802
 
803
    /**
804
     * Set the subject of the message.
805
     *
806
     * @param  string  $subject
807
     * @return $this
808
     */
809
    public function subject($subject)
810
    {
811
        $this->subject = $subject;
812
 
813
        return $this;
814
    }
815
 
816
    /**
817
     * Determine if the mailable has the given subject.
818
     *
819
     * @param  string  $subject
820
     * @return bool
821
     */
822
    public function hasSubject($subject)
823
    {
824
        return $this->subject === $subject ||
825
               (method_exists($this, 'envelope') && $this->envelope()->hasSubject($subject));
826
    }
827
 
828
    /**
829
     * Set the Markdown template for the message.
830
     *
831
     * @param  string  $view
832
     * @param  array  $data
833
     * @return $this
834
     */
835
    public function markdown($view, array $data = [])
836
    {
837
        $this->markdown = $view;
838
        $this->viewData = array_merge($this->viewData, $data);
839
 
840
        return $this;
841
    }
842
 
843
    /**
844
     * Set the view and view data for the message.
845
     *
846
     * @param  string  $view
847
     * @param  array  $data
848
     * @return $this
849
     */
850
    public function view($view, array $data = [])
851
    {
852
        $this->view = $view;
853
        $this->viewData = array_merge($this->viewData, $data);
854
 
855
        return $this;
856
    }
857
 
858
    /**
859
     * Set the rendered HTML content for the message.
860
     *
861
     * @param  string  $html
862
     * @return $this
863
     */
864
    public function html($html)
865
    {
866
        $this->html = $html;
867
 
868
        return $this;
869
    }
870
 
871
    /**
872
     * Set the plain text view for the message.
873
     *
874
     * @param  string  $textView
875
     * @param  array  $data
876
     * @return $this
877
     */
878
    public function text($textView, array $data = [])
879
    {
880
        $this->textView = $textView;
881
        $this->viewData = array_merge($this->viewData, $data);
882
 
883
        return $this;
884
    }
885
 
886
    /**
887
     * Set the view data for the message.
888
     *
889
     * @param  string|array  $key
890
     * @param  mixed  $value
891
     * @return $this
892
     */
893
    public function with($key, $value = null)
894
    {
895
        if (is_array($key)) {
896
            $this->viewData = array_merge($this->viewData, $key);
897
        } else {
898
            $this->viewData[$key] = $value;
899
        }
900
 
901
        return $this;
902
    }
903
 
904
    /**
905
     * Attach a file to the message.
906
     *
907
     * @param  string|\Illuminate\Contracts\Mail\Attachable|\Illuminate\Mail\Attachment  $file
908
     * @param  array  $options
909
     * @return $this
910
     */
911
    public function attach($file, array $options = [])
912
    {
913
        if ($file instanceof Attachable) {
914
            $file = $file->toMailAttachment();
915
        }
916
 
917
        if ($file instanceof Attachment) {
918
            return $file->attachTo($this);
919
        }
920
 
921
        $this->attachments = collect($this->attachments)
922
                    ->push(compact('file', 'options'))
923
                    ->unique('file')
924
                    ->all();
925
 
926
        return $this;
927
    }
928
 
929
    /**
930
     * Attach multiple files to the message.
931
     *
932
     * @param  array  $files
933
     * @return $this
934
     */
935
    public function attachMany($files)
936
    {
937
        foreach ($files as $file => $options) {
938
            if (is_int($file)) {
939
                $this->attach($options);
940
            } else {
941
                $this->attach($file, $options);
942
            }
943
        }
944
 
945
        return $this;
946
    }
947
 
948
    /**
949
     * Determine if the mailable has the given attachment.
950
     *
951
     * @param  string|\Illuminate\Contracts\Mail\Attachable|\Illuminate\Mail\Attachment  $file
952
     * @param  array  $options
953
     * @return bool
954
     */
955
    public function hasAttachment($file, array $options = [])
956
    {
957
        if ($file instanceof Attachable) {
958
            $file = $file->toMailAttachment();
959
        }
960
 
961
        if ($file instanceof Attachment && $this->hasEnvelopeAttachment($file)) {
962
            return true;
963
        }
964
 
965
        if ($file instanceof Attachment) {
966
            $parts = $file->attachWith(
967
                fn ($path) => [$path, ['as' => $file->as, 'mime' => $file->mime]],
968
                fn ($data) => $this->hasAttachedData($data(), $file->as, ['mime' => $file->mime])
969
            );
970
 
971
            if ($parts === true) {
972
                return true;
973
            }
974
 
975
            [$file, $options] = $parts === false
976
                ? [null, []]
977
                : $parts;
978
        }
979
 
980
        return collect($this->attachments)->contains(
981
            fn ($attachment) => $attachment['file'] === $file && array_filter($attachment['options']) === array_filter($options)
982
        );
983
    }
984
 
985
    /**
986
     * Determine if the mailable has the given envelope attachment.
987
     *
988
     * @param  \Illuminate\Mail\Attachment  $attachment
989
     * @return bool
990
     */
991
    private function hasEnvelopeAttachment($attachment)
992
    {
993
        if (! method_exists($this, 'envelope')) {
994
            return false;
995
        }
996
 
997
        $attachments = $this->attachments();
998
 
999
        return Collection::make(is_object($attachments) ? [$attachments] : $attachments)
1000
                ->map(fn ($attached) => $attached instanceof Attachable ? $attached->toMailAttachment() : $attached)
1001
                ->contains(fn ($attached) => $attached->isEquivalent($attachment));
1002
    }
1003
 
1004
    /**
1005
     * Attach a file to the message from storage.
1006
     *
1007
     * @param  string  $path
1008
     * @param  string|null  $name
1009
     * @param  array  $options
1010
     * @return $this
1011
     */
1012
    public function attachFromStorage($path, $name = null, array $options = [])
1013
    {
1014
        return $this->attachFromStorageDisk(null, $path, $name, $options);
1015
    }
1016
 
1017
    /**
1018
     * Attach a file to the message from storage.
1019
     *
1020
     * @param  string  $disk
1021
     * @param  string  $path
1022
     * @param  string|null  $name
1023
     * @param  array  $options
1024
     * @return $this
1025
     */
1026
    public function attachFromStorageDisk($disk, $path, $name = null, array $options = [])
1027
    {
1028
        $this->diskAttachments = collect($this->diskAttachments)->push([
1029
            'disk' => $disk,
1030
            'path' => $path,
1031
            'name' => $name ?? basename($path),
1032
            'options' => $options,
1033
        ])->unique(function ($file) {
1034
            return $file['name'].$file['disk'].$file['path'];
1035
        })->all();
1036
 
1037
        return $this;
1038
    }
1039
 
1040
    /**
1041
     * Determine if the mailable has the given attachment from storage.
1042
     *
1043
     * @param  string  $path
1044
     * @param  string|null  $name
1045
     * @param  array  $options
1046
     * @return bool
1047
     */
1048
    public function hasAttachmentFromStorage($path, $name = null, array $options = [])
1049
    {
1050
        return $this->hasAttachmentFromStorageDisk(null, $path, $name, $options);
1051
    }
1052
 
1053
    /**
1054
     * Determine if the mailable has the given attachment from a specific storage disk.
1055
     *
1056
     * @param  string  $disk
1057
     * @param  string  $path
1058
     * @param  string|null  $name
1059
     * @param  array  $options
1060
     * @return bool
1061
     */
1062
    public function hasAttachmentFromStorageDisk($disk, $path, $name = null, array $options = [])
1063
    {
1064
        return collect($this->diskAttachments)->contains(
1065
            fn ($attachment) => $attachment['disk'] === $disk
1066
                && $attachment['path'] === $path
1067
                && $attachment['name'] === ($name ?? basename($path))
1068
                && $attachment['options'] === $options
1069
        );
1070
    }
1071
 
1072
    /**
1073
     * Attach in-memory data as an attachment.
1074
     *
1075
     * @param  string  $data
1076
     * @param  string  $name
1077
     * @param  array  $options
1078
     * @return $this
1079
     */
1080
    public function attachData($data, $name, array $options = [])
1081
    {
1082
        $this->rawAttachments = collect($this->rawAttachments)
1083
                ->push(compact('data', 'name', 'options'))
1084
                ->unique(function ($file) {
1085
                    return $file['name'].$file['data'];
1086
                })->all();
1087
 
1088
        return $this;
1089
    }
1090
 
1091
    /**
1092
     * Determine if the mailable has the given data as an attachment.
1093
     *
1094
     * @param  string  $data
1095
     * @param  string  $name
1096
     * @param  array  $options
1097
     * @return bool
1098
     */
1099
    public function hasAttachedData($data, $name, array $options = [])
1100
    {
1101
        return collect($this->rawAttachments)->contains(
1102
            fn ($attachment) => $attachment['data'] === $data
1103
                && $attachment['name'] === $name
1104
                && array_filter($attachment['options']) === array_filter($options)
1105
        );
1106
    }
1107
 
1108
    /**
1109
     * Add a tag header to the message when supported by the underlying transport.
1110
     *
1111
     * @param  string  $value
1112
     * @return $this
1113
     */
1114
    public function tag($value)
1115
    {
1116
        array_push($this->tags, $value);
1117
 
1118
        return $this;
1119
    }
1120
 
1121
    /**
1122
     * Determine if the mailable has the given tag.
1123
     *
1124
     * @param  string  $value
1125
     * @return bool
1126
     */
1127
    public function hasTag($value)
1128
    {
1129
        return in_array($value, $this->tags) ||
1130
               (method_exists($this, 'envelope') && in_array($value, $this->envelope()->tags));
1131
    }
1132
 
1133
    /**
1134
     * Add a metadata header to the message when supported by the underlying transport.
1135
     *
1136
     * @param  string  $key
1137
     * @param  string  $value
1138
     * @return $this
1139
     */
1140
    public function metadata($key, $value)
1141
    {
1142
        $this->metadata[$key] = $value;
1143
 
1144
        return $this;
1145
    }
1146
 
1147
    /**
1148
     * Determine if the mailable has the given metadata.
1149
     *
1150
     * @param  string  $key
1151
     * @param  string  $value
1152
     * @return bool
1153
     */
1154
    public function hasMetadata($key, $value)
1155
    {
1156
        return (isset($this->metadata[$key]) && $this->metadata[$key] === $value) ||
1157
               (method_exists($this, 'envelope') && $this->envelope()->hasMetadata($key, $value));
1158
    }
1159
 
1160
    /**
1161
     * Assert that the mailable is from the given address.
1162
     *
1163
     * @param  object|array|string  $address
1164
     * @param  string|null  $name
1165
     * @return $this
1166
     */
1167
    public function assertFrom($address, $name = null)
1168
    {
1169
        $recipient = $this->formatAssertionRecipient($address, $name);
1170
 
1171
        PHPUnit::assertTrue(
1172
            $this->hasFrom($address, $name),
1173
            "Email was not from expected address [{$recipient}]."
1174
        );
1175
 
1176
        return $this;
1177
    }
1178
 
1179
    /**
1180
     * Assert that the mailable has the given recipient.
1181
     *
1182
     * @param  object|array|string  $address
1183
     * @param  string|null  $name
1184
     * @return $this
1185
     */
1186
    public function assertTo($address, $name = null)
1187
    {
1188
        $recipient = $this->formatAssertionRecipient($address, $name);
1189
 
1190
        PHPUnit::assertTrue(
1191
            $this->hasTo($address, $name),
1192
            "Did not see expected recipient [{$recipient}] in email recipients."
1193
        );
1194
 
1195
        return $this;
1196
    }
1197
 
1198
    /**
1199
     * Assert that the mailable has the given recipient.
1200
     *
1201
     * @param  object|array|string  $address
1202
     * @param  string|null  $name
1203
     * @return $this
1204
     */
1205
    public function assertHasTo($address, $name = null)
1206
    {
1207
        return $this->assertTo($address, $name);
1208
    }
1209
 
1210
    /**
1211
     * Assert that the mailable has the given recipient.
1212
     *
1213
     * @param  object|array|string  $address
1214
     * @param  string|null  $name
1215
     * @return $this
1216
     */
1217
    public function assertHasCc($address, $name = null)
1218
    {
1219
        $recipient = $this->formatAssertionRecipient($address, $name);
1220
 
1221
        PHPUnit::assertTrue(
1222
            $this->hasCc($address, $name),
1223
            "Did not see expected recipient [{$recipient}] in email recipients."
1224
        );
1225
 
1226
        return $this;
1227
    }
1228
 
1229
    /**
1230
     * Assert that the mailable has the given recipient.
1231
     *
1232
     * @param  object|array|string  $address
1233
     * @param  string|null  $name
1234
     * @return $this
1235
     */
1236
    public function assertHasBcc($address, $name = null)
1237
    {
1238
        $recipient = $this->formatAssertionRecipient($address, $name);
1239
 
1240
        PHPUnit::assertTrue(
1241
            $this->hasBcc($address, $name),
1242
            "Did not see expected recipient [{$recipient}] in email recipients."
1243
        );
1244
 
1245
        return $this;
1246
    }
1247
 
1248
    /**
1249
     * Assert that the mailable has the given "reply to" address.
1250
     *
1251
     * @param  object|array|string  $address
1252
     * @param  string|null  $name
1253
     * @return $this
1254
     */
1255
    public function assertHasReplyTo($address, $name = null)
1256
    {
1257
        $replyTo = $this->formatAssertionRecipient($address, $name);
1258
 
1259
        PHPUnit::assertTrue(
1260
            $this->hasReplyTo($address, $name),
1261
            "Did not see expected address [{$replyTo}] as email 'reply to' recipient."
1262
        );
1263
 
1264
        return $this;
1265
    }
1266
 
1267
    /**
1268
     * Format the mailable recipeint for display in an assertion message.
1269
     *
1270
     * @param  object|array|string  $address
1271
     * @param  string|null  $name
1272
     * @return string
1273
     */
1274
    private function formatAssertionRecipient($address, $name = null)
1275
    {
1276
        if (! is_string($address)) {
1277
            $address = json_encode($address);
1278
        }
1279
 
1280
        if (filled($name)) {
1281
            $address .= ' ('.$name.')';
1282
        }
1283
 
1284
        return $address;
1285
    }
1286
 
1287
    /**
1288
     * Assert that the mailable has the given subject.
1289
     *
1290
     * @param  string  $subject
1291
     * @return $this
1292
     */
1293
    public function assertHasSubject($subject)
1294
    {
1295
        PHPUnit::assertTrue(
1296
            $this->hasSubject($subject),
1297
            "Did not see expected text [{$subject}] in email subject."
1298
        );
1299
 
1300
        return $this;
1301
    }
1302
 
1303
    /**
1304
     * Assert that the given text is present in the HTML email body.
1305
     *
1306
     * @param  string  $string
1307
     * @return $this
1308
     */
1309
    public function assertSeeInHtml($string)
1310
    {
1311
        [$html, $text] = $this->renderForAssertions();
1312
 
1313
        PHPUnit::assertStringContainsString(
1314
            $string,
1315
            $html,
1316
            "Did not see expected text [{$string}] within email body."
1317
        );
1318
 
1319
        return $this;
1320
    }
1321
 
1322
    /**
1323
     * Assert that the given text is not present in the HTML email body.
1324
     *
1325
     * @param  string  $string
1326
     * @return $this
1327
     */
1328
    public function assertDontSeeInHtml($string)
1329
    {
1330
        [$html, $text] = $this->renderForAssertions();
1331
 
1332
        PHPUnit::assertStringNotContainsString(
1333
            $string,
1334
            $html,
1335
            "Saw unexpected text [{$string}] within email body."
1336
        );
1337
 
1338
        return $this;
1339
    }
1340
 
1341
    /**
1342
     * Assert that the given text strings are present in order in the HTML email body.
1343
     *
1344
     * @param  array  $strings
1345
     * @return $this
1346
     */
1347
    public function assertSeeInOrderInHtml($strings)
1348
    {
1349
        [$html, $text] = $this->renderForAssertions();
1350
 
1351
        PHPUnit::assertThat($strings, new SeeInOrder($html));
1352
 
1353
        return $this;
1354
    }
1355
 
1356
    /**
1357
     * Assert that the given text is present in the plain-text email body.
1358
     *
1359
     * @param  string  $string
1360
     * @return $this
1361
     */
1362
    public function assertSeeInText($string)
1363
    {
1364
        [$html, $text] = $this->renderForAssertions();
1365
 
1366
        PHPUnit::assertStringContainsString(
1367
            $string,
1368
            $text,
1369
            "Did not see expected text [{$string}] within text email body."
1370
        );
1371
 
1372
        return $this;
1373
    }
1374
 
1375
    /**
1376
     * Assert that the given text is not present in the plain-text email body.
1377
     *
1378
     * @param  string  $string
1379
     * @return $this
1380
     */
1381
    public function assertDontSeeInText($string)
1382
    {
1383
        [$html, $text] = $this->renderForAssertions();
1384
 
1385
        PHPUnit::assertStringNotContainsString(
1386
            $string,
1387
            $text,
1388
            "Saw unexpected text [{$string}] within text email body."
1389
        );
1390
 
1391
        return $this;
1392
    }
1393
 
1394
    /**
1395
     * Assert that the given text strings are present in order in the plain-text email body.
1396
     *
1397
     * @param  array  $strings
1398
     * @return $this
1399
     */
1400
    public function assertSeeInOrderInText($strings)
1401
    {
1402
        [$html, $text] = $this->renderForAssertions();
1403
 
1404
        PHPUnit::assertThat($strings, new SeeInOrder($text));
1405
 
1406
        return $this;
1407
    }
1408
 
1409
    /**
1410
     * Assert the mailable has the given attachment.
1411
     *
1412
     * @param  string|\Illuminate\Contracts\Mail\Attachable|\Illuminate\Mail\Attachment  $file
1413
     * @param  array  $options
1414
     * @return $this
1415
     */
1416
    public function assertHasAttachment($file, array $options = [])
1417
    {
1418
        $this->renderForAssertions();
1419
 
1420
        PHPUnit::assertTrue(
1421
            $this->hasAttachment($file, $options),
1422
            'Did not find the expected attachment.'
1423
        );
1424
 
1425
        return $this;
1426
    }
1427
 
1428
    /**
1429
     * Assert the mailable has the given data as an attachment.
1430
     *
1431
     * @param  string  $data
1432
     * @param  string  $name
1433
     * @param  array  $options
1434
     * @return $this
1435
     */
1436
    public function assertHasAttachedData($data, $name, array $options = [])
1437
    {
1438
        $this->renderForAssertions();
1439
 
1440
        PHPUnit::assertTrue(
1441
            $this->hasAttachedData($data, $name, $options),
1442
            'Did not find the expected attachment.'
1443
        );
1444
 
1445
        return $this;
1446
    }
1447
 
1448
    /**
1449
     * Assert the mailable has the given attachment from storage.
1450
     *
1451
     * @param  string  $path
1452
     * @param  string|null  $name
1453
     * @param  array  $options
1454
     * @return $this
1455
     */
1456
    public function assertHasAttachmentFromStorage($path, $name = null, array $options = [])
1457
    {
1458
        $this->renderForAssertions();
1459
 
1460
        PHPUnit::assertTrue(
1461
            $this->hasAttachmentFromStorage($path, $name, $options),
1462
            'Did not find the expected attachment.'
1463
        );
1464
 
1465
        return $this;
1466
    }
1467
 
1468
    /**
1469
     * Assert the mailable has the given attachment from a specific storage disk.
1470
     *
1471
     * @param  string  $disk
1472
     * @param  string  $path
1473
     * @param  string|null  $name
1474
     * @param  array  $options
1475
     * @return $this
1476
     */
1477
    public function assertHasAttachmentFromStorageDisk($disk, $path, $name = null, array $options = [])
1478
    {
1479
        $this->renderForAssertions();
1480
 
1481
        PHPUnit::assertTrue(
1482
            $this->hasAttachmentFromStorageDisk($disk, $path, $name, $options),
1483
            'Did not find the expected attachment.'
1484
        );
1485
 
1486
        return $this;
1487
    }
1488
 
1489
    /**
1490
     * Assert that the mailable has the given tag.
1491
     *
1492
     * @param  string  $tag
1493
     * @return $this
1494
     */
1495
    public function assertHasTag($tag)
1496
    {
1497
        PHPUnit::assertTrue(
1498
            $this->hasTag($tag),
1499
            "Did not see expected tag [{$tag}] in email tags."
1500
        );
1501
 
1502
        return $this;
1503
    }
1504
 
1505
    /**
1506
     * Assert that the mailable has the given metadata.
1507
     *
1508
     * @param  string  $key
1509
     * @param  string  $value
1510
     * @return $this
1511
     */
1512
    public function assertHasMetadata($key, $value)
1513
    {
1514
        PHPUnit::assertTrue(
1515
            $this->hasMetadata($key, $value),
1516
            "Did not see expected key [{$key}] and value [{$value}] in email metadata."
1517
        );
1518
 
1519
        return $this;
1520
    }
1521
 
1522
    /**
1523
     * Render the HTML and plain-text version of the mailable into views for assertions.
1524
     *
1525
     * @return array
1526
     *
1527
     * @throws \ReflectionException
1528
     */
1529
    protected function renderForAssertions()
1530
    {
1531
        if ($this->assertionableRenderStrings) {
1532
            return $this->assertionableRenderStrings;
1533
        }
1534
 
1535
        return $this->assertionableRenderStrings = $this->withLocale($this->locale, function () {
1536
            $this->prepareMailableForDelivery();
1537
 
1538
            $html = Container::getInstance()->make('mailer')->render(
1539
                $view = $this->buildView(), $this->buildViewData()
1540
            );
1541
 
1542
            if (is_array($view) && isset($view[1])) {
1543
                $text = $view[1];
1544
            }
1545
 
1546
            $text ??= $view['text'] ?? '';
1547
 
1548
            if (! empty($text) && ! $text instanceof Htmlable) {
1549
                $text = Container::getInstance()->make('mailer')->render(
1550
                    $text, $this->buildViewData()
1551
                );
1552
            }
1553
 
1554
            return [(string) $html, (string) $text];
1555
        });
1556
    }
1557
 
1558
    /**
1559
     * Prepare the mailable instance for delivery.
1560
     *
1561
     * @return void
1562
     */
1563
    private function prepareMailableForDelivery()
1564
    {
1565
        if (method_exists($this, 'build')) {
1566
            Container::getInstance()->call([$this, 'build']);
1567
        }
1568
 
1569
        $this->ensureHeadersAreHydrated();
1570
        $this->ensureEnvelopeIsHydrated();
1571
        $this->ensureContentIsHydrated();
1572
        $this->ensureAttachmentsAreHydrated();
1573
    }
1574
 
1575
    /**
1576
     * Ensure the mailable's headers are hydrated from the "headers" method.
1577
     *
1578
     * @return void
1579
     */
1580
    private function ensureHeadersAreHydrated()
1581
    {
1582
        if (! method_exists($this, 'headers')) {
1583
            return;
1584
        }
1585
 
1586
        $headers = $this->headers();
1587
 
1588
        $this->withSymfonyMessage(function ($message) use ($headers) {
1589
            if ($headers->messageId) {
1590
                $message->getHeaders()->addIdHeader('Message-Id', $headers->messageId);
1591
            }
1592
 
1593
            if (count($headers->references) > 0) {
1594
                $message->getHeaders()->addTextHeader('References', $headers->referencesString());
1595
            }
1596
 
1597
            foreach ($headers->text as $key => $value) {
1598
                $message->getHeaders()->addTextHeader($key, $value);
1599
            }
1600
        });
1601
    }
1602
 
1603
    /**
1604
     * Ensure the mailable's "envelope" data is hydrated from the "envelope" method.
1605
     *
1606
     * @return void
1607
     */
1608
    private function ensureEnvelopeIsHydrated()
1609
    {
1610
        if (! method_exists($this, 'envelope')) {
1611
            return;
1612
        }
1613
 
1614
        $envelope = $this->envelope();
1615
 
1616
        if (isset($envelope->from)) {
1617
            $this->from($envelope->from->address, $envelope->from->name);
1618
        }
1619
 
1620
        foreach (['to', 'cc', 'bcc', 'replyTo'] as $type) {
1621
            foreach ($envelope->{$type} as $address) {
1622
                $this->{$type}($address->address, $address->name);
1623
            }
1624
        }
1625
 
1626
        if ($envelope->subject) {
1627
            $this->subject($envelope->subject);
1628
        }
1629
 
1630
        foreach ($envelope->tags as $tag) {
1631
            $this->tag($tag);
1632
        }
1633
 
1634
        foreach ($envelope->metadata as $key => $value) {
1635
            $this->metadata($key, $value);
1636
        }
1637
 
1638
        foreach ($envelope->using as $callback) {
1639
            $this->withSymfonyMessage($callback);
1640
        }
1641
    }
1642
 
1643
    /**
1644
     * Ensure the mailable's content is hydrated from the "content" method.
1645
     *
1646
     * @return void
1647
     */
1648
    private function ensureContentIsHydrated()
1649
    {
1650
        if (! method_exists($this, 'content')) {
1651
            return;
1652
        }
1653
 
1654
        $content = $this->content();
1655
 
1656
        if ($content->view) {
1657
            $this->view($content->view);
1658
        }
1659
 
1660
        if ($content->html) {
1661
            $this->view($content->html);
1662
        }
1663
 
1664
        if ($content->text) {
1665
            $this->text($content->text);
1666
        }
1667
 
1668
        if ($content->markdown) {
1669
            $this->markdown($content->markdown);
1670
        }
1671
 
1672
        if ($content->htmlString) {
1673
            $this->html($content->htmlString);
1674
        }
1675
 
1676
        foreach ($content->with as $key => $value) {
1677
            $this->with($key, $value);
1678
        }
1679
    }
1680
 
1681
    /**
1682
     * Ensure the mailable's attachments are hydrated from the "attachments" method.
1683
     *
1684
     * @return void
1685
     */
1686
    private function ensureAttachmentsAreHydrated()
1687
    {
1688
        if (! method_exists($this, 'attachments')) {
1689
            return;
1690
        }
1691
 
1692
        $attachments = $this->attachments();
1693
 
1694
        Collection::make(is_object($attachments) ? [$attachments] : $attachments)
1695
            ->each(function ($attachment) {
1696
                $this->attach($attachment);
1697
            });
1698
    }
1699
 
1700
    /**
1701
     * Set the name of the mailer that should send the message.
1702
     *
1703
     * @param  string  $mailer
1704
     * @return $this
1705
     */
1706
    public function mailer($mailer)
1707
    {
1708
        $this->mailer = $mailer;
1709
 
1710
        return $this;
1711
    }
1712
 
1713
    /**
1714
     * Register a callback to be called with the Symfony message instance.
1715
     *
1716
     * @param  callable  $callback
1717
     * @return $this
1718
     */
1719
    public function withSymfonyMessage($callback)
1720
    {
1721
        $this->callbacks[] = $callback;
1722
 
1723
        return $this;
1724
    }
1725
 
1726
    /**
1727
     * Register a callback to be called while building the view data.
1728
     *
1729
     * @param  callable  $callback
1730
     * @return void
1731
     */
1732
    public static function buildViewDataUsing(callable $callback)
1733
    {
1734
        static::$viewDataCallback = $callback;
1735
    }
1736
 
1737
    /**
1738
     * Dynamically bind parameters to the message.
1739
     *
1740
     * @param  string  $method
1741
     * @param  array  $parameters
1742
     * @return $this
1743
     *
1744
     * @throws \BadMethodCallException
1745
     */
1746
    public function __call($method, $parameters)
1747
    {
1748
        if (static::hasMacro($method)) {
1749
            return $this->macroCall($method, $parameters);
1750
        }
1751
 
1752
        if (str_starts_with($method, 'with')) {
1753
            return $this->with(Str::camel(substr($method, 4)), $parameters[0]);
1754
        }
1755
 
1756
        static::throwBadMethodCallException($method);
1757
    }
1758
}