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

Progressive Image Bundle Laravel Package

tito10047/progressive-image-bundle

Progressive image bundle for PHP apps: generates low-quality image placeholders and responsive, lazily loaded variants so pages paint instantly while full-resolution images stream in behind the scenes.

View on GitHub
Deep Wiki
Context7

Getting Started

First, calibrate expectations: this is a Symfony bundle, not a Laravel package. There is no service provider, no config/progressive_image.php, no publishable assets. composer require in a Laravel app will install code that never boots. So the "getting started" path splits in two:

  • You actually have a Symfony app (or a Laravel app fronting a Symfony service): follow the pgi.blue docs, start with the Flex recipe once merged, and pin the version exactly — composer require "vendor/progressive-image-bundle:2.0.0-rc1", not ^2.0.
  • You're a Laravel dev reading this for ideas (the likely case): read it as a reference architecture. The valuable, portable parts are the content-addressed Variant pipeline, the three generation strategies, and the post-processing chain. You'll implement those with intervention/image ^4.2, Laravel queues, and Cache::lock().

Prerequisites to check before anything else: PHP 8.3+ (hard bump from 8.2, driven by intervention/image ^4.2), and Symfony 6.4 / 7.4 / 8.1+. If your CI matrix, Docker base image, Forge/Vapor runtime, or composer.json platform config is on 8.2, nothing else matters until that's fixed.

Where to look first: the CHANGELOG entry for 2.0.0-rc1, and specifically the Liip migration guide — not because you're migrating, but because it enumerates every behavior that changed, which is the fastest way to understand the new pipeline's shape.

First use case: generate one thumbnail, synchronously, and inspect the resulting filename. If it's a deterministic hash of source + spec, the model clicked and you can reason about the rest.

Implementation Patterns

Map the three strategies onto Laravel primitives. This is the core translation exercise:

Progressive Image Bundle Laravel equivalent
sync generate inline in the controller/request
terminate (on kernel.terminate) dispatchAfterResponse() or a terminable middleware
async via Messenger a queued job on a dedicated images queue

For user-facing pages, terminate/dispatchAfterResponse is usually the right default: the response ships immediately, generation happens after the client is served, and you avoid a worker round-trip for something that takes 200ms.

Content-addressed variants are the pattern worth stealing. Hash the source identity plus a normalized spec (filter names, order, and numeric params as integers — not floats, not arrays in arbitrary key order). Write to a path derived from that hash. The payoff is idempotency: two workers, three retries, and a duplicate job all converge on the same file with no coordination. Wrap generation in Cache::lock("variant:{$hash}", 30) and release in a finally — the lock exists to prevent redundant work, not to guarantee correctness, so a lock timeout should mean "someone else is doing it, return the existing path or a placeholder," not an error.

The relative_resize + paste combination is your watermark/composite path. auto_rotate should run before any resize (EXIF orientation, then dimensions), and strip_metadata after — but see the gotcha below about ICC profiles.

Post-processing is a separate, fallible stage. jpegoptim, pngquant, cwebp, avifenc are external binaries. Model them as an optional step with a per-format allowlist and a hard which-style check at boot/deploy rather than a silent no-op at runtime.

URL resolution needs a PHP-side path, not just Twig. The rc exposes ResolveFilterUrlHandler alongside the pgi_filter() Twig function. In Laravel terms that's your ImageVariantResource / API transformer returning signed URLs, with a route that 302s to the variant if it exists and dispatches generation + returns a low-quality placeholder if not. Sign those URLs — an unsigned on-the-fly resolve route is an unbounded-cost endpoint.

CLI warm/remove map directly to Artisan. progressive-image:variant:warm → php artisan images:warm (iterate your models/collections, dispatch a job per spec). variant:remove → php artisan images:prune --older-than=30d. You need the second one from day one; see below.

Gotchas and Tips

This is a release candidate. The notes say explicitly to expect breaking changes before 2.0.0 final. Pin the exact string 2.0.0-rc1. Do not use ^2.0, do not use ~2.0, and do not let a composer update in a sibling branch silently move you between RCs — the pipeline internals are new enough that patch-level drift is not safe here.

PHP 8.3 is non-negotiable. intervention/image ^4.2 requires it. This is the single most common reason a 2.0 upgrade fails, and it fails at container compilation, not at runtime, which makes it look like a bundle bug.

intervention/image v3 → v4 is its own breaking change. If you have existing Image::make()->resize() code, the v4 API differs. Budget for that separately from the bundle upgrade; they're independent migrations that happen to land together.

Liip independence cuts both ways. The dependency is truly gone — not required, not touched. If you're coming from 1.x, your liip_imagine config, imagine_filter() Twig calls, and cached file paths on disk all become orphans. The filter-name mapping is not guaranteed 1:1; check each one against the new filter set (grayscale, negative, auto_rotate, paste, relative_resize, plus the older ones) rather than assuming.

generation.transport was broken in rc0 and is fixed in rc1. If you evaluated rc0 and concluded async didn't work, retest. Verify the routing actually resolves to your transport — a misconfigured transport that silently falls back to sync is worse than an error, because it looks fine in dev and blows up under load.

Content addressing means the cache never shrinks by itself. Every spec change — a new quality value, a filter reorder, a strip_metadata toggle — mints a whole new family of files and orphans the old ones. You need variant:remove on a schedule from the first deploy. Normalize specs aggressively before hashing so that quality: 80 and quality: "80" don't produce two variants of everything.

strip_metadata can shift colors. Stripping EXIF is fine; stripping the ICC profile will visibly change wide-gamut images and any image with a non-sRGB profile. Prefer an option that drops EXIF but retains ICC, and spot-check photographic content rather than test swatches.

The two rc1 fixes are Symfony-infrastructure bugs (asset_mapper resolver breaking container compilation when symfony/asset-mapper isn't installed; bundle assets registering under the wrong scoped npm namespace). They tell you the Flex recipe is still settling, so treat "zero-config composer require" as aspirational until the recipe merges.

Extension points are Symfony DI constructs — compiler passes, tagged services, services.yaml overrides. There is no Laravel-style package bootstrapping, facade, or config merge. If you're building a Laravel wrapper, you're writing a new package that borrows the design, and you own the whole surface.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle