Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | 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})
399 lars 698
            ->reverse()
148 lars 699
            ->unique('address')
399 lars 700
            ->reverse()
148 lars 701
            ->values()
702
            ->all();
703
 
704
        return $this;
705
    }
706
 
707
    /**
708
     * Convert the given recipient arguments to an array.
709
     *
710
     * @param  object|array|string  $address
711
     * @param  string|null  $name
712
     * @return array
713
     */
714
    protected function addressesToArray($address, $name)
715
    {
716
        if (! is_array($address) && ! $address instanceof Collection) {
717
            $address = is_string($name) ? [['name' => $name, 'email' => $address]] : [$address];
718
        }
719
 
720
        return $address;
721
    }
722
 
723
    /**
724
     * Convert the given recipient into an object.
725
     *
726
     * @param  mixed  $recipient
727
     * @return object
728
     */
729
    protected function normalizeRecipient($recipient)
730
    {
731
        if (is_array($recipient)) {
732
            if (array_values($recipient) === $recipient) {
733
                return (object) array_map(function ($email) {
734
                    return compact('email');
735
                }, $recipient);
736
            }
737
 
738
            return (object) $recipient;
739
        } elseif (is_string($recipient)) {
740
            return (object) ['email' => $recipient];
741
        } elseif ($recipient instanceof Address) {
742
            return (object) ['email' => $recipient->getAddress(), 'name' => $recipient->getName()];
743
        } elseif ($recipient instanceof Mailables\Address) {
744
            return (object) ['email' => $recipient->address, 'name' => $recipient->name];
745
        }
746
 
747
        return $recipient;
748
    }
749
 
750
    /**
751
     * Determine if the given recipient is set on the mailable.
752
     *
753
     * @param  object|array|string  $address
754
     * @param  string|null  $name
755
     * @param  string  $property
756
     * @return bool
757
     */
758
    protected function hasRecipient($address, $name = null, $property = 'to')
759
    {
760
        if (empty($address)) {
761
            return false;
762
        }
763
 
764
        $expected = $this->normalizeRecipient(
765
            $this->addressesToArray($address, $name)[0]
766
        );
767
 
768
        $expected = [
769
            'name' => $expected->name ?? null,
770
            'address' => $expected->email,
771
        ];
772
 
773
        if ($this->hasEnvelopeRecipient($expected['address'], $expected['name'], $property)) {
774
            return true;
775
        }
776
 
777
        return collect($this->{$property})->contains(function ($actual) use ($expected) {
778
            if (! isset($expected['name'])) {
779
                return $actual['address'] == $expected['address'];
780
            }
781
 
782
            return $actual == $expected;
783
        });
784
    }
785
 
786
    /**
787
     * Determine if the mailable "envelope" method defines a recipient.
788
     *
789
     * @param  string  $address
790
     * @param  string|null  $name
791
     * @param  string  $property
792
     * @return bool
793
     */
794
    private function hasEnvelopeRecipient($address, $name, $property)
795
    {
796
        return method_exists($this, 'envelope') && match ($property) {
797
            'from' => $this->envelope()->isFrom($address, $name),
798
            'to' => $this->envelope()->hasTo($address, $name),
799
            'cc' => $this->envelope()->hasCc($address, $name),
800
            'bcc' => $this->envelope()->hasBcc($address, $name),
801
            'replyTo' => $this->envelope()->hasReplyTo($address, $name),
802
        };
803
    }
804
 
805
    /**
806
     * Set the subject of the message.
807
     *
808
     * @param  string  $subject
809
     * @return $this
810
     */
811
    public function subject($subject)
812
    {
813
        $this->subject = $subject;
814
 
815
        return $this;
816
    }
817
 
818
    /**
819
     * Determine if the mailable has the given subject.
820
     *
821
     * @param  string  $subject
822
     * @return bool
823
     */
824
    public function hasSubject($subject)
825
    {
826
        return $this->subject === $subject ||
827
               (method_exists($this, 'envelope') && $this->envelope()->hasSubject($subject));
828
    }
829
 
830
    /**
831
     * Set the Markdown template for the message.
832
     *
833
     * @param  string  $view
834
     * @param  array  $data
835
     * @return $this
836
     */
837
    public function markdown($view, array $data = [])
838
    {
839
        $this->markdown = $view;
840
        $this->viewData = array_merge($this->viewData, $data);
841
 
842
        return $this;
843
    }
844
 
845
    /**
846
     * Set the view and view data for the message.
847
     *
848
     * @param  string  $view
849
     * @param  array  $data
850
     * @return $this
851
     */
852
    public function view($view, array $data = [])
853
    {
854
        $this->view = $view;
855
        $this->viewData = array_merge($this->viewData, $data);
856
 
857
        return $this;
858
    }
859
 
860
    /**
861
     * Set the rendered HTML content for the message.
862
     *
863
     * @param  string  $html
864
     * @return $this
865
     */
866
    public function html($html)
867
    {
868
        $this->html = $html;
869
 
870
        return $this;
871
    }
872
 
873
    /**
874
     * Set the plain text view for the message.
875
     *
876
     * @param  string  $textView
877
     * @param  array  $data
878
     * @return $this
879
     */
880
    public function text($textView, array $data = [])
881
    {
882
        $this->textView = $textView;
883
        $this->viewData = array_merge($this->viewData, $data);
884
 
885
        return $this;
886
    }
887
 
888
    /**
889
     * Set the view data for the message.
890
     *
891
     * @param  string|array  $key
892
     * @param  mixed  $value
893
     * @return $this
894
     */
895
    public function with($key, $value = null)
896
    {
897
        if (is_array($key)) {
898
            $this->viewData = array_merge($this->viewData, $key);
899
        } else {
900
            $this->viewData[$key] = $value;
901
        }
902
 
903
        return $this;
904
    }
905
 
906
    /**
907
     * Attach a file to the message.
908
     *
909
     * @param  string|\Illuminate\Contracts\Mail\Attachable|\Illuminate\Mail\Attachment  $file
910
     * @param  array  $options
911
     * @return $this
912
     */
913
    public function attach($file, array $options = [])
914
    {
915
        if ($file instanceof Attachable) {
916
            $file = $file->toMailAttachment();
917
        }
918
 
919
        if ($file instanceof Attachment) {
920
            return $file->attachTo($this);
921
        }
922
 
923
        $this->attachments = collect($this->attachments)
924
                    ->push(compact('file', 'options'))
925
                    ->unique('file')
926
                    ->all();
927
 
928
        return $this;
929
    }
930
 
931
    /**
932
     * Attach multiple files to the message.
933
     *
934
     * @param  array  $files
935
     * @return $this
936
     */
937
    public function attachMany($files)
938
    {
939
        foreach ($files as $file => $options) {
940
            if (is_int($file)) {
941
                $this->attach($options);
942
            } else {
943
                $this->attach($file, $options);
944
            }
945
        }
946
 
947
        return $this;
948
    }
949
 
950
    /**
951
     * Determine if the mailable has the given attachment.
952
     *
953
     * @param  string|\Illuminate\Contracts\Mail\Attachable|\Illuminate\Mail\Attachment  $file
954
     * @param  array  $options
955
     * @return bool
956
     */
957
    public function hasAttachment($file, array $options = [])
958
    {
959
        if ($file instanceof Attachable) {
960
            $file = $file->toMailAttachment();
961
        }
962
 
963
        if ($file instanceof Attachment && $this->hasEnvelopeAttachment($file)) {
964
            return true;
965
        }
966
 
967
        if ($file instanceof Attachment) {
968
            $parts = $file->attachWith(
969
                fn ($path) => [$path, ['as' => $file->as, 'mime' => $file->mime]],
970
                fn ($data) => $this->hasAttachedData($data(), $file->as, ['mime' => $file->mime])
971
            );
972
 
973
            if ($parts === true) {
974
                return true;
975
            }
976
 
977
            [$file, $options] = $parts === false
978
                ? [null, []]
979
                : $parts;
980
        }
981
 
982
        return collect($this->attachments)->contains(
983
            fn ($attachment) => $attachment['file'] === $file && array_filter($attachment['options']) === array_filter($options)
984
        );
985
    }
986
 
987
    /**
988
     * Determine if the mailable has the given envelope attachment.
989
     *
990
     * @param  \Illuminate\Mail\Attachment  $attachment
991
     * @return bool
992
     */
993
    private function hasEnvelopeAttachment($attachment)
994
    {
995
        if (! method_exists($this, 'envelope')) {
996
            return false;
997
        }
998
 
999
        $attachments = $this->attachments();
1000
 
1001
        return Collection::make(is_object($attachments) ? [$attachments] : $attachments)
1002
                ->map(fn ($attached) => $attached instanceof Attachable ? $attached->toMailAttachment() : $attached)
1003
                ->contains(fn ($attached) => $attached->isEquivalent($attachment));
1004
    }
1005
 
1006
    /**
1007
     * Attach a file to the message from storage.
1008
     *
1009
     * @param  string  $path
1010
     * @param  string|null  $name
1011
     * @param  array  $options
1012
     * @return $this
1013
     */
1014
    public function attachFromStorage($path, $name = null, array $options = [])
1015
    {
1016
        return $this->attachFromStorageDisk(null, $path, $name, $options);
1017
    }
1018
 
1019
    /**
1020
     * Attach a file to the message from storage.
1021
     *
1022
     * @param  string  $disk
1023
     * @param  string  $path
1024
     * @param  string|null  $name
1025
     * @param  array  $options
1026
     * @return $this
1027
     */
1028
    public function attachFromStorageDisk($disk, $path, $name = null, array $options = [])
1029
    {
1030
        $this->diskAttachments = collect($this->diskAttachments)->push([
1031
            'disk' => $disk,
1032
            'path' => $path,
1033
            'name' => $name ?? basename($path),
1034
            'options' => $options,
1035
        ])->unique(function ($file) {
1036
            return $file['name'].$file['disk'].$file['path'];
1037
        })->all();
1038
 
1039
        return $this;
1040
    }
1041
 
1042
    /**
1043
     * Determine if the mailable has the given attachment from storage.
1044
     *
1045
     * @param  string  $path
1046
     * @param  string|null  $name
1047
     * @param  array  $options
1048
     * @return bool
1049
     */
1050
    public function hasAttachmentFromStorage($path, $name = null, array $options = [])
1051
    {
1052
        return $this->hasAttachmentFromStorageDisk(null, $path, $name, $options);
1053
    }
1054
 
1055
    /**
1056
     * Determine if the mailable has the given attachment from a specific storage disk.
1057
     *
1058
     * @param  string  $disk
1059
     * @param  string  $path
1060
     * @param  string|null  $name
1061
     * @param  array  $options
1062
     * @return bool
1063
     */
1064
    public function hasAttachmentFromStorageDisk($disk, $path, $name = null, array $options = [])
1065
    {
1066
        return collect($this->diskAttachments)->contains(
1067
            fn ($attachment) => $attachment['disk'] === $disk
1068
                && $attachment['path'] === $path
1069
                && $attachment['name'] === ($name ?? basename($path))
1070
                && $attachment['options'] === $options
1071
        );
1072
    }
1073
 
1074
    /**
1075
     * Attach in-memory data as an attachment.
1076
     *
1077
     * @param  string  $data
1078
     * @param  string  $name
1079
     * @param  array  $options
1080
     * @return $this
1081
     */
1082
    public function attachData($data, $name, array $options = [])
1083
    {
1084
        $this->rawAttachments = collect($this->rawAttachments)
1085
                ->push(compact('data', 'name', 'options'))
1086
                ->unique(function ($file) {
1087
                    return $file['name'].$file['data'];
1088
                })->all();
1089
 
1090
        return $this;
1091
    }
1092
 
1093
    /**
1094
     * Determine if the mailable has the given data as an attachment.
1095
     *
1096
     * @param  string  $data
1097
     * @param  string  $name
1098
     * @param  array  $options
1099
     * @return bool
1100
     */
1101
    public function hasAttachedData($data, $name, array $options = [])
1102
    {
1103
        return collect($this->rawAttachments)->contains(
1104
            fn ($attachment) => $attachment['data'] === $data
1105
                && $attachment['name'] === $name
1106
                && array_filter($attachment['options']) === array_filter($options)
1107
        );
1108
    }
1109
 
1110
    /**
1111
     * Add a tag header to the message when supported by the underlying transport.
1112
     *
1113
     * @param  string  $value
1114
     * @return $this
1115
     */
1116
    public function tag($value)
1117
    {
1118
        array_push($this->tags, $value);
1119
 
1120
        return $this;
1121
    }
1122
 
1123
    /**
1124
     * Determine if the mailable has the given tag.
1125
     *
1126
     * @param  string  $value
1127
     * @return bool
1128
     */
1129
    public function hasTag($value)
1130
    {
1131
        return in_array($value, $this->tags) ||
1132
               (method_exists($this, 'envelope') && in_array($value, $this->envelope()->tags));
1133
    }
1134
 
1135
    /**
1136
     * Add a metadata header to the message when supported by the underlying transport.
1137
     *
1138
     * @param  string  $key
1139
     * @param  string  $value
1140
     * @return $this
1141
     */
1142
    public function metadata($key, $value)
1143
    {
1144
        $this->metadata[$key] = $value;
1145
 
1146
        return $this;
1147
    }
1148
 
1149
    /**
1150
     * Determine if the mailable has the given metadata.
1151
     *
1152
     * @param  string  $key
1153
     * @param  string  $value
1154
     * @return bool
1155
     */
1156
    public function hasMetadata($key, $value)
1157
    {
1158
        return (isset($this->metadata[$key]) && $this->metadata[$key] === $value) ||
1159
               (method_exists($this, 'envelope') && $this->envelope()->hasMetadata($key, $value));
1160
    }
1161
 
1162
    /**
1163
     * Assert that the mailable is from the given address.
1164
     *
1165
     * @param  object|array|string  $address
1166
     * @param  string|null  $name
1167
     * @return $this
1168
     */
1169
    public function assertFrom($address, $name = null)
1170
    {
1171
        $recipient = $this->formatAssertionRecipient($address, $name);
1172
 
1173
        PHPUnit::assertTrue(
1174
            $this->hasFrom($address, $name),
1175
            "Email was not from expected address [{$recipient}]."
1176
        );
1177
 
1178
        return $this;
1179
    }
1180
 
1181
    /**
1182
     * Assert that the mailable has the given recipient.
1183
     *
1184
     * @param  object|array|string  $address
1185
     * @param  string|null  $name
1186
     * @return $this
1187
     */
1188
    public function assertTo($address, $name = null)
1189
    {
1190
        $recipient = $this->formatAssertionRecipient($address, $name);
1191
 
1192
        PHPUnit::assertTrue(
1193
            $this->hasTo($address, $name),
1194
            "Did not see expected recipient [{$recipient}] in email recipients."
1195
        );
1196
 
1197
        return $this;
1198
    }
1199
 
1200
    /**
1201
     * Assert that the mailable has the given recipient.
1202
     *
1203
     * @param  object|array|string  $address
1204
     * @param  string|null  $name
1205
     * @return $this
1206
     */
1207
    public function assertHasTo($address, $name = null)
1208
    {
1209
        return $this->assertTo($address, $name);
1210
    }
1211
 
1212
    /**
1213
     * Assert that the mailable has the given recipient.
1214
     *
1215
     * @param  object|array|string  $address
1216
     * @param  string|null  $name
1217
     * @return $this
1218
     */
1219
    public function assertHasCc($address, $name = null)
1220
    {
1221
        $recipient = $this->formatAssertionRecipient($address, $name);
1222
 
1223
        PHPUnit::assertTrue(
1224
            $this->hasCc($address, $name),
1225
            "Did not see expected recipient [{$recipient}] in email recipients."
1226
        );
1227
 
1228
        return $this;
1229
    }
1230
 
1231
    /**
1232
     * Assert that the mailable has the given recipient.
1233
     *
1234
     * @param  object|array|string  $address
1235
     * @param  string|null  $name
1236
     * @return $this
1237
     */
1238
    public function assertHasBcc($address, $name = null)
1239
    {
1240
        $recipient = $this->formatAssertionRecipient($address, $name);
1241
 
1242
        PHPUnit::assertTrue(
1243
            $this->hasBcc($address, $name),
1244
            "Did not see expected recipient [{$recipient}] in email recipients."
1245
        );
1246
 
1247
        return $this;
1248
    }
1249
 
1250
    /**
1251
     * Assert that the mailable has the given "reply to" address.
1252
     *
1253
     * @param  object|array|string  $address
1254
     * @param  string|null  $name
1255
     * @return $this
1256
     */
1257
    public function assertHasReplyTo($address, $name = null)
1258
    {
1259
        $replyTo = $this->formatAssertionRecipient($address, $name);
1260
 
1261
        PHPUnit::assertTrue(
1262
            $this->hasReplyTo($address, $name),
1263
            "Did not see expected address [{$replyTo}] as email 'reply to' recipient."
1264
        );
1265
 
1266
        return $this;
1267
    }
1268
 
1269
    /**
399 lars 1270
     * Format the mailable recipient for display in an assertion message.
148 lars 1271
     *
1272
     * @param  object|array|string  $address
1273
     * @param  string|null  $name
1274
     * @return string
1275
     */
1276
    private function formatAssertionRecipient($address, $name = null)
1277
    {
1278
        if (! is_string($address)) {
1279
            $address = json_encode($address);
1280
        }
1281
 
1282
        if (filled($name)) {
1283
            $address .= ' ('.$name.')';
1284
        }
1285
 
1286
        return $address;
1287
    }
1288
 
1289
    /**
1290
     * Assert that the mailable has the given subject.
1291
     *
1292
     * @param  string  $subject
1293
     * @return $this
1294
     */
1295
    public function assertHasSubject($subject)
1296
    {
1297
        PHPUnit::assertTrue(
1298
            $this->hasSubject($subject),
1299
            "Did not see expected text [{$subject}] in email subject."
1300
        );
1301
 
1302
        return $this;
1303
    }
1304
 
1305
    /**
1306
     * Assert that the given text is present in the HTML email body.
1307
     *
1308
     * @param  string  $string
1309
     * @return $this
1310
     */
1311
    public function assertSeeInHtml($string)
1312
    {
1313
        [$html, $text] = $this->renderForAssertions();
1314
 
1315
        PHPUnit::assertStringContainsString(
1316
            $string,
1317
            $html,
1318
            "Did not see expected text [{$string}] within email body."
1319
        );
1320
 
1321
        return $this;
1322
    }
1323
 
1324
    /**
1325
     * Assert that the given text is not present in the HTML email body.
1326
     *
1327
     * @param  string  $string
1328
     * @return $this
1329
     */
1330
    public function assertDontSeeInHtml($string)
1331
    {
1332
        [$html, $text] = $this->renderForAssertions();
1333
 
1334
        PHPUnit::assertStringNotContainsString(
1335
            $string,
1336
            $html,
1337
            "Saw unexpected text [{$string}] within email body."
1338
        );
1339
 
1340
        return $this;
1341
    }
1342
 
1343
    /**
1344
     * Assert that the given text strings are present in order in the HTML email body.
1345
     *
1346
     * @param  array  $strings
1347
     * @return $this
1348
     */
1349
    public function assertSeeInOrderInHtml($strings)
1350
    {
1351
        [$html, $text] = $this->renderForAssertions();
1352
 
1353
        PHPUnit::assertThat($strings, new SeeInOrder($html));
1354
 
1355
        return $this;
1356
    }
1357
 
1358
    /**
1359
     * Assert that the given text is present in the plain-text email body.
1360
     *
1361
     * @param  string  $string
1362
     * @return $this
1363
     */
1364
    public function assertSeeInText($string)
1365
    {
1366
        [$html, $text] = $this->renderForAssertions();
1367
 
1368
        PHPUnit::assertStringContainsString(
1369
            $string,
1370
            $text,
1371
            "Did not see expected text [{$string}] within text email body."
1372
        );
1373
 
1374
        return $this;
1375
    }
1376
 
1377
    /**
1378
     * Assert that the given text is not present in the plain-text email body.
1379
     *
1380
     * @param  string  $string
1381
     * @return $this
1382
     */
1383
    public function assertDontSeeInText($string)
1384
    {
1385
        [$html, $text] = $this->renderForAssertions();
1386
 
1387
        PHPUnit::assertStringNotContainsString(
1388
            $string,
1389
            $text,
1390
            "Saw unexpected text [{$string}] within text email body."
1391
        );
1392
 
1393
        return $this;
1394
    }
1395
 
1396
    /**
1397
     * Assert that the given text strings are present in order in the plain-text email body.
1398
     *
1399
     * @param  array  $strings
1400
     * @return $this
1401
     */
1402
    public function assertSeeInOrderInText($strings)
1403
    {
1404
        [$html, $text] = $this->renderForAssertions();
1405
 
1406
        PHPUnit::assertThat($strings, new SeeInOrder($text));
1407
 
1408
        return $this;
1409
    }
1410
 
1411
    /**
1412
     * Assert the mailable has the given attachment.
1413
     *
1414
     * @param  string|\Illuminate\Contracts\Mail\Attachable|\Illuminate\Mail\Attachment  $file
1415
     * @param  array  $options
1416
     * @return $this
1417
     */
1418
    public function assertHasAttachment($file, array $options = [])
1419
    {
1420
        $this->renderForAssertions();
1421
 
1422
        PHPUnit::assertTrue(
1423
            $this->hasAttachment($file, $options),
1424
            'Did not find the expected attachment.'
1425
        );
1426
 
1427
        return $this;
1428
    }
1429
 
1430
    /**
1431
     * Assert the mailable has the given data as an attachment.
1432
     *
1433
     * @param  string  $data
1434
     * @param  string  $name
1435
     * @param  array  $options
1436
     * @return $this
1437
     */
1438
    public function assertHasAttachedData($data, $name, array $options = [])
1439
    {
1440
        $this->renderForAssertions();
1441
 
1442
        PHPUnit::assertTrue(
1443
            $this->hasAttachedData($data, $name, $options),
1444
            'Did not find the expected attachment.'
1445
        );
1446
 
1447
        return $this;
1448
    }
1449
 
1450
    /**
1451
     * Assert the mailable has the given attachment from storage.
1452
     *
1453
     * @param  string  $path
1454
     * @param  string|null  $name
1455
     * @param  array  $options
1456
     * @return $this
1457
     */
1458
    public function assertHasAttachmentFromStorage($path, $name = null, array $options = [])
1459
    {
1460
        $this->renderForAssertions();
1461
 
1462
        PHPUnit::assertTrue(
1463
            $this->hasAttachmentFromStorage($path, $name, $options),
1464
            'Did not find the expected attachment.'
1465
        );
1466
 
1467
        return $this;
1468
    }
1469
 
1470
    /**
1471
     * Assert the mailable has the given attachment from a specific storage disk.
1472
     *
1473
     * @param  string  $disk
1474
     * @param  string  $path
1475
     * @param  string|null  $name
1476
     * @param  array  $options
1477
     * @return $this
1478
     */
1479
    public function assertHasAttachmentFromStorageDisk($disk, $path, $name = null, array $options = [])
1480
    {
1481
        $this->renderForAssertions();
1482
 
1483
        PHPUnit::assertTrue(
1484
            $this->hasAttachmentFromStorageDisk($disk, $path, $name, $options),
1485
            'Did not find the expected attachment.'
1486
        );
1487
 
1488
        return $this;
1489
    }
1490
 
1491
    /**
1492
     * Assert that the mailable has the given tag.
1493
     *
1494
     * @param  string  $tag
1495
     * @return $this
1496
     */
1497
    public function assertHasTag($tag)
1498
    {
1499
        PHPUnit::assertTrue(
1500
            $this->hasTag($tag),
1501
            "Did not see expected tag [{$tag}] in email tags."
1502
        );
1503
 
1504
        return $this;
1505
    }
1506
 
1507
    /**
1508
     * Assert that the mailable has the given metadata.
1509
     *
1510
     * @param  string  $key
1511
     * @param  string  $value
1512
     * @return $this
1513
     */
1514
    public function assertHasMetadata($key, $value)
1515
    {
1516
        PHPUnit::assertTrue(
1517
            $this->hasMetadata($key, $value),
1518
            "Did not see expected key [{$key}] and value [{$value}] in email metadata."
1519
        );
1520
 
1521
        return $this;
1522
    }
1523
 
1524
    /**
1525
     * Render the HTML and plain-text version of the mailable into views for assertions.
1526
     *
1527
     * @return array
1528
     *
1529
     * @throws \ReflectionException
1530
     */
1531
    protected function renderForAssertions()
1532
    {
1533
        if ($this->assertionableRenderStrings) {
1534
            return $this->assertionableRenderStrings;
1535
        }
1536
 
1537
        return $this->assertionableRenderStrings = $this->withLocale($this->locale, function () {
1538
            $this->prepareMailableForDelivery();
1539
 
1540
            $html = Container::getInstance()->make('mailer')->render(
1541
                $view = $this->buildView(), $this->buildViewData()
1542
            );
1543
 
1544
            if (is_array($view) && isset($view[1])) {
1545
                $text = $view[1];
1546
            }
1547
 
1548
            $text ??= $view['text'] ?? '';
1549
 
1550
            if (! empty($text) && ! $text instanceof Htmlable) {
1551
                $text = Container::getInstance()->make('mailer')->render(
1552
                    $text, $this->buildViewData()
1553
                );
1554
            }
1555
 
1556
            return [(string) $html, (string) $text];
1557
        });
1558
    }
1559
 
1560
    /**
1561
     * Prepare the mailable instance for delivery.
1562
     *
1563
     * @return void
1564
     */
1565
    private function prepareMailableForDelivery()
1566
    {
1567
        if (method_exists($this, 'build')) {
1568
            Container::getInstance()->call([$this, 'build']);
1569
        }
1570
 
1571
        $this->ensureHeadersAreHydrated();
1572
        $this->ensureEnvelopeIsHydrated();
1573
        $this->ensureContentIsHydrated();
1574
        $this->ensureAttachmentsAreHydrated();
1575
    }
1576
 
1577
    /**
1578
     * Ensure the mailable's headers are hydrated from the "headers" method.
1579
     *
1580
     * @return void
1581
     */
1582
    private function ensureHeadersAreHydrated()
1583
    {
1584
        if (! method_exists($this, 'headers')) {
1585
            return;
1586
        }
1587
 
1588
        $headers = $this->headers();
1589
 
1590
        $this->withSymfonyMessage(function ($message) use ($headers) {
1591
            if ($headers->messageId) {
1592
                $message->getHeaders()->addIdHeader('Message-Id', $headers->messageId);
1593
            }
1594
 
1595
            if (count($headers->references) > 0) {
1596
                $message->getHeaders()->addTextHeader('References', $headers->referencesString());
1597
            }
1598
 
1599
            foreach ($headers->text as $key => $value) {
1600
                $message->getHeaders()->addTextHeader($key, $value);
1601
            }
1602
        });
1603
    }
1604
 
1605
    /**
1606
     * Ensure the mailable's "envelope" data is hydrated from the "envelope" method.
1607
     *
1608
     * @return void
1609
     */
1610
    private function ensureEnvelopeIsHydrated()
1611
    {
1612
        if (! method_exists($this, 'envelope')) {
1613
            return;
1614
        }
1615
 
1616
        $envelope = $this->envelope();
1617
 
1618
        if (isset($envelope->from)) {
1619
            $this->from($envelope->from->address, $envelope->from->name);
1620
        }
1621
 
1622
        foreach (['to', 'cc', 'bcc', 'replyTo'] as $type) {
1623
            foreach ($envelope->{$type} as $address) {
1624
                $this->{$type}($address->address, $address->name);
1625
            }
1626
        }
1627
 
1628
        if ($envelope->subject) {
1629
            $this->subject($envelope->subject);
1630
        }
1631
 
1632
        foreach ($envelope->tags as $tag) {
1633
            $this->tag($tag);
1634
        }
1635
 
1636
        foreach ($envelope->metadata as $key => $value) {
1637
            $this->metadata($key, $value);
1638
        }
1639
 
1640
        foreach ($envelope->using as $callback) {
1641
            $this->withSymfonyMessage($callback);
1642
        }
1643
    }
1644
 
1645
    /**
1646
     * Ensure the mailable's content is hydrated from the "content" method.
1647
     *
1648
     * @return void
1649
     */
1650
    private function ensureContentIsHydrated()
1651
    {
1652
        if (! method_exists($this, 'content')) {
1653
            return;
1654
        }
1655
 
1656
        $content = $this->content();
1657
 
1658
        if ($content->view) {
1659
            $this->view($content->view);
1660
        }
1661
 
1662
        if ($content->html) {
1663
            $this->view($content->html);
1664
        }
1665
 
1666
        if ($content->text) {
1667
            $this->text($content->text);
1668
        }
1669
 
1670
        if ($content->markdown) {
1671
            $this->markdown($content->markdown);
1672
        }
1673
 
1674
        if ($content->htmlString) {
1675
            $this->html($content->htmlString);
1676
        }
1677
 
1678
        foreach ($content->with as $key => $value) {
1679
            $this->with($key, $value);
1680
        }
1681
    }
1682
 
1683
    /**
1684
     * Ensure the mailable's attachments are hydrated from the "attachments" method.
1685
     *
1686
     * @return void
1687
     */
1688
    private function ensureAttachmentsAreHydrated()
1689
    {
1690
        if (! method_exists($this, 'attachments')) {
1691
            return;
1692
        }
1693
 
1694
        $attachments = $this->attachments();
1695
 
1696
        Collection::make(is_object($attachments) ? [$attachments] : $attachments)
1697
            ->each(function ($attachment) {
1698
                $this->attach($attachment);
1699
            });
1700
    }
1701
 
1702
    /**
1703
     * Set the name of the mailer that should send the message.
1704
     *
1705
     * @param  string  $mailer
1706
     * @return $this
1707
     */
1708
    public function mailer($mailer)
1709
    {
1710
        $this->mailer = $mailer;
1711
 
1712
        return $this;
1713
    }
1714
 
1715
    /**
1716
     * Register a callback to be called with the Symfony message instance.
1717
     *
1718
     * @param  callable  $callback
1719
     * @return $this
1720
     */
1721
    public function withSymfonyMessage($callback)
1722
    {
1723
        $this->callbacks[] = $callback;
1724
 
1725
        return $this;
1726
    }
1727
 
1728
    /**
1729
     * Register a callback to be called while building the view data.
1730
     *
1731
     * @param  callable  $callback
1732
     * @return void
1733
     */
1734
    public static function buildViewDataUsing(callable $callback)
1735
    {
1736
        static::$viewDataCallback = $callback;
1737
    }
1738
 
1739
    /**
1740
     * Dynamically bind parameters to the message.
1741
     *
1742
     * @param  string  $method
1743
     * @param  array  $parameters
1744
     * @return $this
1745
     *
1746
     * @throws \BadMethodCallException
1747
     */
1748
    public function __call($method, $parameters)
1749
    {
1750
        if (static::hasMacro($method)) {
1751
            return $this->macroCall($method, $parameters);
1752
        }
1753
 
1754
        if (str_starts_with($method, 'with')) {
1755
            return $this->with(Str::camel(substr($method, 4)), $parameters[0]);
1756
        }
1757
 
1758
        static::throwBadMethodCallException($method);
1759
    }
1760
}