Add pluggable escaper resolver for the Like filter#375
Merged
dereuromark merged 1 commit intoMay 11, 2026
Conversation
ADmad
approved these changes
May 11, 2026
Builds on the per-query driver resolution from the previous commit
in this branch by exposing the driver-to-escaper mapping as a
config option. Apps register a custom escaper for a custom or
subclassed driver by extending the map at filter setup, without
having to subclass the filter:
$searchManager->like('title', [
'escapers' => [
App\Database\Driver\MyMariaDb::class => 'App.MyMariaDb',
],
]);
The shipped defaults (Sqlserver -> Search.Sqlserver, Postgres ->
Search.Postgres) are merged in via Cake's normal `_defaultConfig`
behavior, so users only specify their additions. Entries are
evaluated in iteration order; the first instanceof match wins.
When nothing matches, `Search.Default` is used.
The previous in-line driver match in `_setEscaper()` is replaced
by a new protected hook `_resolveEscaperClass(Driver $driver)`,
which subclasses can also override for custom resolution logic.
Adds two tests: custom escaper picked via map override, and the
fallback to Search.Default when the map is explicitly empty.
Updates docs/filters-and-examples.md to document `escapers` next
to `escaper`, including a worked example and a note about
ordering and instanceof matching.
9c5f0fd to
fc32a71
Compare
dereuromark
added a commit
that referenced
this pull request
May 11, 2026
…374) * Prevent extraParams from leaking between filters. Processor::process() mutated the shared $filterParams map in place when merging in extraParams for a given filter, which meant filters declared later in the chain still saw those values even though they hadn't requested them via their own extraParams config. Build a per-filter view (`$currentParams`) instead so each filter only sees its own merge. Adds a regression test asserting that a filter without extraParams does not see another filter's extra value. * Deprecate null returns from Callback::process(). The Callback filter's process() method returned `?? true`, which silently coerced a missing/void return from the user's callback into isSearch=true. The documented contract is that callbacks must return bool to drive isSearch(). That mismatch caused subtle bugs: a callback that forgets the return statement is reported as having filtered when it may not have. Preserve the historical behaviour for one minor (null still maps to true) but emit a deprecation pointing at the offending callback so users can fix it. The next minor can hard-fail. Updates the existing testProcess case to explicitly return true and adds a new test asserting the deprecation fires when a callback returns null. * Resolve Like escaper per-query and add a Postgres branch. Driver detection in Like::_setEscaper() was a substring suffix match against get_class($driver), which mis-detects custom or subclassed drivers, and the resolved escaper class was written back into the filter's config so a reused filter instance kept the first detected escaper even when the next query ran against a different connection. Switch to `instanceof` against the driver instance, add a Postgres branch (new PostgresEscaper class that currently inherits from DefaultEscaper but lets driver-specific wildcard rules diverge later), and stop caching the result so each call to _setEscaper() resolves afresh. Adds tests for: - Sqlserver detection against a sub-classed driver, - Postgres detection, - the same filter resolving different escapers across two queries backed by different drivers. * Force E_USER_DEPRECATED in callback test for lowest-deps matrix. * Update src/Model/Filter/Callback.php Co-authored-by: ADmad <admad.coder@gmail.com> * Add pluggable escaper resolver for the Like filter. (#375) Builds on the per-query driver resolution from the previous commit in this branch by exposing the driver-to-escaper mapping as a config option. Apps register a custom escaper for a custom or subclassed driver by extending the map at filter setup, without having to subclass the filter: $searchManager->like('title', [ 'escapers' => [ App\Database\Driver\MyMariaDb::class => 'App.MyMariaDb', ], ]); The shipped defaults (Sqlserver -> Search.Sqlserver, Postgres -> Search.Postgres) are merged in via Cake's normal `_defaultConfig` behavior, so users only specify their additions. Entries are evaluated in iteration order; the first instanceof match wins. When nothing matches, `Search.Default` is used. The previous in-line driver match in `_setEscaper()` is replaced by a new protected hook `_resolveEscaperClass(Driver $driver)`, which subclasses can also override for custom resolution logic. Adds two tests: custom escaper picked via map override, and the fallback to Search.Default when the map is explicitly empty. Updates docs/filters-and-examples.md to document `escapers` next to `escaper`, including a worked example and a note about ordering and instanceof matching. --------- Co-authored-by: ADmad <admad.coder@gmail.com>
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
Exposes the Like filter's driver-to-escaper mapping as a config option (
escapers), so apps register custom escapers for custom or subclassed drivers without subclassing the filter. Builds on the per-query driver resolution from #374.API
The shipped defaults (
Sqlserver::class => 'Search.Sqlserver',Postgres::class => 'Search.Postgres') are merged in via Cake's normal_defaultConfigbehavior, so apps only specify their additions. Match isinstanceof-based, so a subclassed driver still resolves correctly. Entries are evaluated in iteration order; the first match wins. When nothing matches,Search.Defaultis used.Implementation
'escapers' => [Sqlserver::class => 'Search.Sqlserver', Postgres::class => 'Search.Postgres']toLike::$_defaultConfig._setEscaper()with a new protected hook_resolveEscaperClass(Driver $driver)so subclasses can also override the entire resolution policy.escaperoption still works as before: if set, it pins a specific escaper regardless of the active driver. The newescapersoption only kicks in whenescaperisnull(the default).Tests
Two new cases:
testCustomEscaperViaEscapersMap— a driver subclass listed inescapersis resolved before the shipped Sqlserver/Postgres defaults, demonstrating the override path.testEscaperFallsBackToDefaultWhenNoMatch— when the map is explicitly empty and no shipped entry matches the active driver,Search.Defaultis used.All gates green locally:
phpunit(158 tests, 340 assertions),phpstanlevel 8 (clean),phpcs(clean).Docs
docs/filters-and-examples.mdupdated alongside the code: theescaperoption's description was simplified and a newescapersentry was added with a worked example, ordering note, and pointer to the EscaperInterface.Dependency
This PR is based on the branch from #374 (extraParams leak, Callback null contract, per-query Like escaper resolution). It will not merge cleanly until #374 is merged or this is rebased onto master after #374 lands.
Recommended order: land #374 first, then rebase this on master and merge.