Getting Started
Progressive Image Bundle requires PHP 8.3+ and Symfony 6.4, 7.4, or 8.1+. It ships as a Symfony bundle (tito10047/progressive-image-bundle) and works with either Symfony UX AssetMapper or a Webpack Encore / bundler-based frontend, since it only needs a working Stimulus environment.
Why PHP 8.3? The Variant pipeline is built on
intervention/image^4.2, which itself requires PHP 8.3+. Seecomposer.jsonfor the exact per-package Symfony version constraints.
1. Install via Composer
composer require tito10047/progressive-image-bundleThere's no published Symfony Flex recipe yet, so composer require won't auto-configure anything — register the bundle and import its routing manually, both shown below.
// config/bundles.php
return [
// ...
Tito10047\ProgressiveImageBundle\ProgressiveImageBundle::class => ['all' => true],
];2. Import the routing
The bundle exposes two routes: pgi_variant_serve, used to serve a variant that is still pending generation and as an nginx try_files fallback target (see Serving Behind Nginx); and pgi_variant_resolve, the on-the-fly {filterSet}/{path} resolve route (see On-the-Fly Resolve Route). Import both even if you don't plan to use those flows immediately:
# config/routes/progressive_image.yaml
progressive_image:
resource: "@ProgressiveImageBundle/config/routes.php"3. Configure a tagged cache pool (optional but recommended)
If you enable image_cache_enabled (fragment-caches the rendered <twig:pgi:Image> HTML, see Caching), the bundle needs a cache pool that supports tag-based invalidation:
# config/packages/framework.yaml
framework:
cache:
pools:
cache.my_cache:
tags: true4. Configure the bundle
At minimum, decide how images get resolved from a Twig src string to a real file (a resolvers entry) and, if you want the bundle to actually generate resized files instead of just calculating dimensions, where generated variants are stored (variant_store.storage, see Variant Pipeline → Storage).
# config/packages/progressive_image.yaml
progressive_image:
resolvers:
default:
type: filesystem
roots: ['%kernel.project_dir%/public']
allowUnresolvable: true
responsive_strategy:
grid:
framework: tailwind # or bootstrap, or custom
ratios:
landscape: '16/9'
portrait: '3/4'
square: '1/1'
retina:
enabled: true
multipliers: [1, 2]
# Opt into the Variant pipeline: without this, the bundle only computes responsive
# attributes/URLs — it never generates a resized file itself.
variant_store:
storage: 'oneup_flysystem.variants_filesystem' # any League\Flysystem\FilesystemOperator serviceSee the full Configuration Reference for every option and its default.
5. Generate the responsive CSS
The bundle communicates each breakpoint's target width to the browser via CSS custom properties. Generate the stylesheet once (re-run whenever you change breakpoints):
php bin/console progressive-image:generate-custom-cssThis writes assets/styles/progressive-image-custom.css. Import it from your JS entry point:
// assets/app.js
import './styles/progressive-image-custom.css';Using Tailwind or Bootstrap breakpoints? Pre-built CSS files already ship in the bundle's
assets/styles/directory — importprogressive-image-tailwind.cssorprogressive-image-bootstrap.cssinstead of running the generator.
6. Render your first image
{# Simplest possible usage — one fixed size, no responsive breakpoints #}
<twig:pgi:Image src="{{ asset('images/hero.jpg') }}" alt="Beautiful landscape" />
{# Responsive, with Tailwind-like breakpoint selectors #}
<twig:pgi:Image
src="{{ asset('images/hero.jpg') }}"
sizes="sm:12 md:6@landscape lg:4@square"
alt="Responsive hero image"
/>sizes token | Meaning |
|---|---|
sm:12 | Full-width (12 columns) from the sm breakpoint |
md:6@landscape | Half-width (6 columns) from md, cropped to 16:9 |
lg:4@square | One-third width (4 columns) from lg, cropped to 1:1 |
The bundle resolves the source's real dimensions, computes the pixel width needed at each breakpoint, and (if variant_store.storage is configured) generates every required size on first request — a <picture> element with the correct srcset/sizes is rendered immediately, and subsequent requests are served straight from storage. See The Twig Component for every available attribute.
For maintainers: what a Flex recipe would look like
Symfony Flex recipes live in the separate symfony/recipes-contrib repository (not inside this one) as a PR adding tito10047/progressive-image-bundle/<version>/, with:
manifest.json:json{ "bundles": { "Tito10047\\ProgressiveImageBundle\\ProgressiveImageBundle": ["all"] }, "copy-from-recipe": { "config/": "%CONFIG_DIR%/" } }config/packages/progressive_image.yaml— a minimal starter config (at least oneresolversentry, since that's required):yamlprogressive_image: resolvers: default: type: filesystem roots: ['%kernel.project_dir%/public']config/routes/progressive_image.yaml— importing this bundle'sconfig/routes.php, the same snippet shown in step 2 above.
This automates exactly the two manual steps above for anyone using Flex; until it's submitted and merged, follow steps 1–2 as written.
What's next?
- Configuration Reference — every setting, explained
- The Twig Component — full
sizessyntax, point of interest, retina, preload - Variant Pipeline → Overview — how images actually get generated
- Architecture — how the codebase is layered, for contributors