snowcap/im-bundle
Snowcap IM Bundle is a Symfony bundle for adding instant messaging to your application. It provides services and integrations for user-to-user conversations, message handling, and configurable storage, making it quick to wire up basic chat features without building the messaging layer from scratch.
This is not a Laravel package — it's a Symfony2 bundle (last release 2016, 23 stars, zero dependents) that shells out to the convert/mogrify CLI. There is no service provider, no facade, no config publishing. Your first decision is whether to use it at all.
Install reality check
composer require snowcap/im-bundle
On a modern Laravel app this will almost certainly fail resolution: the bundle pins symfony/process, symfony/config, and symfony/dependency-injection to 2.x/3.x ranges and targets PHP 5.x. Don't fight it. Pick one:
Manager.php into app/Support/ImageMagick.php, add symfony/process, and wire it yourself. You get 95% of the value with none of the dependency hell.intervention/image (needs the imagick PHP extension) or league/glide if all you need is resize/crop/format. Be honest with yourself about this — the package's whole surface is -resize/-crop/-quality string building.Where to look first (in the repo)
Manager.php (under src/Snowcap/ImBundle/) — read this end to end first. It is the package. Everything else is Symfony plumbing.DependencyInjection/Configuration.php — the exact YAML schema for defining named formats.Resources/config/services.xml — the service id you'd alias if you did bridge it (snowcap_im.manager, verify the exact id here).README.md — the format-preset syntax and Twig helper examples.Twig/ extension — that's your only "integration" surface.First use case
Define a format preset, then convert an uploaded file to a destination path:
# config (Symfony) or config/images.php (Laravel bridge)
snowcap_im:
formats:
thumb:
filters: [ "-auto-orient", "-resize 200x200", "-strip" ]
// Laravel: bind a singleton in a service provider
$this->app->singleton(ImageMagick::class, fn () =>
new ImageMagick(config('images.formats'), config('images.binary', 'convert'))
);
// In a controller/job
$im->convert('thumb', storage_path("app/orig/{$name}"), storage_path("app/thumbs/{$name}"));
Confirm the binary first: convert -version (IM6) or magick -version (IM7). If that fails, nothing else matters.
Presets live in config, never in request data. The core value of this bundle is the format registry: a name (thumb, hero, og) maps to a fixed filter string. Keep the mapping in config/images.php, cache it (php artisan config:cache), and never accept a filter string from a user — that's a shell injection vector (see Gotchas).
convert vs mogrify — pick deliberately.
convert(format, $src, $dst) — non-destructive, source preserved. Default for uploads.mogrify(format, $src) — in-place, destructive, no destination. Only use on a file you already copied to a temp/derived path. Easy way to lose originals in a retry loop.Bridge pattern for Laravel (if you go the extract route):
// app/Providers/ImageServiceProvider.php
public function register(): void
{
$this->app->singleton(ImageMagick::class, function ($app) {
return new ImageMagick(
$app['config']->get('images.formats'),
$app['config']->get('images.binary', 'convert'),
);
});
}
Then inject ImageMagick into jobs and services. Add a Storage-based wrapper so callers pass disks/paths, not raw strings.
Queue the work. Conversion is CPU-bound and spawns a process. Dispatch a GenerateThumbnails job after upload; set $timeout and $tries on the job. A 20 MP JPEG resize can take seconds — never in the request cycle.
Pipeline shape that works well:
storage/app/orig).convert each format → write to storage/app/derived/{format}/{hash}.jpg.md5($sourcePath . $mtime . $format) so re-runs are idempotent.Chained formats. Because presets are just filter strings, compose them by concatenating filter arrays at config time (thumb_square = thumb + -crop 1:1) rather than chaining conversions — one convert invocation, one decode/encode.
Testing. Unit-test your wrapper by asserting the built command string (inject a fake Process). Keep one integration test guarded by if (!shell_exec('which convert')) $this->markTestSkipped(). Use a 10×10 PNG fixture; don't commit real photos.
Symfony-side usage (if you actually run the bundle): the Twig extension is the day-to-day ergonomic win — {{ asset | im_convert('thumb') }} in templates. In Laravel, replicate it as a Blade directive or a img_thumb($path) helper that returns a URL, not a filesystem path.
Process APIs (ProcessBuilder::setArguments) and deprecated array/string argument handling are the usual breakages. Budget an hour for a fork-and-fix if you insist on using it as-is.$(...), or a leading - will break or, worse, execute. Quote paths defensively and reject/normalize uploads (use Str::slug + a UUID, never the client filename). Prefer array-style process args over string commands.convert fails with a non-zero exit and a terse stderr. Always mkdir -p the destination and check $process->getExitCode() / getErrorOutput() — the bundle historically swallows or under-reports this.policy.xml disables PDF/PS by default, and memory/disk/map limits cause failures — often as an opaque exit code or truncated output. If conversions "work locally, fail in prod", check /etc/ImageMagick-*/policy.xml and raise memory/disk limits for large inputs.convert on IM6, magick on IM7 (where convert is often a symlink but magick convert is the safe form). Pin the binary in config and pin the IM version in your Docker image — resize rounding and -strip behavior differ across versions.-auto-orient in every preset. Add it once at the top of the filter list, not per-format-and-hoping.-background white -flatten (or -alpha remove) in any preset that outputs JPEG from arbitrary input.-profile/-colorspace sRGB handling is on you — the bundle has no opinion.$formats[$name] on a missing key yields a null/undefined-index rather than a clear error. Validate your images.formats keys at boot (a small config test) so thumb vs thumbs is caught in CI.app/Support/ImageMagick.php and deleting the bundle dependency.How can I help you explore Laravel packages today?