Routes, URLs and errors
The bundle registers a route for on-demand image transformation (used by Glide and other local transformers):
GET /image/{transformer}/{loader}/{path}Import the routes in your application:
picasso: resource: '@PicassoBundle/config/routes.php'URL Aliases
Section titled “URL Aliases”By default, image URLs name the transformer and the loader: /image/glide/product_image/photo.jpg. Give either one a url_alias to replace its name in the URL with a shorter one, or with one that keeps your configuration names private:
picasso: transformers: glide: { url_alias: g } loaders: product_image: { type: vich, url_alias: pi }# → /image/g/pi/photo.jpgLoaders and transformers registered with the attributes take it as urlAlias: #[AsImageLoader('s3', urlAlias: 's')], #[AsImageTransformer('cloudinary', urlAlias: 'c')].
- An alias only changes URLs. Templates,
default_loader,default_transformerand purges keep using the names. - URLs naming a loader or transformer by its name keep being served after it gets an alias, so adding one does not break the URLs already published.
- An alias may contain letters, digits,
_and-. It must not be the alias or the name of another loader (or transformer, for a transformer alias). A loader and a transformer can share one, since they fill different URL segments. Conflicts fail at container build. - With
public_cache, cache keys use the aliases as well, so they keep mirroring the URL path. Setting or changing an alias moves the cache keys like renaming would: purge the variants first, or let the old ones be.
Error Responses
Section titled “Error Responses”The image controller answers a 404 Not Found whenever an image cannot be served, and wraps the reason in a NotFoundHttpException whose previous exception tells the cases apart:
| Cause | Previous exception |
|---|---|
| Source file missing, invalid signature, malformed public-cache path | ImageNotFoundException |
Source file exists but is not a decodable image (truncated upload, PDF named .jpg…) |
UndecodableImageException |
This lets a kernel.exception listener react to one case only. For instance, an application redirecting unservable image URLs to the original file should do so for ImageNotFoundException only: for an UndecodableImageException the original is the broken file.
if ($throwable instanceof NotFoundHttpException && $throwable->getPrevious() instanceof ImageNotFoundException) { // Safe to redirect to the original file}When the storage holding the source is unavailable (an ImageSourceUnavailableException: unreachable, timing out, answering 5xx or 429), the image may well exist: the controller, and ImageServer::serve(), answer a 503 Service Unavailable instead, with Retry-After: 30 and Cache-Control: no-store whatever cache_control says, so no CDN keeps it after the outage. It is a ServiceUnavailableHttpException whose previous exception is the ImageSourceUnavailableException. Symfony logs 5xx exceptions as critical; to keep an outage of your storage out of your error tracker, lower it:
framework: exceptions: Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException: log_level: warningThis needs Glide 3 or later: Glide 2 drops the source’s exception, so such a read stays a 500 there.
The controller, not the transformer, owns the Cache-Control of what it serves, configured under picasso.cache_control:
- Served images (and their
304 Not Modified) getpublic, max-age=<max_age>plusimmutablewhen enabled; a transformer’sExpiresis dropped so it cannot contradictmax-age. Withmax_age: ~, the transformer’s own headers are kept. Redirects keep theirs. - 404s are not cacheable by default. Set
error_max_ageto let clients and CDNs keep them for that many seconds (Cache-Control: public, max-age=…).
When two requests render the same variant at once and the cache storage rejects the second write (S3-compatible storages may answer 409 Conflict), the request is still answered with the variant the first one cached, instead of an error.
