Skip to content

Fall back to the default image when an image cannot be generated - #1665

Merged
dbu merged 4 commits into
liip:2.xfrom
Amoifr:feature-1622-default-image-fallback
Sep 6, 2026
Merged

dbu merged 4 commits into
liip:2.xfrom
Amoifr:feature-1622-default-image-fallback

Conversation

@Amoifr

@Amoifr Amoifr commented Sep 5, 2026

Copy link
Copy Markdown
Contributor
Q A
Branch? 2.x
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Fixed tickets #1622
License MIT
Doc yes

Follows @dbu's design in #1622.

default_image only ever replaced a source image that could not be loaded. When the source is there but the driver cannot process it (the reporter's case: a video uploaded next to images, but also an SVG imagick will not rasterize), or when the requested filter set does not exist, the controller still turned that into an error page.

The controller now takes a debug flag, defaulting to %kernel.debug%:

  • debug: true keeps throwing, so a typo in a filter name or a file the driver chokes on stays visible while developing.
  • debug: false redirects to the default_image for those two cases as well, and logs the exception with the warning level so it does not get silently swallowed. If no default image is configured, the exception is thrown as before.

NotLoadableException is untouched: it falls back to the default image regardless of the flag, as it always did.

ControllerConfig gains a second constructor argument that defaults to true, and ImagineController a fifth optional one for the logger, so both stay BC.

While at it, default_image is documented for the first time. It has been around for 11 years and only appeared in the config dump.

The default image only replaced a source that could not be loaded. An image
the driver fails to process, or a filter set that does not exist, still ended
up as an error page in production.

Outside of debug mode, both now redirect to the default image as well, and the
exception is logged as a warning. The new controller.debug option defaults to
the kernel debug mode, so failures stay visible while developing.
@Amoifr
Amoifr force-pushed the feature-1622-default-image-fallback branch from 28342db to c9c5bb4 Compare September 5, 2026 09:12

@dbu dbu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, this seems a good idea to me.

i suggest 2 small improvements to the PR, otherwise looks correct to me.

Comment thread Controller/ImagineController.php Outdated
return null;
}

$this->logger->warning(\sprintf('Failed to create image for filter "%s", falling back to the default image. Message was "%s"', $filter, $exception->getMessage()), ['exception' => $exception]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please don't use sprintf but logger context for the filter and message. the additional context with the exception is good, it makes the exception logged if the log level is high enough.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: filter and message are context values now, with the exception alongside them.

Comment thread Controller/ImagineController.php Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it would make sense to route this through createDefaultImageResponse as well, so that we get the logging and avoid duplication.

i am aware that there is a BC break that the exception is now thrown in debug mode, but i'd hope people do not run debug mode in production. if we are worried about it, we could add a flag to createDefaultImageResponse to overwrite the debug flag for a call.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Routed through the helper, with an $evenInDebug flag rather than the BC break. This fallback predates the debug check and already applied in debug mode, so the flag keeps it exactly as it was while still getting the logging and dropping the duplication. Covered by a new test.

Pass the filter and the message as logger context instead of interpolating
them into the message, as suggested.

Route the NotLoadableException fallback through createDefaultImageResponse
too, so it is logged and the code is not duplicated. That fallback predates
the debug mode check and applied in debug mode as well, so the helper takes
a flag to keep it that way rather than changing its behaviour.
@Amoifr

Amoifr commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the quick review!

First point applied: the filter and the message are logger context now, and the exception stays in there too.

On the second one I took your fallback option rather than the BC break, because that fallback is older than this PR: NotLoadableException already returned the default image in debug mode before I touched anything. Routing it through the helper as-is would not have changed my new code, it would have changed a long-standing behaviour for people who did not ask for it.

So createDefaultImageResponse() takes an $evenInDebug flag, which that one call site passes. You get the single code path and the logging, and nobody's debug setup changes. I added testNotLoadableSourceStillRedirectsToDefaultImageInDebugMode since nothing covered that guarantee.

Comment thread Controller/ImagineController.php Resolved

@dbu dbu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, thanks!

i added a note that we want to remove the evenInDebug flag in the next major.

@dbu
dbu merged commit 0aa0040 into liip:2.x Sep 6, 2026
12 checks passed
@trsteel88

Copy link
Copy Markdown
Contributor

@dbu, I'm seeing a lot of critical errors in production. It's coming from bots hitting old URLs for filters that have since been deleted from config.

Tracing it through: when a filter is totally unconfigured (not just missing default_image), createDefaultImageResponse() → DataManager::getDefaultImageUrl() → FilterConfiguration::get($filter) throws NonExistingFilterException again, and that second throw isn't caught - it escapes past the catch (NonExistingFilterException) block entirely instead of reaching the NotFoundHttpException fallback right below it.

This only happens outside debug mode, since in debug the method returns early before calling getDefaultImageUrl. Looks like getDefaultImageUrl() (or its caller) needs to treat "filter not configured" as "no default image" rather than propagating the exception.

@Amoifr

Amoifr commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks a lot for tracing it, @trsteel88, and sorry for the trouble! That is my bug: the fallback did not account for getDefaultImageUrl() throwing for an unknown filter. The fix is in #1669, @dbu, it brings back the 404 those bots got before 2.18.0.

@Amoifr
Amoifr deleted the feature-1622-default-image-fallback branch October 18, 2026 10:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants