Repository navigation
[6.x] Short-circuit a Value wrapping null before it reaches a modifier - #15540
Merged
jasonvarga merged 1 commit intoSep 29, 2026
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #15539
ModifierManager::evaluate()checked$returnValue === nullbefore unwrapping aValue, so aValuewrappingnullpassed the check and reached the modifier as a barenull. A barenullnever gets that far, becauseevaluate()returns before the modifier loop. This swaps the two blocks so the unwrap happens first and a wrapped null stops the chain the same way.Empty optional fields in replicator rows arrive as
Value(null), becauseReplicator::performAugmentation()fills in every field in the set's blueprint, so this path is common. Measured on6.xby rendering each registered modifier with no parameters. There are 240 handles, and 235 were swept, leaving outdd,ddd,dump,debugandray. Before the change, a wrapped null:E_DEPRECATEDin 56 modifiers (for examplesanitize,strip_tags,nl2br,md5);count,explode,limit,is_after). These are mostlyTypeErrors, whichModify::modify()doesn't turn into aModifierException, so they aren't logged away when debug is off;After the change, every one of the 235 renders the same for
Value(null)as fornull.Behavior change
Those 32 modifiers no longer produce output for a wrapped null. They now return what they already returned for a bare
null:bool_stringcdataceilconsole_logcontains_alldoesnt_overlapends_withfaviconfloorformat_numberimageis_alphais_alphanumericis_blankis_lowercaseis_uppercasejsonlengthlinkmailtomd5neatifyread_timeroundshrugstarts_withsumtableto_jsontype_ofword_countyears_agoFor example,
{{ empty_field | md5 }}renderedd41d8cd98f00b204e9800998ecf8427eand{{ empty_field | is_blank }}rendered1when the field was aValue(null), but nothing when it wasnull. So this removes an inconsistency rather than adding one.Tests
Adds
test_a_value_wrapping_null_short_circuits_modifiers_like_a_bare_nulltotests/Antlers/Runtime/CoreModifiersTest.php. It renderssanitize,count,md5,is_blankand a chain (upper | md5) on both a barenulland aValue(null)and expects empty output from each. On6.xwithout the change it fails withcount(): Argument #1 ($value) must be of type Countable|array, null given; with it, it passes.tests/Antlers(961 tests) andtests/Modifiers(571) pass. In the full suite on Windows, the only failures areComposerLockBackupTest(2) andTemplateFolderTest(symlink(): Permission denied), and those fail the same way on6.xwithout this change.