Remote (HTTP) Source Loading
By default the bundle only reads source images from local disk (via resolvers / FileSystemResolver / AssetMapperResolver). If your originals live behind a URL instead — another service's CDN, a headless CMS, a third-party asset host — variant_source.http lets SourcePath values be absolute http:///https:// URLs instead of local paths.
This is off by default, and stays off unless you explicitly allowlist hosts: fetching whatever URL a caller (or a filter set built from user input) supplies is a textbook SSRF surface, so the bundle fails closed rather than open.
Enabling it
# config/packages/progressive_image.yaml
progressive_image:
variant_source:
http:
enabled: true
allowed_hosts: ['images.example.com', 'cdn.partner.example']
timeout: 5 # seconds, defaults to 5allowed_hosts is required (and validated at compile time) whenever enabled: true — an empty allowlist with HTTP loading enabled is rejected outright rather than silently allowing every host.
Once enabled, any SourcePath starting with http:// or https:// is routed to the remote reader; everything else keeps going through the existing local resolver chain, unchanged:
<twig:pgi:Image src="https://images.example.com/hero.jpg" sizes="..." />{{ pgi_filter('https://images.example.com/hero.jpg', 'thumb_small') }}How it works
A new HttpSourceReader (implementing the same SourceReader port as the built-in local reader) fetches the URL via symfony/http-client, checks the response status and decodes the bytes with getimagesizefromstring() — exactly the same SourceImage shape (stream + dimensions + mime) the rest of the pipeline already expects from a local file. ChainSourceReader picks between it and the local reader based on the SourcePath value.
Requires symfony/http-client (composer require symfony/http-client if it isn't already pulled in transitively) — enabling variant_source.http without it installed fails fast at container-compile time with a clear message, the same pattern the bundle uses for the messenger-dependent async generation strategy.
Verifying it behaves correctly
Like every other port in this bundle, SourceReader has a shared behavioral contract test — Tito10047\ProgressiveImageBundle\Tests\Variant\Contract\SourceReaderContractTestCase — run against both the built-in local reader and HttpSourceReader. If you write your own SourceReader (e.g. a signed-S3-URL variant, or one with retry/backoff), extend it the same way:
final class MySourceReaderContractTest extends SourceReaderContractTestCase
{
protected function createReader(): SourceReader { /* ... */ }
protected function existingSourcePath(): SourcePath { /* ... */ }
protected function expectedDimensions(): Dimensions { /* ... */ }
protected function expectedMime(): string { /* ... */ }
protected function missingSourcePath(): SourcePath { /* ... */ }
}A reader that passes only your own ad-hoc tests but not this shared contract is a lie GenerateVariantHandler and InterventionImageManipulator would silently rely on.