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

Im Bundle Laravel Package

leapt/im-bundle

LeaptImBundle integrates the Imagine image manipulation library into Symfony, letting you define reusable filters for resizing, cropping, and thumbnailing images. It provides cached, on-demand image generation and Twig helpers so templates can serve optimized images with minimal configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before you start. leapt/im-bundle is a thin Symfony bundle (5 stars, 0 dependents) that shells out to the convert/mogrify binaries. It is not a Laravel package, and it has no facade, no service provider, and no config publishing. If you are working in Laravel, you are reading this to copy the pattern, not to composer require it.

Pin to >= 6.1.0. v6.1.0 fixes shell command injection in the Wrapper classes and ClearCommand, plus a regex injection in the Twig ImExtension. There is no CVE-style advisory attached and no backwards-incompatible change, but anything below 6.1.0 (including a fork you copied last year) carries the injection flaw. Treat "leapt/im-bundle": "^6.1" as the floor, not a suggestion.

Minimal steps (Symfony):

composer require leapt/im-bundle:^6.1

Register the bundle (config/bundles.php on modern Symfony, AppKernel.php on legacy):

Leapt\ImBundle\LeaptImBundle::class => ['all' => true],

Configure the cache directory and named formats:

# config/packages/leapt_im.yaml
leapt_im:
    cache_path: '%kernel.project_dir%/public/media/cache'
    formats:
        thumb:
            convert: true
            params: ['-thumbnail', '150x150^', '-gravity', 'center', '-extent', '150x150']
        large:
            convert: true
            params: ['-resize', '1200x1200>']

Where to look first: the Manager service (usually leapt_im.manager) and the Wrapper/ directory (Convert, Mogrify). Those two classes are the entire mental model — a wrapper builds an argument list, the manager runs it and returns the path to the cached file.

First use case: generate a thumbnail from an existing image, in a controller:

public function thumbnail(Manager $im, string $source): Response
{
    $path = $im->convert('thumb', $source); // returns cached file path

    return new BinaryFileResponse($path);
}

That is the 80% case: "give me format X of file Y, cached, idempotent."

Verify the binary before anything else. Run convert -version in the same user context as PHP-FPM. Most first-run failures are PATH, not code.

Implementation Patterns

Format-as-configuration. Resist the urge to pass raw ImageMagick flags from controllers. Define named formats in config and reference them by name. This gives you one place to tune quality, one place to invalidate the cache, and prevents arbitrary argument injection.

formats:
    og_image:
        convert: true
        params: ['-resize', '1200x630^', '-gravity', 'center', '-extent', '1200x630', '-strip', '-quality', '82']

Twig usage for templates. The bundle ships a Twig extension; the intended pattern is generating derived assets at render time and letting the cache absorb the cost:

<img src="{{ im_resize(asset_path, 'thumb') }}" alt="">

As of 6.1.0 this also composes correctly with AssetMapper (previously the extension's path handling broke on AssetMapper-style logical paths). If you are on Symfony 7 with AssetMapper, 6.1.0 is effectively your minimum viable version. Note that Symfony 8.0 was dropped from the CI matrix in the same release — on a bleeding-edge Symfony 8 app, test it yourself rather than assuming support.

Be aware that Twig-time generation puts filesystem work inside the render loop. It is fine for a product page with one hero image; it is not fine for a 200-item grid on first load. Warm the cache in a console command or a queue job for anything list-shaped.

Laravel translation of the same pattern. If you are porting the idea, skip the bundle and write one focused service — this is the whole package in ~40 lines:

class Imagemagick
{
    public function __construct(private array $formats, private string $cachePath) {}

    public function convert(string $format, string $source): string
    {
        $format = $this->formats[$format] ?? throw new InvalidArgumentException($format);
        $target = $this->cachePath.'/'.$format['name'].'/'.sha1($source).'-'.basename($source);

        if (! is_file($target)) {
            File::ensureDirectoryExists(dirname($target));
            $cmd = array_merge(
                ['convert', $source. '[0]'],
                $format['params'],
                [$target]
            );
            $this->run($cmd); // proc_open with array args, never a shell string
        }

        return $target;
    }
}

The important design decisions — named formats, content-addressed cache filename, array-form command, single entry point — are portable regardless of framework. They are also the parts 6.1.0 hardened in the upstream bundle, which is a good template for what to get right in your port.

Chain with your upload layer. Let your upload handler own storage and naming; let this layer own derivation. Don't let the image bundle decide your filenames — it caches by source path, so if the source path changes (upload dedup, CDN migration), you silently regenerate everything.

Async for anything expensive. Large PDFs, RAW files, or heavy filters should go through a job. The bundle is synchronous by nature; wrap the convert() call in a queued job and write the resulting path back to your model.

Serve through your web server, not PHP. The cache path should be inside your public/docroot and matched by a static-file rule. Returning a BinaryFileResponse for every thumbnail is the single biggest performance mistake with this pattern.

Deploy-time clearing is a first-class step. The bundle ships a ClearCommand (hardened in 6.1.0) — wire it into your deploy script instead of hand-rolling rm -rf on the cache path. Because cached files key on the source path and not on format params, clearing on deploy is the only reliable way to pick up a tweaked -quality value.

Testing. Because output is a file on disk, assert on the file (dimensions, mime type, existence, idempotent second call) rather than mocking the manager. Gate integration tests behind a binary-availability check so CI without ImageMagick doesn't fail spuriously.

Gotchas and Tips

It is a Symfony bundle, full stop. There is no Laravel bridge. If you're evaluating this for a Laravel codebase, read the source as a reference implementation (it is short) and reimplement. Do not pull in Symfony's HttpKernel and container as a dependency for a 40-line wrapper.

The 6.1.0 security fixes are the headline, not the CI churn. Most of this release is housekeeping — Zizmor for GitHub Actions hardening, PHPStan baseline, dependabot action bumps, un-skipping tests. The two PRs that matter for you are "Fix shell command injection in Wrapper and ClearCommand, regex injection in ImExtension" (#52) and "Fix usage with AssetMapper" (#51). Everything else is maintenance signal: a single maintainer doing steady hygiene work, not a roadmap.

Low adoption means you own the bugs. 5 stars and 0 dependents means near-zero community surface area: no Stack Overflow corpus, no third-party blog posts, no guarantee of a fix for the edge case you hit. The injection fix arriving quietly in a minor release is exactly what that looks like — no advisory, no fanfare, just a changelog line. Fork it into your app/ or vendor it, and pin the version.

Shell injection is fixed upstream — don't reintroduce it. 6.1.0 escapes arguments in the wrapper and clear command, and fixes a regex injection where format names were interpolated into the Twig extension's pattern. That does not mean arbitrary input is now safe end to end: if you implement WrapperInterface, add a pass-through format, or accept a source path from a request parameter, build the argument list as an array and use proc_open/Symfony\Process with an array — not exec("convert $input ..."). Also guard the source path against traversal, and validate format names against your config keys rather than passing them straight through.

ImageMagick's policy.xml will bite you. Modern distros ship policies that block PDF, PS, and sometimes MVG/EPHEMERAL. A convert file.pdf out.png that works in your shell may fail under PHP-FPM if the policy differs, or may silently require Ghostscript. Check /etc/ImageMagick-*/policy.xml when a format "just doesn't work."

Cache invalidation is manual. Cached files key on the source path, not on content or format params. Change thumb's quality setting and every existing thumbnail stays stale. Two mitigations: include a version/hash of the params in the cache filename, or run the bundle's clear command on deploy.

Directory permissions and umask. The cache path must be writable by the web user and readable by the web server process that serves static files. Watch for files created 0600 by a CLI-warmed cache when PHP-FPM runs as a different user — classic "works locally, 403 in staging."

Memory and timeouts. ImageMagick reads the whole image. A 50MP photo can blow memory_limit or hit max_execution_time. Set resource limits explicitly in your params or config (-limit memory, -limit map) and always apply > / < geometry modifiers so small images aren't upscaled into giant buffers.

Strip before you ship. Add -strip to every format. Without it you leak EXIF (GPS coordinates in user uploads is a real privacy issue) and carry 100KB+ of metadata into every derivative. Conversely, if you need orientation to be correct, apply -auto-orient before -strip, since stripping removes the orientation tag and leaves rotated images sideways.

Animated GIFs and multi-page files. By default ImageMagick coalesces and you may get a filmstrip or a single page. source[0] picks the first frame deliberately; anything else is accidental. Decide explicitly.

Debugging: log the command line. When a conversion produces the wrong result, the fastest path is dumping the exact argv. Add a debug log around the process call in your fork — ImageMagick's own error messages are terse (convert: unable to open image), and seeing the full command resolves most of them in seconds. If you are debugging a pre-6.1.0 install, the logged argv is also how you'd spot an escaping bug.

Extension points are thin but real. The value is in WrapperInterface — implement it for a different backend (libvips via vips, or a call to your CDN's transform API) and swap the service definition. If you find yourself fighting the wrapper, replacing it is cheaper than patching it. Just remember any wrapper you write is your responsibility for argument escaping, since the 6.1.0 hardening only covers the bundled ones.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource