janwebdev/symfony-intervention-image-bundle
Symfony bundle that wires Intervention Image into your application, giving you a service-based way to resize, crop, and manipulate images through Symfony's container and configuration. A lightweight integration for projects that need image processing without manual setup.
Read this first: it is a Symfony bundle, not a Laravel package. The name is literal — it ships a Bundle subclass, a DI extension, and a config tree, and it exists to wire Intervention\Image\ImageManager into the Symfony container. Laravel's package discovery only looks for Illuminate\Support\ServiceProvider subclasses, so composer require janwebdev/symfony-intervention-image-bundle in a Laravel app installs the code and registers nothing. You also drag in symfony/framework-bundle, symfony/dependency-injection, and symfony/config for zero benefit. A new tag does not change that: v1.0.0 is still a bundle.
So the correct "getting started" is:
# Do NOT install the bundle in Laravel. Install the library.
composer require intervention/image
# Or, for a Laravel-native binding + facade:
composer require intervention/image-laravel
What the v1.0.0 tag changes (and what it doesn't). The release notes are a single line — "initial release" — and no changelog, diff summary, or dependency metadata accompanied it. That is a version reset on a package whose previous line dated to mid-2022, not a documented feature release. Concretely, it means one prior claim in this assessment is now stale: you can no longer say "the last release was 2022, so it targets Intervention 2.x" without checking. Before you reason about it at all, run:
composer show janwebdev/symfony-intervention-image-bundle --all
# then read the constraints directly
composer show -a janwebdev/symfony-intervention-image-bundle | grep -E 'intervention|symfony|php'
Two possible worlds, and the release note does not tell you which:
intervention/image:^3, PHP 8.1+, Symfony 6/7) → the bundle is a credible wiring layer for a Symfony app again, but it is still irrelevant to Laravel.^2.7 still hard-blocks Intervention 3.Where to look first, depending on why you're here:
intervention/image docs, section "Installation" and "Reading images". The bundle adds nothing you can't write in 15 lines, in either world.vendor/janwebdev/symfony-intervention-image-bundle/DependencyInjection/ for Configuration.php and *Extension.php to get the exact config keys (driver option, likely gd vs imagick) and the ImageManager service ID. Then confirm the installed Intervention major from composer show intervention/image, because the 2.x→3.x API break is what determines every line of code you write.Minimal Laravel equivalent of what the bundle does:
// app/Providers/AppServiceProvider.php
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Gd\Driver;
public function register(): void
{
$this->app->singleton(ImageManager::class, fn () => new ImageManager(
new Driver(), // or new ImagickDriver()
));
}
First use case: thumbnail generation for user uploads. Upload the original untouched, dispatch a queued job that reads the file, produces 2–3 widths, and writes WebP variants. That is 90% of real-world usage and the only scenario worth wiring up on day one.
Inject the manager, never the static facade. In Intervention v3 the static Image::make() API from 2.x is gone; $manager->read() is the entry point and it is trivially testable. Injecting ImageManager into jobs and action classes keeps your controllers clean and lets you swap the driver in tests. If v1.0.0 turns out to be a v3-era bundle, this is the only integration style it can support anyway.
Queued variant generation (the pattern you'll actually reuse):
final class GenerateImageVariants implements ShouldQueue
{
public function __construct(
public string $disk,
public string $path,
public array $widths = [160, 480, 960],
) {}
public function handle(ImageManager $images): void
{
$storage = Storage::disk($this->disk);
$image = $images->read($storage->path($this->path))
->orientate(); // fix EXIF rotation once, up front
foreach ($this->widths as $width) {
$encoded = $image->scale(width: $width)->toWebp(80);
$storage->put(
"variants/{$width}/{$this->path}.webp",
(string) $encoded,
);
}
}
}
Dispatch it from an ImageUploaded event listener so uploads stay fast and the request never blocks on GD/Imagick.
On-the-fly resizing with an HTTP cache — for user-supplied avatars where pre-generating every size is wasteful:
Route::get('/media/{path}', function (string $path, ImageManager $images) {
$size = (int) request('w', 320);
abort_unless(in_array($size, [160, 320, 640], true), 404);
$cacheKey = "derived/{$size}/".md5($path);
if (! Storage::disk('public')->exists($cacheKey)) {
$encoded = $images->read(Storage::disk('public')->path($path))
->cover($size, $size)
->toWebp(75);
Storage::disk('public')->put($cacheKey, (string) $encoded);
}
return response()->file(Storage::disk('public')->path($cacheKey))
->header('Cache-Control', 'public, max-age=31536000, immutable');
})->where('path', '.*');
Whitelist the width parameter — never pass user input straight into scale().
Wrap it in your own service, not the framework's. The bundle's job is to hand you an ImageManager; your job is the domain logic. A thin ImageProcessor class that owns naming conventions, disk choices, and quality settings is the extension point that survives a driver swap or a move off Intervention entirely.
Driver selection per environment. GD is almost always present and fine for thumbnails; Imagick handles CMYK JPEGs, animated GIFs, and colour profiles better. Configure it once:
// config/services.php or a dedicated config/image.php
'driver' => env('IMAGE_DRIVER', 'gd'),
and resolve GdDriver / ImagickDriver in the singleton closure.
Store encoded output as a string, not a resource. (string) $encoded or ->save($absolutePath). Mixing Storage::put with an Intervention object and expecting implicit casting is a common source of zero-byte files.
If you are on the Symfony side and upgrading to v1.0.0: treat it as a fresh integration, not a patch. Read DependencyInjection/Configuration.php against your existing config/packages/*.yaml, diff the service IDs, and run composer update janwebdev/symfony-intervention-image-bundle --dry-run -v first. A version reset can silently reorder constraints.
composer.json is not a revival. Read the repo, not the tag name.^2.0, ~0.x, dev-master, or a specific 2022 tag, a new 1.0.0 tag may not satisfy it — or may satisfy a ^1.0 you'd previously written off. Run composer why-not janwebdev/symfony-intervention-image-bundle 1.0.0 and composer update --dry-run before touching anything; a surprise major bump here can cascade into symfony/framework-bundle and PHP constraint changes you did not budget for.symfony/framework-bundle pulls symfony/http-kernel, symfony/routing, symfony/event-dispatcher — components Laravel also uses indirectly, at different versions. You may get composer solver noise and you will definitely get autoload/Container class duplication risk. A new tag does not fix this; nothing short of a Laravel service provider would.Image::make($path)->resize(300, null, fn($c) => $c->aspectRatio()). In v3 that is $manager->read($path)->scale(width: 300). With the bundle freshly re-tagged you can no longer infer the major version from the release date. Check composer show intervention/image and the bundle's own constraint; treat every snippet as suspect until you do.memory_limit for queue workers (php -d memory_limit=512M artisan queue:work) or reject oversized uploads at validation with dimensions:max_width=...,max_height=.... Validate getimagesize() before decoding in on-the-fly endpoints, or you've built a DoS vector.->orientate(). Do it before scaling, and strip metadata on output (->strip() in v3, or the save(..., strip: true)/encoder options in v2) so you don't leak GPS coordinates from user uploads.Storage::put() with an encoded image. Storage::put($path, $image->encode()) silently writes garbage or an empty file in some Intervention versions. Always cast to string or use absolute-path save().phpunit.xml.php -m | grep -E 'gd|imagick', confirm imagewebp is in gd_info(), php artisan tinker → app(ImageManager::class)->read(...), and log $encoded->mime() plus strlen((string) $encoded). If a variant is 0 bytes, the write failed, not the transform. On the Symfony side, add bin/console debug:container ImageManager and debug:config <bundle_alias> to confirm the extension actually loaded after the version bump.AppServiceProvider is your swap point (you can bind a decorated manager, a driver selected by config, or a fake in tests). If you outgrow that, implement your own Thumbnailer/ImageProcessor contract and keep Intervention as an implementation detail — nothing in your controllers should type-hint Intervention\Image\*. On Symfony, the equivalent swap point is a service alias or decorator over the bundle's ImageManager service ID.DependencyInjection/Configuration.php first and copy the config keys into config/packages/*.yaml. But for a Laravel codebase, deleting this dependency and writing the 15-line service provider is still the higher-value move — it removes three Symfony packages and an unvetted maintainer from your supply chain. The v1.0.0 tag, on its own, does not change that arithmetic.How can I help you explore Laravel packages today?