From 5047814c99671922469baf926bb5f262ef715d50 Mon Sep 17 00:00:00 2001 From: Bizley Date: Tue, 19 Apr 2022 10:03:34 +0200 Subject: [PATCH 1/3] Add guide section for filtering REST collection. --- docs/guide/README.md | 1 + docs/guide/rest-filtering-collections.md | 191 +++++++++++++++++++++++ docs/guide/rest-quick-start.md | 2 +- docs/guide/rest-resources.md | 8 - framework/rest/IndexAction.php | 2 +- 5 files changed, 194 insertions(+), 10 deletions(-) create mode 100644 docs/guide/rest-filtering-collections.md diff --git a/docs/guide/README.md b/docs/guide/README.md index 87edd6d4f73..92fe901fb4c 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -136,6 +136,7 @@ RESTful Web Services * [Quick Start](rest-quick-start.md) * [Resources](rest-resources.md) * [Controllers](rest-controllers.md) +* [Filtering Collections](rest-filtering-collections.md) * [Routing](rest-routing.md) * [Response Formatting](rest-response-formatting.md) * [Authentication](rest-authentication.md) diff --git a/docs/guide/rest-filtering-collections.md b/docs/guide/rest-filtering-collections.md new file mode 100644 index 00000000000..141a58a4248 --- /dev/null +++ b/docs/guide/rest-filtering-collections.md @@ -0,0 +1,191 @@ +Filtering Collections +===================== + +Resource collection can be filtered using [[yii\data\DataFilter]] component since 2.0.13. It allows validating and +building the filter conditions passed via request, and, with the help of its extended version [[yii\data\ActiveDataFilter]], +using them in a format suitable for [[yii\db\QueryInterface::where()]]. + + +## Configuring Data Provider For Filtering + +As mentioned in the [Collections](rest-resources.md#collections) section, we can use +[Data Provider](output-data-providers#data-providers) to output sorted and paginated list of resources. We can also use +it to filter that list. + +```php +$filter = new ActiveDataFilter([ + 'searchModel' => 'app\models\PostSearch' +]); + +$filterCondition = null; +// You may load filters from any source. For example, +// if you prefer JSON in request body, +// use Yii::$app->request->getBodyParams() below: +if ($filter->load(Yii::$app->request->get())) { + $filterCondition = $filter->build(); + if ($filterCondition === false) { + // Serializer would get errors out of it + return $filter; + } +} + +$query = Post::find(); +if ($filterCondition !== null) { + $query->andWhere($filterCondition); +} + +return new ActiveDataProvider([ + 'query' => $query, +]); +``` + +`PostSearch` model serves the purpose of defining which properties and values are allowed for filtering: + +```php +use yii\base\Model; + +class PostSearch extends Model +{ + public $id; + public $title; + + public function rules() + { + return [ + ['id', 'integer'], + ['title', 'string', 'min' => 2, 'max' => 200], + ]; + } +} +``` + +Instead of preparing the standalone model for search rules you can use [[yii\base\DynamicModel]] if you don't need any +special business logic there. + +```php +$filter = new ActiveDataFilter([ + 'searchModel' => (new DynamicModel(['id', 'title'])) + ->addRule(['id'], 'integer') + ->addRule(['title'], 'string', ['min' => 2, 'max' => 200]), +]); +``` + +Defining `searchModel` is required in order to control the filter conditions allowed to the end user. + + +## Filtering Request + +End user is usually expected to provide optional filtering conditions in the request by one or more of the allowed +methods (which should be explicitly stated in the API documentation). For example, if filtering is handled via POST +method using JSON it can be something similar to: + +```json +{ + "filter": { + "id": {"in": [2, 5, 9]}, + "title": {"like": "cheese"} + } +} +``` + +The above conditions are: +- `id` must be either 2, 5, or 9 **AND** +- `title` must contain the word `cheese`. + +The same conditions sent as a part of GET query are: + +``` +?filter[id][in][]=2&filter[id][in][]=5&filter[id][in][]=9&filter[title][like]=cheese +``` + +You can change the default `filter` key word by setting [[yii\data\DataFilter::$filterAttributeName]]. + + +## Filter Control Keywords + +The default list of allowed filter control keywords is as the following: + +| filter control | translates to | +|:--------------:|:-------------:| +| `and` | `AND` | +| `or` | `OR` | +| `not` | `NOT` | +| `lt` | `<` | +| `gt` | `>` | +| `lte` | `<=` | +| `gte` | `>=` | +| `eq` | `=` | +| `neq` | `!=` | +| `in` | `IN` | +| `nin` | `NOT IN` | +| `like` | `LIKE` | + +You can expand that list by expanding option [[yii\data\DataFilter::$filterControls]], for example you could provide +several keywords for the same filter build key, creating multiple aliases like: + +```php +[ + 'eq' => '=', + '=' => '=', + '==' => '=', + '===' => '=', + // ... +] +``` + +Keep in mind that any unspecified keyword will not be recognized as a filter control and will be treated as an attribute +name - you should avoid conflicts between control keywords and attribute names (for example: in case you have control +keyword `like` and an attribute named `like`, specifying condition for such attribute will be impossible). + +> Note: while specifying filter controls take actual data exchange format, which your API uses, in mind. + Make sure each specified control keyword is valid for the format. For example, in XML tag name can start + only with a letter character, thus controls like `>`, `=`, or `$gt` will break the XML schema. + +> Note: When adding new filter control word make sure to check whether you need also to update + [[yii\data\DataFilter::$conditionValidators]] and/or [[yii\data\DataFilter::$operatorTypes]] in order to achieve + expected query result based on the complication of the operator and the way it should work. + + +## Handling The Null Values + +While it is easy to use `null` inside the JSON statement, it is not possible to sent it using the GET query without +confusing the literal `null` with the string `"null"`. Since 2.0.40 you can use [[yii\data\DataFilter::$nullValue]] +option to configure the word that will be used as a replacement for literal `null` (by default it's `"NULL"`). + + +## Aliasing Attributes + +Whether you want to alias the attribute with another name or to filter the joined DB table you can use +[[yii\data\DataFilter::$attributeMap]] to set the map of aliases: + +```php +[ + 'carPart' => 'car_part', // carPart will be used to filter car_part property + 'authorName' => '{{author}}.[[name]]', // authorName will be used to filter name property of joined author table +] +``` + +## Configuring Filters For ActiveController + +[[yii\rest\ActiveController]] comes with the handy set of common REST actions that you can easily configure to use +filters as well through [[yii\rest\IndexAction::$dataFilter]] property. One of the possible ways of doing so is to use +[[yii\rest\ActiveController::actions()]]: + +```php +public function actions() +{ + $actions = parent::actions(); + + $actions['index']['dataFilter'] = [ + 'class' => \yii\data\ActiveDataFilter::class, + 'attributeMap' => [ + 'clockIn' => 'clock_in', + ], + 'searchModel' => (new DynamicModel(['id', 'clockIn']))->addRule(['id', 'clockIn'], 'integer', ['min' => 1]), + ]; + + return $actions; +} +``` + +Now your collection (accessed through `index` action) can be filtered by `id` and `clockIn` properties. diff --git a/docs/guide/rest-quick-start.md b/docs/guide/rest-quick-start.md index b9cd113c327..32ccd6ad68a 100644 --- a/docs/guide/rest-quick-start.md +++ b/docs/guide/rest-quick-start.md @@ -193,7 +193,7 @@ For example, the URL `http://localhost/users?fields=id,email` will only return t Additionally, you can sort collections like `http://localhost/users?sort=email` or `http://localhost/users?sort=-email`. Filtering collections like `http://localhost/users?filter[id]=10` or `http://localhost/users?filter[email][like]=gmail.com` could be implemented using -data filters. See [Resources](rest-resources.md#filtering-collections) section for details. +data filters. See [Filtering Collections](rest-filtering-collections.md) section for details. ## Customizing Pagination and Sorting in the list diff --git a/docs/guide/rest-resources.md b/docs/guide/rest-resources.md index 7b1f72e5ff9..c681e4d2385 100644 --- a/docs/guide/rest-resources.md +++ b/docs/guide/rest-resources.md @@ -251,11 +251,3 @@ will also include the pagination information by the following HTTP headers: Since collection in REST APIs is a data provider, it shares all data provider features i.e. pagination and sorting. An example may be found in the [Quick Start](rest-quick-start.md#trying-it-out) section. - -### Filtering collections - -Since version 2.0.13 Yii provides a facility to filter collections. An example can be found in the -[Quick Start](rest-quick-start.md#trying-it-out) guide. In case you're implementing an endpoint yourself, -filtering could be done as described in -[Filtering Data Providers using Data Filters](output-data-providers.md#filtering-data-providers-using-data-filters) -section of Data Providers guide. diff --git a/framework/rest/IndexAction.php b/framework/rest/IndexAction.php index 7befdbbfbe0..766ed2a6b6b 100644 --- a/framework/rest/IndexAction.php +++ b/framework/rest/IndexAction.php @@ -66,7 +66,7 @@ class IndexAction extends Action public $prepareSearchQuery; /** * @var DataFilter|null data filter to be used for the search filter composition. - * You must setup this field explicitly in order to enable filter processing. + * You must set up this field explicitly in order to enable filter processing. * For example: * * ```php From f65ceb10fc811948f55e9ea727ad41919165aa80 Mon Sep 17 00:00:00 2001 From: Bizley Date: Wed, 20 Apr 2022 08:50:52 +0200 Subject: [PATCH 2/3] Update docs/guide/rest-filtering-collections.md Co-authored-by: Alexey Rogachev --- docs/guide/rest-filtering-collections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/rest-filtering-collections.md b/docs/guide/rest-filtering-collections.md index 141a58a4248..f54df7262c2 100644 --- a/docs/guide/rest-filtering-collections.md +++ b/docs/guide/rest-filtering-collections.md @@ -14,7 +14,7 @@ it to filter that list. ```php $filter = new ActiveDataFilter([ - 'searchModel' => 'app\models\PostSearch' + 'searchModel' => 'app\models\PostSearch', ]); $filterCondition = null; From 74b925d490f90b3500443a46b59f8c2c9a95445d Mon Sep 17 00:00:00 2001 From: Bizley Date: Wed, 20 Apr 2022 08:56:14 +0200 Subject: [PATCH 3/3] Update docs/guide/rest-filtering-collections.md Co-authored-by: Alexey Rogachev --- docs/guide/rest-filtering-collections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/rest-filtering-collections.md b/docs/guide/rest-filtering-collections.md index f54df7262c2..f5934da1d86 100644 --- a/docs/guide/rest-filtering-collections.md +++ b/docs/guide/rest-filtering-collections.md @@ -165,7 +165,7 @@ Whether you want to alias the attribute with another name or to filter the joine ] ``` -## Configuring Filters For ActiveController +## Configuring Filters For `ActiveController` [[yii\rest\ActiveController]] comes with the handy set of common REST actions that you can easily configure to use filters as well through [[yii\rest\IndexAction::$dataFilter]] property. One of the possible ways of doing so is to use