[6.x] Guard ImageGenerator against a null asset - #15447
Conversation
…c#15359) When an asset URL resolves to null (e.g. a repository that can't find the asset by that URL, such as statamic/eloquent-driver#609), generateByAsset() dereferenced it directly, throwing an uncaught Error rather than the Exception the glide tag already knows how to catch and skip. Return '' early instead, matching the existing skip convention used elsewhere in this method.
| */ | ||
| public function generateByAsset($asset, array $params) | ||
| { | ||
| if (! $asset) { |
There was a problem hiding this comment.
Confirmed the crash is real: Glide::generateImage() (src/Tags/Glide.php:180) does call generateByAsset(Asset::find($item), $params) unguarded, and Asset::find() can return null (e.g. when an item resolves as an asset ID/instance rather than a raw path, so it skips the Str::isUrl() branch and falls through to this line). So a null asset really can reach isVideo() here pre-fix — good catch, and the regression test faithfully reproduces the exact Error from the issue (fails pre-fix, passes post-fix).\n\nOne gap though: this silently returns '' with no Log::error() (or equivalent). The issue's own "Expected" behavior is "an asset that cannot be resolved is logged and skipped, like every other error inside the tag" — every other failure mode in Glide::generate()'s closure hits the catch (\Exception $e) { Log::error(...) } and gets logged. This new path is the one exception: it degrades gracefully but leaves zero trace, so the underlying "asset repository returned null for a resolvable file" condition (the actual bug, per statamic/eloquent-driver#609) becomes invisible/undebuggable in production — which is exactly the visibility gap that made the original 500 take a day to notice at scale.\n\nWorth considering: either add a Log::error()/Log::warning() call here before returning '', or (closer to the issue's first suggested alternative) guard in Glide::generateImage() by throwing an \Exception when Asset::find($item) is null, letting the tag's existing catch (\Exception) log it — that would also mean the 3 other callers of generateByAsset() (PresetGenerator, StaticUrlBuilder, ThumbnailController), which always pass an already-resolved asset, don't carry a defensive null-check they don't need. Not a blocker — the fix does stop the 500 — but as-is a real production trigger of this bug will still go unnoticed.
There was a problem hiding this comment.
Fair catch, added — Log::error() now fires before the early return (also rebased onto 6.x, which had picked up an unrelated conflicting change to this same method in the meantime).
wakqasahmed
left a comment
There was a problem hiding this comment.
Reviewed cold against the linked issue and current source.
Verified as real: Glide::generateImage() (src/Tags/Glide.php:180) passes Asset::find($item) straight into generateByAsset() with no null check, and that result genuinely can be null (statamic/eloquent-driver#609 is a real, confirmed trigger). Pre-fix, generateByAsset() dereferences it immediately via isVideo(), throwing an uncaught \Error that isn't caught by the tag's catch (\Exception), so it does escalate one bad asset into a full-page 500. The fix (early return ''; before the first isVideo() call) stops that, and does so using the same sentinel ('') the method's own isVideo() skip-branch already returns, which Attributes::from() already handles gracefully (degrades to width/height 0, no crash). The new test in ImageGeneratorTest reproduces the exact issue error pre-fix and passes post-fix — traced through makeGenerator(), no mocking gaps.
One real gap (left as an inline comment on the added guard): the fix is silent — no Log::error() — where the issue explicitly asks for 'logged and skipped, like every other error inside the tag.' Every other failure path in Glide::generate()'s closure logs via its catch (\Exception); this one now uniquely doesn't, which reintroduces the original observability problem (undiagnosed for a day in production) in a quieter form. Suggest either logging here, or throwing in Glide::generateImage() instead so the existing catch handles it — that also avoids adding an unnecessary defensive check to generateByAsset()'s three other call sites (PresetGenerator, StaticUrlBuilder, ThumbnailController), which only ever pass already-resolved assets.
Also noting (not a code issue): GitHub currently reports this PR as CONFLICTING/DIRTY against 6.x — will need a rebase before merge.
Not blocking — the crash is genuinely fixed and the test is sound — but the logging gap is worth addressing before merge.
…null-asset # Conflicts: # src/Imaging/ImageGenerator.php # tests/Imaging/ImageGeneratorTest.php
Closes #15359
ImageGenerator::generateByAsset()calls$asset->isVideo()immediately, so when an asset URL resolves tonull(e.g. a repository that can't find the asset by that URL — statamic/eloquent-driver#609 is one real cause) it throwsError: Call to a member function isVideo() on nullinstead of returning gracefully. Because that's an\Error, not an\Exception, it isn't caught by the glide tag's existingcatch (\Exception)handling, so one unresolvable asset 500s the whole page instead of just being skipped.Added an early return of
''when$assetis falsy, matching the method's existing pattern of returning''to skip (see theisVideo()branch just below it). Added a regression test forgenerateByAsset(null, [...])and confirmed it throws the exact error from the issue before the fix and passes after. Ran the fulltests/Imaging+tests/Tags/GlideTest.phpsuites (147 tests, 297 assertions) and Pint, both clean.