@@ -29,10 +29,12 @@ provided to make it easy to implement filtering on your resource.
2929
3030The easiest way to define a filter is to use the ` CustomFilter ` class, which
3131accepts the name of the filter parameter and a callback to apply the filter to
32- the query. The value received by a filter can be a string or an array, so you
33- will need to handle both:
32+ the query. Without a declared type, the value received by a filter is the raw
33+ query string value: either a string or an array, depending on how the filter was
34+ used in the URL:
3435
3536``` php
37+ use Tobyz\JsonApiServer\Context;
3638use Tobyz\JsonApiServer\Schema\CustomFilter;
3739
3840CustomFilter::make('name', function (
@@ -51,7 +53,110 @@ GET /posts?filter[name]=Toby
5153GET /posts?filter[name][]=Toby&filter[name][]=Franz
5254```
5355
54- ## Boolean Filters
56+ ### Typed Values
57+
58+ Filters receive raw query string values by default. If you want a filter to
59+ receive a validated value, declare its type using the same type system used by
60+ fields and parameters:
61+
62+ ``` php
63+ use Tobyz\JsonApiServer\Schema\CustomFilter;
64+ use Tobyz\JsonApiServer\Schema\Type;
65+
66+ CustomFilter::make('published', function ($query, bool $value) {
67+ $query->where('published', $value);
68+ })->type(Type\Boolean::make());
69+
70+ CustomFilter::make('ids', function ($query, array $ids) {
71+ $query->whereKey($ids);
72+ })->type(
73+ Type\Arr::make()->items(Type\Integer::make())
74+ );
75+ ```
76+
77+ ### Arrays
78+
79+ Array filters accept repeated query parameters, and scalar values are treated as
80+ one-item arrays. If you want a comma-delimited string to be split into array
81+ items, use ` commaSeparated() ` on the array type:
82+
83+ ``` php
84+ CustomFilter::make('ids', function ($query, array $ids) {
85+ $query->whereKey($ids);
86+ })->type(
87+ Type\Arr::make()->items(Type\Integer::make())->commaSeparated()
88+ );
89+ ```
90+
91+ Now all of these requests pass an integer array to the callback:
92+
93+ ``` http
94+ GET /posts?filter[ids]=1
95+ GET /posts?filter[ids]=1,2,3
96+ GET /posts?filter[ids][]=1&filter[ids][]=2
97+ ```
98+
99+ ### Operators
100+
101+ You may also opt into operator syntax:
102+
103+ ``` php
104+ CustomFilter::make('views', function ($query, array $value) {
105+ foreach ($value as $operator => $views) {
106+ $query->where('views', [
107+ 'eq' => '=',
108+ 'gt' => '>',
109+ 'gte' => '>=',
110+ 'lt' => '<',
111+ 'lte' => '<=',
112+ ][$operator], $views);
113+ }
114+ })
115+ ->type(Type\Integer::make())
116+ ->operators(['eq', 'gt', 'gte', 'lt', 'lte']);
117+ ```
118+
119+ ``` http
120+ GET /posts?filter[views]=100
121+ GET /posts?filter[views][gt]=100
122+ ```
123+
124+ The value passed to the callback is an array keyed by operator. When no operator
125+ is specified, the first configured operator is used.
126+
127+ ## Writing Filters
128+
129+ If you need to reuse filter logic across resources, create your own filter
130+ class by extending ` Tobyz\JsonApiServer\Schema\Filter ` and implementing the
131+ ` applyValue ` method:
132+
133+ ``` php
134+ use Tobyz\JsonApiServer\Context;
135+ use Tobyz\JsonApiServer\Schema\Filter;
136+ use Tobyz\JsonApiServer\Schema\Type;
137+
138+ class WhereIn extends Filter
139+ {
140+ public static function make(string $name): static
141+ {
142+ return new static($name);
143+ }
144+
145+ public function __construct(string $name)
146+ {
147+ parent::__construct($name);
148+
149+ $this->type(Type\Arr::make()->items(Type\Str::make()));
150+ }
151+
152+ protected function applyValue(object $query, mixed $value, Context $context): void
153+ {
154+ $query->whereIn($this->name, $value);
155+ }
156+ }
157+ ```
158+
159+ ## Boolean Groups
55160
56161By default it is assumed that each filter applied to the query will be combined
57162with a logical ` AND ` . When a resource implements
@@ -69,8 +174,8 @@ GET /posts
69174 &filter[and][1][or][1][not][status]=archived
70175```
71176
72- In this request every result must be published, and it must also either have
73- more than 100 views or it is not archived.
177+ In this request every result must be published, and it must either have more
178+ than 100 views or not be archived.
74179
75180``` http
76181GET /posts
@@ -83,27 +188,6 @@ This request returns drafts, or posts that are published and have comments. The
83188second example also shows that in certain cases you can omit ` [and] ` groups and
84189numeric indices; sibling filters at the same level default to ` AND ` behaviour.
85190
86- ## Writing Filters
87-
88- To create your own filter class, extend the ` Tobyz\JsonApiServer\Schema\Filter `
89- class and implement the ` apply ` method:
90-
91- ``` php
92- use Tobyz\JsonApiServer\Context;
93- use Tobyz\JsonApiServer\Schema\Filter;
94-
95- class WhereIn extends Filter
96- {
97- public function apply(
98- object $query,
99- string|array $value,
100- Context $context,
101- ): void {
102- $query->whereIn($this->name, $value);
103- }
104- }
105- ```
106-
107191## Visibility
108192
109193If you want to restrict the ability to use a filter, use the ` visible ` or
0 commit comments