diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eae72b..1fed62a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## 6.1.0 + +* Attempt to automatically select an `intervention/image` driver based on the available extensions. +* Fix an error in package discovery if attempting to install both `plank/laravel-mediable` and `intervention/image-laravel` at the same time + +## 6.0.5 + +* move ImageManipulator singleton to lazy instantiation + +## 6.0.4 + +* Fix alt migration default value for the mysql dialect. Default value assigned from the Media model + +## 6.0.3 + +* fix service provider database migration bindings by @frasmage in #349 + ## 6.0.2 - Added `intervention/image-laravel` package to the composer suggests list - Updated documentation with configuration instructions for intervention/image diff --git a/docs/source/variants.rst b/docs/source/variants.rst index 25c8edb..6252232 100644 --- a/docs/source/variants.rst +++ b/docs/source/variants.rst @@ -10,30 +10,44 @@ Laravel-Mediable integrates the `intervention/image `_ or `ImageMagick `_ libraries as the underlying driver. +Before you can use the ImageManipulation features of this package, you will need to make sure that the intervention/image package is properly configured. Intervention/image is capable of using either the `GD `_ or `ImageMagick `_ PHP extensions as the underlying driver. + +If intervention/image is not configured in the Laravel service container, this package will attempt to automatically select an intervention/image driver based on the extensions available, preferring `imagick` if available, and falling back to `gd`. If neither are available, attempting to use the ImageManipulator will result in an exception. + +To configure Intervention/image yourself, refer to the following guides based on your version of the package. Intervention/image >=3.0 ^^^^^^^^^^^^^^^^^^^^^^^ RECOMMENDED: install the `intervention/image-laravel `_ package to configure the container bindings automatically. -Alternatively, you can add the necessary bindings to the service container by adding the following to one of the service providers of your application. +Alternatively, you can add the necessary bindings to the service container manually by adding the following bindings to one of the service providers of your application. :: bind(Intervention\Image\Interfaces\DriverInterface::class, \Intervention\Image\Drivers\Gd\Driver::class); + class AppServiceProvider extends ServiceProvider + { + public function register() + { + // if using GD + $app->bind(Intervention\Image\Interfaces\DriverInterface::class, + \Intervention\Image\Drivers\Gd\Driver::class + ); - // if using Imagick - $app->bind(Intervention\Image\Interfaces\DriverInterface::class, \Intervention\Image\Drivers\Imagick\Driver::class); + // if using Imagick + $app->bind(Intervention\Image\Interfaces\DriverInterface::class, + \Intervention\Image\Drivers\Imagick\Driver::class + ); + } + } Intervention/image <3.0 ^^^^^^^^^^^^^^^^^^^^^^^ RECOMMENDED: `follow the steps `_ to enable the intervention/image Laravel service provider. -Otherwise, by default, intervention/image will use the `GD `_ library driver. If you intend to use the additional features of the `ImageMagick `_ driver, you should make sure that the PHP extension is installed and the correct configuration is bound to the Laravel service container. +Alternatively, you can add the necessary bindings to the service container manually by adding the following bindings to one of the service providers of your application. :: @@ -49,6 +63,7 @@ Otherwise, by default, intervention/image will use the `GD 'imagick']); + // return new ImageManager(['driver' => 'gd']); } ); } diff --git a/src/Exceptions/MediaUpload/ConfigurationException.php b/src/Exceptions/MediaUpload/ConfigurationException.php index 7ffa949..c67a5f7 100644 --- a/src/Exceptions/MediaUpload/ConfigurationException.php +++ b/src/Exceptions/MediaUpload/ConfigurationException.php @@ -52,4 +52,9 @@ public static function invalidOptimizer(string $optimizerClass): self { return new self("Invalid optimizer class `{$optimizerClass}`. Must implement `\Spatie\ImageOptimizer\Optimizer`."); } + + public static function interventionImageNotConfigured(): self + { + return new self("Before variants can be created, the intervention/image package must be configured in the Laravel container."); + } } diff --git a/src/ImageManipulator.php b/src/ImageManipulator.php index e2bf080..bf07e19 100644 --- a/src/ImageManipulator.php +++ b/src/ImageManipulator.php @@ -9,6 +9,7 @@ use Intervention\Image\Image; use Intervention\Image\ImageManager; use Plank\Mediable\Exceptions\ImageManipulationException; +use Plank\Mediable\Exceptions\MediaUpload\ConfigurationException; use Plank\Mediable\SourceAdapters\SourceAdapterInterface; use Plank\Mediable\SourceAdapters\StreamAdapter; use Psr\Http\Message\StreamInterface; @@ -16,7 +17,7 @@ class ImageManipulator { - private ImageManager $imageManager; + private ?ImageManager $imageManager; /** * @var ImageManipulation[] @@ -33,7 +34,7 @@ class ImageManipulator private ImageOptimizer $imageOptimizer; public function __construct( - ImageManager $imageManager, + ?ImageManager $imageManager, FilesystemManager $filesystem, ImageOptimizer $imageOptimizer ) { @@ -47,6 +48,9 @@ public function defineVariant( ImageManipulation $manipulation, ?array $tags = [] ) { + if (!$this->imageManager) { + throw ConfigurationException::interventionImageNotConfigured(); + } $this->variantDefinitions[$variantName] = $manipulation; foreach ($tags as $tag) { $this->variantDefinitionGroups[$tag][] = $variantName; @@ -105,6 +109,10 @@ public function createImageVariant( string $variantName, bool $forceRecreate = false ): Media { + if (!$this->imageManager) { + throw ConfigurationException::interventionImageNotConfigured(); + } + $this->validateMedia($media); $modelClass = config('mediable.model'); @@ -217,6 +225,10 @@ public function manipulateUpload( SourceAdapterInterface $source, ImageManipulation $manipulation ): StreamAdapter { + if (!$this->imageManager) { + throw ConfigurationException::interventionImageNotConfigured(); + } + $outputFormat = $this->determineOutputFormat($manipulation, $media); if (method_exists($this->imageManager, 'read')) { // Intervention Image >=3.0 diff --git a/src/MediableServiceProvider.php b/src/MediableServiceProvider.php index d637bf8..46d69e0 100644 --- a/src/MediableServiceProvider.php +++ b/src/MediableServiceProvider.php @@ -3,13 +3,11 @@ namespace Plank\Mediable; -use CreateMediableTables; use Illuminate\Contracts\Container\Container; -use Illuminate\Filesystem\Filesystem; use Illuminate\Filesystem\FilesystemManager; use Illuminate\Support\ServiceProvider; use Intervention\Image\ImageManager; -use Mimey\MimeTypes; +use Intervention\Image\Interfaces\DriverInterface; use Plank\Mediable\Commands\ImportMediaCommand; use Plank\Mediable\Commands\PruneMediaCommand; use Plank\Mediable\Commands\SyncMediaCommand; @@ -108,11 +106,11 @@ public function register(): void ); $this->registerSourceAdapterFactory(); + $this->registerImageManipulator(); $this->registerUploader(); $this->registerMover(); $this->registerUrlGeneratorFactory(); $this->registerConsoleCommands(); - $this->registerImageManipulator(); } /** @@ -193,7 +191,7 @@ public function registerImageManipulator(): void { $this->app->singleton(ImageManipulator::class, function (Container $app) { return new ImageManipulator( - $app->get(ImageManager::class), + $this->getInterventionImageManagerConfiguration($app), $app->get(FilesystemManager::class), $app->get(ImageOptimizer::class) ); @@ -212,4 +210,42 @@ public function registerConsoleCommands(): void SyncMediaCommand::class, ]); } + + private function getInterventionImageManagerConfiguration(Container $app): ?ImageManager + { + $imageManager = null; + if ($app->has(ImageManager::class) + || ( + class_exists(DriverInterface::class) // intervention >= 3.0 + && $app->has(DriverInterface::class) + ) + ) { + // use whatever the user has bound to the container if available + $imageManager = $app->get(ImageManager::class); + } elseif (extension_loaded('imagick')) { + // attempt to automatically configure for imagick + if (class_exists(\Intervention\Image\Drivers\Imagick\Driver::class)) { + // intervention/image >=3.0 + $imageManager = new ImageManager( + new \Intervention\Image\Drivers\Imagick\Driver() + ); + } else { + // intervention/image <3.0 + $imageManager = new ImageManager(['driver' => 'imagick']); + } + } elseif (extension_loaded('gd')) { + // attempt to automatically configure for gd + if (class_exists(\Intervention\Image\Drivers\GD\Driver::class)) { + // intervention/image >=3.0 + $imageManager = new ImageManager( + new \Intervention\Image\Drivers\GD\Driver() + ); + } else { + // intervention/image <3.0 + $imageManager = new ImageManager(['driver' => 'gd']); + } + } + + return $imageManager; + } } diff --git a/tests/Integration/ImageManipulatorTest.php b/tests/Integration/ImageManipulatorTest.php index 9b6aa24..3f60a92 100644 --- a/tests/Integration/ImageManipulatorTest.php +++ b/tests/Integration/ImageManipulatorTest.php @@ -7,10 +7,12 @@ use Intervention\Image\Image; use Intervention\Image\ImageManager; use Plank\Mediable\Exceptions\ImageManipulationException; +use Plank\Mediable\Exceptions\MediaUpload\ConfigurationException; use Plank\Mediable\ImageManipulation; use Plank\Mediable\ImageManipulator; use Plank\Mediable\ImageOptimizer; use Plank\Mediable\Media; +use Plank\Mediable\SourceAdapters\SourceAdapterInterface; use Plank\Mediable\Tests\TestCase; use Spatie\ImageOptimizer\Optimizers\Optipng; use Spatie\ImageOptimizer\Optimizers\Pngquant; @@ -765,4 +767,50 @@ public function getManipulator(): ImageManipulator ); } } + + public function test_it_throws_if_no_intervention_image_define_variant(): void + { + $this->expectException(ConfigurationException::class); + $manipulator = new ImageManipulator( + null, + app(FilesystemManager::class), + app(ImageOptimizer::class) + ); + + $manipulator->defineVariant( + 'foo', + new ImageManipulation($this->getMockCallable()) + ); + } + + public function test_it_throws_if_no_intervention_image_create_image_variant(): void + { + $this->expectException(ConfigurationException::class); + $manipulator = new ImageManipulator( + null, + app(FilesystemManager::class), + app(ImageOptimizer::class) + ); + + $manipulator->createImageVariant( + new Media, + 'foo' + ); + } + + public function test_it_throws_if_no_intervention_image_manipulate_upload(): void + { + $this->expectException(ConfigurationException::class); + $manipulator = new ImageManipulator( + null, + app(FilesystemManager::class), + app(ImageOptimizer::class) + ); + + $manipulator->manipulateUpload( + new Media, + $this->createMock(SourceAdapterInterface::class), + $this->createMock(ImageManipulation::class), + ); + } } diff --git a/tests/Integration/MediaUploaderTest.php b/tests/Integration/MediaUploaderTest.php index 7b5a398..3d5048f 100644 --- a/tests/Integration/MediaUploaderTest.php +++ b/tests/Integration/MediaUploaderTest.php @@ -823,7 +823,8 @@ public function test_it_manipulates_images(): void function (Image $image) { $image->resize(16, 16); } - )->outputJpegFormat(); + )->outputJpegFormat() + ->noOptimization(); app(ImageManipulator::class)->defineVariant( 'foo', @@ -843,8 +844,8 @@ function (Image $image) { $this->assertEquals('image/jpeg', $media->mime_type); $this->assertEquals('image', $media->aggregate_type); $this->assertTrue( - $media->size >= 933 // intervention/image <3.0 - && $media->size <= 951 // intervention/image >=3.0 + $media->size <= 951, // intervention/image >=3.0, + "got size {$media->size}" ); }