Skip to content

Fix the search filter example, which is a PHP fatal error - #603

Merged
imorland merged 1 commit into
flarum:mainfrom
karl-bullock:docs-search-examples
Oct 3, 2026
Merged

imorland merged 1 commit into
flarum:mainfrom
karl-bullock:docs-search-examples

Conversation

@karl-bullock

Copy link
Copy Markdown
Member

The first worked example on this page does not load. Verified by running the exact declaration against the real interface.

CountryFilter is a PHP fatal error

The page declares:

public function filter(SearchState $state, string $filterValue, bool $negate)

FilterInterface declares string|array $value and a void return. PHP does not permit an implementation to narrow a parameter type, so this is a fatal error rather than a stylistic difference:

PHP Fatal error: Declaration of CountryFilter::filter(SearchState $state, string $filterValue, bool $negate)
must be compatible with FilterInterface::filter(SearchState $state, array|string $value, bool $negate): void

Anyone following the page to add a filter, which is what this section is for, gets that and no filter.

The signature now matches the interface, and the body normalises the array form, which is what arrives when the same filter key is supplied more than once. A short caution explains why the signature cannot be trimmed, since string looks like the obvious type to write and the reason it is not is non-obvious.

The mutator registration names a class that does not exist

The example defines OnlySameCountrySearchMutator, then registers OnlySameCountryFilterMutator. Following the page produces a class-not-found error. Registration now uses the name that was defined.

Two "complete" classes were missing imports

AcmeSearcher uses User and Builder in its signature with neither imported, so both resolve into the example's own namespace. AbstractAcmeSearcher uses FilterManager, User, SearchCriteria and SearchResults with none of the four imported. Both now import what they reference, matching core's own UserSearcher.

While there: AcmeSearcher now scopes its base query with whereVisibleTo, as every core searcher does. A searcher that does not scope its query returns rows the actor is not permitted to see, which is a worse failure than a missing feature. I added a line noting that whereVisibleTo comes from ScopeVisibilityTrait and linking Model Visibility, so it is not mistaken for something every model has.

The gambits half needed nothing

Worth recording, since 2.0 moved gambits to the frontend and that is exactly where stale samples tend to survive:

  • BooleanGambit and KeyValueGambit are both exported from flarum/common/query/IGambit as documented, with IGambit as the default export.
  • The UnreadGambit sample matches core's real one (core also defines enabled(), which is optional).
  • The eight members listed under Advanced gambits (type, pattern, toFilter, filterKey, fromFilter, suggestion, predicates, enabled) match the IGambit interface exactly, in order.

Verification

The corrected filter was run against a stand-in of the real interface and loads and executes for both the string and array forms. npx docusaurus build --locale en completes with no errors and no anchors broken on this page. This branch touches only search.md, which no other open PR of mine edits.

The CountryFilter sample on this page, the first thing an extension author copies to add a filter, does not load. It declares:

    public function filter(SearchState $state, string $filterValue, bool $negate)

while FilterInterface declares `string|array $value` and a `void` return. PHP does not permit an implementation to narrow a parameter type, so this is a fatal error, not a style difference: "Declaration of CountryFilter::filter(...) must be compatible with FilterInterface::filter(SearchState $state, array|string $value, bool $negate): void". The signature now matches, the body normalises the array form (which is what arrives when a filter key is repeated), and a note explains why the signature cannot be trimmed.

The mutator registration referred to OnlySameCountryFilterMutator while the class above it is OnlySameCountrySearchMutator, so following the example produced a class-not-found error. Registration now uses the name that was defined.

Two classes presented as complete also lacked imports for types in their own signatures. AcmeSearcher used User and Builder with neither imported; AbstractAcmeSearcher used FilterManager, User, SearchCriteria and SearchResults with none of the four imported. Both now import what they reference, matching core's own UserSearcher.

AcmeSearcher additionally scopes its base query with whereVisibleTo, as every core searcher does, since a searcher that does not scope returns rows the actor may not see. A line notes that this comes from ScopeVisibilityTrait and links Model Visibility, so it is not mistaken for something every model has.

The gambits half of the page was checked and needed nothing: BooleanGambit and KeyValueGambit are exported where documented, the UnreadGambit sample matches core's real one, and the eight IGambit members listed under advanced gambits match the interface exactly.
@imorland
imorland merged commit e1c985b into flarum:main Oct 3, 2026
1 check passed
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.

2 participants