Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Image Bundle Laravel Package

gregwar/image-bundle

Symfony bundle wrapping the Gregwar Image library for easy image manipulation: resize, crop, rotate, watermark, and apply filters via services, Twig helpers, or a simple API, with built-in caching to keep generated images fast.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're actually installing. gregwar/image-bundle is a Symfony bundle — it wires the gregwar/image library into Symfony's container, Twig, and routing. In a Laravel app you do not install the bundle; you install the underlying library and use it directly:

composer require gregwar/image

That requires PHP with the GD extension (Imagick optional). If you're on Symfony, the bundle itself gives you the gregwar_image service, the image() Twig function, and a cache-serving controller out of the box.

Where to look first:

  • The Gregwar\Image\Image class in the library — that's 90% of the API surface.
  • The bundle's GregwarImageExtension (Twig function signature) and ImageController (cache-serving route) if you're porting the behavior to Laravel.
  • Config: in Symfony, config/packages/gregwar_image.yaml; in Laravel, your own config/images.php.

First use case — a cached thumbnail. The canonical entry point:

use Gregwar\Image\Image;

Image::open(storage_path('app/public/uploads/photo.jpg'))
    ->resize(300, 300)
    ->save(storage_path('app/public/thumbs/photo.jpg'));

That's it. If you're used to Intervention Image, the mental model is the same, just with a fluent object and no facade.

Implementation Patterns

Chaining operations. The Image object is immutable-ish by convention — each call returns $this, so chain and terminate with a save/output:

Image::open($source)
    ->crop(0, 0, 1200, 1200)
    ->resize(400, 400)
    ->grayscale()
    ->rotate(90)
    ->save($dest, 'jpg', 85);

Terminal methods to remember: save($path, $type, $quality), output($type, $quality) (streams to the browser, useful in a controller), write($path) (no format change), and shortcuts jpeg(), png(), guess().

Resize vs. scale vs. forceResize. These trip people up:

  • resize($w, $h) — shrinks within the box, preserving aspect ratio, padding with background if needed.
  • scale($w, $h) — resizes and crops to exactly fill the box. This is your "cover" behavior.
  • forceResize($w, $h) — stretches to exactly $w × $h, aspect ratio be damned. Almost never what you want.

Layered composition — watermark and effects stack naturally:

Image::open($source)
    ->scale(1200, 800)
    ->watermark(public_path('logo.png'), 'bottom-right', 20, 20, 60)
    ->brightness(-5)
    ->save($dest);

Dynamic, URL-driven serving (the bundle pattern). The Symfony bundle's best idea is that image transforms are described by a URL and cached on disk, so you never regenerate on every request. You can replicate this in Laravel with a route:

Route::get('/img/{path}', function (string $path) {
    $src = storage_path("app/public/{$path}");
    $cache = storage_path('app/image-cache/'.md5($src.request()->query()));
    if (! file_exists($cache)) {
        Image::open($src)
            ->setCache(storage_path('app/image-cache'))
            ->resize(request('w', 800), request('h', 800))
            ->save($cache);
    }
    return response()->file($cache);
})->where('path', '.*');

Cache configuration. In the library, setCache($dir, $gcPeriod) enables a built-in garbage collector and a "pretty name" cache key so URLs like /cache/thumbnails/photo_200x200.jpg map to deterministic files. In the bundle you set this via gregwar_image.cache_dir and web_dir. The web_dir matters: the cache must sit somewhere the web server can serve statically, otherwise every request proxies through PHP.

Wrap it in a Laravel service. Since there's no first-party integration, the day-to-day pattern is a small dedicated class:

class Thumbnailer
{
    public function make(string $diskPath, int $w, int $h): string
    {
        $src = Storage::disk('public')->path($diskPath);
        $out = "thumbs/".md5($diskPath.$w.$h).".jpg";
        Image::open($src)->scale($w, $h)->save(Storage::disk('public')->path($out), 'jpg', 85);
        return $out;
    }
}

Return the relative path and let Storage::url() handle the rest — don't hardcode public_path() everywhere.

Queue the work for real apps. Generation is synchronous and blocking. For user uploads, dispatch a job after the upload completes; keep the HTTP request path free of resize calls.

Gotchas and Tips

The bundle/package identity mismatch is the #1 trap. Composer package name, GitHub repo, and namespace all say "Symfony bundle," but the useful code is the gregwar/image dependency. Anyone copy-pasting bundle docs into Laravel will get nowhere — the image() Twig function and gregwar_image service simply don't exist outside Symfony.

Extensions are optional at runtime. GD is required; Imagick gives you better quality and more formats but isn't guaranteed. If you call webp() or process a TIFF and get silent failures, check the adapter before blaming the code. There's no adapter class to configure explicitly — it auto-detects.

Cache directory permissions and web exposure. The cache dir must be writable by PHP and readable by the web server. The classic failure is a writable cache under storage/ that the web server can't reach — every "cached" image still routes through PHP. Also, setCache() will happily write files derived from user-supplied paths; never pass raw request input as the source path without validating against a whitelist, or you've built a path-traversal file reader.

Memory limits on large source images. A 6000×4000 JPEG can blow past PHP's default memory_limit during decode. The library has setMemoryLimit() but it only raises the limit up to a cap — better to reject oversized uploads at validation time (dimensions:max_width=4000) than to discover this in production under load.

Format guessing follows the extension. save('foo.jpg') writes JPEG, save('foo') guesses from the source. If you rename a PNG to .jpg upstream, you'll get unexpected mime types served with the wrong Content-Type. Be explicit: save($path, 'jpg', 85).

Quality defaults are lossy. JPEG default quality is around 80. For thumbnails that's fine; for hero images bump to 90+ or switch to PNG/WebP deliberately.

Stale cache has no invalidation. File-based caching keys on the path (and, with pretty names, the transform parameters — but not the file mtime). Overwrite an image and the old cached derivative lingers. Either version your filenames (photo-v2.jpg) or clear the cache dir in the same job that replaces the source.

Project health is a real consideration. Low recent activity and no Laravel shims mean you're on your own for framework glue and PHP-version compatibility on bump. Pin the version and keep the wrapper class thin so swapping to Intervention Image later is a one-file change.

Extension points worth knowing: the Image object is mutable-by-chaining, so you can write helpers that accept an Image and apply a house style — a BrandStyle::apply(Image $img) that watermarks and applies contrast is a clean seam and keeps controllers trivial. In Symfony, the equivalent seam is a Twig filter or a tagged service; in Laravel, a Blade directive or a small facade.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource