fix(hooks): support Closure as dynamic filter reply - #272
Open
faisalahammad wants to merge 1 commit into
Open
Conversation
- WP_Mock::expectFilter('foo', $closure) now invokes the closure at
runtime with the actual filter arguments and uses its return value
as the filter reply. Mirrors the userFunction closure-return pattern
requested in 10up#235.
- The lower-level onFilter('foo')->with(...)->reply($closure) also
works: Filter_Responder::send() gains a Closure branch that invokes
the stored closure with the runtime args, ahead of the existing
InvokedFilterValue and raw-value branches.
- Filter::apply() falls through to a __CLOSURE__ bucket when the trie
walk misses, and forwards runtime args in the argsnull branch so a
closure stored there also receives them.
- Fix pre-existing Behat parse error in features/bootstrap/FunctionsContext.php
(missing semicolon) and remove a step-collision from the iExcpectWhenIRun
typo method, which prevented the entire Behat suite from running.
Closes 10up#235
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.
Summary
Fixes #235. A
Closurepassed toWP_Mock::expectFilter(, )or toonFilter()->with(...)->reply()is now invoked at runtime with the actual filter arguments and its return value becomes the filter reply. This mirrors theWP_Mock::userFunction(..., ['return' => )pattern that already exists for mocked functions.Changes
php/WP_Mock/Filter.phpWhy: The trie-walk in
Filter::apply()had no path to a closure reply when the runtime argument offsets did not match the stored bucket key, andFilter_Responder::send()returned any non-InvokedFilterValuevalue verbatim, leaking the closure object out ofapply_filters().Filter_Responder::send(): newinstanceof \Closurebranch ahead of the existingInvokedFilterValuecheck. The closure is invoked with the runtime args.Filter::apply(): new fall-through to a'__CLOSURE__'bucket when the trie walk misses. The argsnull branch now forwards `` tosend()(was passing no args) so a closure stored in either bucket receives the runtime filter values.php/WP_Mock.phpWhy: The existing
expectFilter()signature accepts variadic args that get forwarded toonFilter()->with(...). With a closure as a variadic arg, the existingInvokedFilterValueshim path was a no-op for closures, so a separate short-circuit was needed.expectFilter(): detects a\Closurein the variadic args and routes throughonFilter()->with(\Closure::class)->reply(). TheInterceptmock setup is skipped in this path (assertions still work viaonFilter's existingEventManager::calledcall).tests/Unit/WP_MockTest.phpWhy: Three PHPUnit tests exercise the new behavior end-to-end:
testExpectFilterWithClosureRepliesDynamically: confirmsexpectFilter('foo', $closure)returns the closure's value, and that the closure is invoked per call.testOnFilterWithClosureReplyIsInvokedWithRuntimeArgs: confirms the lower-levelonFilter(...)->with(\Closure::class)->reply($closure)works and receives the runtime filter args.testOnFilterWithClosureMatcherAndClosureReply: confirms the'__CLOSURE__'fall-through path is reachable.features/hooks.featureandfeatures/bootstrap/HooksContext.phpWhy: BDD coverage for the new behavior via a dedicated step
I expect filter :filter to reply dynamically with a :operation closure.features/bootstrap/FunctionsContext.phpWhy: Unrelated to the feature, but blocked the Behat suite from running. A pre-existing parse error (missing semicolon) and a step-collision in the
iExcpectWithoutRtypo method prevented the suite from running at all. Without this fix, the Behat suite returns a fatal error and CI never executes the feature scenarios.phpstan-baseline.neonWhy: Two new
call_user_func_array(array(..., 'send'), $args)call sites inFilter::apply()match an existing baseline pattern; updated count from 1 to 4.docs/usage/mocking-wp-action-and-filter-hooks.mdAdded a
## Dynamic return values with a Closuresection with examples for both the high-level and low-level APIs.CHANGELOG.mdAdded an Unreleased entry under
### Addedfor the new feature and### Removedfor the typo method.Testing
The pre-existing Behat failure (
expectFilterNotAdded fails when filter added) is unrelated to this fix and exists ontrunk.Reproduction
Before the fix, the following test fails because the closure is returned as a
Closureobject fromapply_filters()instead of being invoked:After the fix, the same call returns
'HELLO'.