From 6f54cf428e494cbe5d45d1b2340d49bfe2e664b6 Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Wed, 22 Jun 2022 10:53:56 +0200 Subject: [PATCH] Add support for Laravel's `Attachable` objects (#2963) * add attachable support * Fix styling * fix phpstan errors * remove static analysis as it keeps failing only on github Co-authored-by: freekmurze --- .github/workflows/phpstan.yml | 31 -------- composer.json | 14 ++-- .../attaching-media-in-mails.md | 72 +++++++++++++++++++ phpstan-baseline.neon | 21 +++++- phpunit.xml.dist | 3 + src/MediaCollections/Models/Media.php | 23 +++++- tests/Feature/Media/ToMailAttachmentTest.php | 51 +++++++++++++ tests/TestSupport/Mail/AttachmentMail.php | 21 ++++++ .../InvalidMediaConversionAttachmentMail.php | 23 ++++++ .../Mail/MediaConversionAttachmentMail.php | 23 ++++++ .../resources/views/mailable.blade.php | 1 + 11 files changed, 242 insertions(+), 41 deletions(-) delete mode 100644 .github/workflows/phpstan.yml create mode 100644 docs/advanced-usage/attaching-media-in-mails.md create mode 100644 tests/Feature/Media/ToMailAttachmentTest.php create mode 100644 tests/TestSupport/Mail/AttachmentMail.php create mode 100644 tests/TestSupport/Mail/InvalidMediaConversionAttachmentMail.php create mode 100644 tests/TestSupport/Mail/MediaConversionAttachmentMail.php create mode 100644 tests/TestSupport/resources/views/mailable.blade.php diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml deleted file mode 100644 index 5e06f4a4f..000000000 --- a/.github/workflows/phpstan.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: PHPStan - -on: - push: - paths: - - '**.php' - - 'phpstan.neon.dist' - pull_request: - paths: - - '**.php' - - 'phpstan.neon.dist' - - -jobs: - phpstan: - name: phpstan - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - coverage: none - - - name: Install composer dependencies - uses: ramsey/composer-install@v1 - - - name: Run PHPStan - run: ./vendor/bin/phpstan --error-format=github diff --git a/composer.json b/composer.json index c745a2d88..f23ee5d28 100644 --- a/composer.json +++ b/composer.json @@ -26,11 +26,11 @@ "ext-exif": "*", "ext-fileinfo": "*", "ext-json": "*", - "illuminate/bus": "^9.0", - "illuminate/console": "^9.0", - "illuminate/database": "^9.0", - "illuminate/pipeline": "^9.0", - "illuminate/support": "^9.0", + "illuminate/bus": "^9.18", + "illuminate/console": "^9.18", + "illuminate/database": "^9.18", + "illuminate/pipeline": "^9.18", + "illuminate/support": "^9.18", "intervention/image": "^2.7", "maennchen/zipstream-php": "^2.0", "spatie/image": "^2.2.2", @@ -52,7 +52,7 @@ "spatie/laravel-ray": "^1.28", "spatie/pdf-to-image": "^2.1", "spatie/phpunit-snapshot-assertions": "^4.2", - "pestphp/pest": "^1.20" + "pestphp/pest": "^1.21" }, "conflict": { "php-ffmpeg/php-ffmpeg": "<0.6.1" @@ -69,6 +69,8 @@ }, "scripts": { "analyse": "vendor/bin/phpstan analyse", + "baseline": "vendor/bin/phpstan analyse --generate-baseline", + "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes", "test": "vendor/bin/pest" }, diff --git a/docs/advanced-usage/attaching-media-in-mails.md b/docs/advanced-usage/attaching-media-in-mails.md new file mode 100644 index 000000000..ffa225fe2 --- /dev/null +++ b/docs/advanced-usage/attaching-media-in-mails.md @@ -0,0 +1,72 @@ +--- +title: Attaching media in mails +weight: 9 +--- + +Laravel allows [to attach all sorts of classes](https://laravel.com/docs/9.x/mail#attachable-objects) in mails. The `Media` model implements Laravel's `Attachable` interface, so you can attach `Media` models directly in mails. + + +```php +namespace App\Mails; + +use Illuminate\Mail\Mailable; +use Spatie\MediaLibrary\MediaCollections\Models\Media; +use App\Models\Order; + +class OrderConfirmationMail extends Mailable +{ + public function __construct(public Order $order) + { + + } + + public function build() + { + /** @var \Spatie\MediaLibrary\MediaCollections\Models\Media $invoice */ + $invoice = $this->order->getFirstMedia('invoice') + + return $this + ->view('invoice') + ->attach($invoice); + } +} +``` + +## Using conversions as attachments + +You can call `mailAttachment()` on a `Media` model to get back an `Attachment` that you can use in a Mailable. You can pass the name of a conversion to `mailAttachment()` to get an attachable conversion back. + +```php +namespace App\Mails; + +use Illuminate\Mail\Mailable; +use Spatie\MediaLibrary\MediaCollections\Models\Media; +use App\Models\BlogPost; + +class BlogPostThumbnailMail extends Mailable +{ + public function __construct(public BlogPost $blogPost) + { + + } + + public function build() + { + /** @var \Spatie\MediaLibrary\MediaCollections\Models\Media $mediaItem */ + $mediaItem = $this->blogPost->getFirstMedia(); + + // pass the conversion name + $thumbnailAttachment = $mediaItem->mailAttachment('thumbnail'); + + return $this + ->view('mails/blogpostThumbnail') + ->attach($thumbnailAttachment); + } +} +``` + +## Customizing the attachment + +By default, the attachment will use the `file_name` and `mime_type` properties to configure Laravel's `Attachment` class. To override how `Attachments` are made, [use a custom media model](https://spatie.be/docs/laravel-medialibrary/v10/advanced-usage/using-your-own-model), and override the `toMailAttachment` method. + + diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 12b3fdb7c..7b65a3937 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -725,6 +725,11 @@ parameters: count: 1 path: src/MediaCollections/FileAdder.php + - + message: "#^Cannot call static method created\\(\\) on class\\-string\\\\|null\\.$#" + count: 1 + path: src/MediaCollections/FileAdder.php + - message: "#^Class Spatie\\\\MediaLibraryPro\\\\Models\\\\TemporaryUpload not found\\.$#" count: 2 @@ -1172,12 +1177,12 @@ parameters: - message: "#^Access to an undefined property Spatie\\\\MediaLibrary\\\\MediaCollections\\\\Models\\\\Media\\:\\:\\$disk\\.$#" - count: 2 + count: 3 path: src/MediaCollections/Models/Media.php - message: "#^Access to an undefined property Spatie\\\\MediaLibrary\\\\MediaCollections\\\\Models\\\\Media\\:\\:\\$file_name\\.$#" - count: 3 + count: 4 path: src/MediaCollections/Models/Media.php - @@ -1185,9 +1190,14 @@ parameters: count: 4 path: src/MediaCollections/Models/Media.php + - + message: "#^Access to an undefined property Spatie\\\\MediaLibrary\\\\MediaCollections\\\\Models\\\\Media\\:\\:\\$mime\\.$#" + count: 1 + path: src/MediaCollections/Models/Media.php + - message: "#^Access to an undefined property Spatie\\\\MediaLibrary\\\\MediaCollections\\\\Models\\\\Media\\:\\:\\$mime_type\\.$#" - count: 2 + count: 3 path: src/MediaCollections/Models/Media.php - @@ -1215,6 +1225,11 @@ parameters: count: 2 path: src/MediaCollections/Models/Media.php + - + message: "#^Call to an undefined method Spatie\\\\MediaLibrary\\\\Support\\\\UrlGenerator\\\\UrlGenerator\\:\\:getPathRelativeToRoot\\(\\)\\.$#" + count: 1 + path: src/MediaCollections/Models/Media.php + - message: "#^Cannot call method determineOrderColumnName\\(\\) on Illuminate\\\\Database\\\\Eloquent\\\\Collection\\\\|Spatie\\\\MediaLibrary\\\\MediaCollections\\\\Models\\\\Media\\|null\\.$#" count: 1 diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 22da30cc8..3663181f9 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -10,4 +10,7 @@ tests + + + diff --git a/src/MediaCollections/Models/Media.php b/src/MediaCollections/Models/Media.php index 2bde7e1c2..ac354e6e7 100644 --- a/src/MediaCollections/Models/Media.php +++ b/src/MediaCollections/Models/Media.php @@ -3,6 +3,7 @@ namespace Spatie\MediaLibrary\MediaCollections\Models; use DateTimeInterface; +use Illuminate\Contracts\Mail\Attachable; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Contracts\Support\Responsable; use Illuminate\Database\Eloquent\Builder; @@ -10,6 +11,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; +use Illuminate\Mail\Attachment; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Spatie\MediaLibrary\Conversions\Conversion; @@ -37,7 +39,7 @@ * @property-read string $previewUrl * @property-read string $originalUrl */ -class Media extends Model implements Responsable, Htmlable +class Media extends Model implements Responsable, Htmlable, Attachable { use IsSorted; use CustomMediaProperties; @@ -214,6 +216,7 @@ public function getCustomProperty(string $propertyName, $default = null): mixed /** * @param mixed $value + * * @return $this */ public function setCustomProperty(string $name, $value): self @@ -428,4 +431,22 @@ public static function findWithTemporaryUploadInCurrentSession(array $uuids) ) ->get(); } + + public function mailAttachment(string $conversion = ''): Attachment + { + $path = $this->getUrlGenerator($conversion)->getPathRelativeToRoot(); + + $attachment = Attachment::fromStorageDisk($this->disk, $path)->as($this->file_name); + + if ($this->mime_type) { + $attachment->withMime($this->mime); + } + + return $attachment; + } + + public function toMailAttachment(): Attachment + { + return $this->mailAttachment(); + } } diff --git a/tests/Feature/Media/ToMailAttachmentTest.php b/tests/Feature/Media/ToMailAttachmentTest.php new file mode 100644 index 000000000..97eaa0a1a --- /dev/null +++ b/tests/Feature/Media/ToMailAttachmentTest.php @@ -0,0 +1,51 @@ +testModelWithConversion + ->addMedia($this->getTestJpg()) + ->toMediaCollection(); +}); + +it('can create a mail attachment from a media', function () { + $mailAttachment = Media::first()->toMailAttachment(); + + expect($mailAttachment)->toBeInstanceOf(Attachment::class); +}); + +it('can send a mail with a media attached', function () { + $mailable = new AttachmentMail(Media::first()); + + Mail::send($mailable); + + // assert no exceptions thrown + expect(true)->toBeTrue(); +}); + +it('can create an attachment to a conversion', function () { + $mailAttachment = $this->testModelWithConversion->getFirstMedia()->mailAttachment('thumb'); + + expect($mailAttachment)->toBeInstanceOf(Attachment::class); +}); + +it('can send a mail with conversion attached', function () { + $mailable = new MediaConversionAttachmentMail(Media::first()); + + Mail::send($mailable); + + // assert no exceptions thrown + expect(true)->toBeTrue(); +}); + +it('will throw an exception when attaching a media specifying a non-existing conversion', function () { + $mailable = new InvalidMediaConversionAttachmentMail(Media::first()); + + Mail::send($mailable); +})->throws(InvalidConversion::class); diff --git a/tests/TestSupport/Mail/AttachmentMail.php b/tests/TestSupport/Mail/AttachmentMail.php new file mode 100644 index 000000000..471305720 --- /dev/null +++ b/tests/TestSupport/Mail/AttachmentMail.php @@ -0,0 +1,21 @@ +to('johndoe@example.com') + ->view('mailable') + ->attach($this->media); + } +} diff --git a/tests/TestSupport/Mail/InvalidMediaConversionAttachmentMail.php b/tests/TestSupport/Mail/InvalidMediaConversionAttachmentMail.php new file mode 100644 index 000000000..41ae3cf73 --- /dev/null +++ b/tests/TestSupport/Mail/InvalidMediaConversionAttachmentMail.php @@ -0,0 +1,23 @@ +media->mailAttachment('non-existing-conversion'); + + return $this + ->to('johndoe@example.com') + ->view('mailable') + ->attach($thumbnailAttachment); + } +} diff --git a/tests/TestSupport/Mail/MediaConversionAttachmentMail.php b/tests/TestSupport/Mail/MediaConversionAttachmentMail.php new file mode 100644 index 000000000..df240aaa5 --- /dev/null +++ b/tests/TestSupport/Mail/MediaConversionAttachmentMail.php @@ -0,0 +1,23 @@ +media->mailAttachment('thumb'); + + return $this + ->to('johndoe@example.com') + ->view('mailable') + ->attach($thumbnailAttachment); + } +} diff --git a/tests/TestSupport/resources/views/mailable.blade.php b/tests/TestSupport/resources/views/mailable.blade.php new file mode 100644 index 000000000..c631c5967 --- /dev/null +++ b/tests/TestSupport/resources/views/mailable.blade.php @@ -0,0 +1 @@ +Test mailable \ No newline at end of file