rest-quick-start.md 36.7 KB
Newer Older
Qiang Xue committed
1 2 3
Implementing RESTful Web Service APIs
=====================================

4
> Note: This section is under development.
Qiang Xue committed
5

Qiang Xue committed
6 7 8 9
Yii provides a whole set of tools to greatly simplify the task of implementing RESTful Web Service APIs.
In particular, Yii provides support for the following aspects regarding RESTful APIs:

* Quick prototyping with support for common APIs for ActiveRecord;
10
* Response format (supporting JSON and XML by default) negotiation;
Qiang Xue committed
11 12 13 14
* Customizable object serialization with support for selectable output fields;
* Proper formatting of collection data and validation errors;
* Efficient routing with proper HTTP verb check;
* Support `OPTIONS` and `HEAD` verbs;
15
* Authentication;
Qiang Xue committed
16
* Authorization;
Qiang Xue committed
17
* Support for HATEOAS;
18
* Caching via `yii\filters\HttpCache`;
19
* Rate limiting;
Qiang Xue committed
20 21 22 23 24 25 26 27 28 29 30 31
* Searching and filtering: TBD
* Testing: TBD
* Automatic generation of API documentation: TBD


A Quick Example
---------------

Let's use a quick example to show how to build a set of RESTful APIs using Yii.
Assume you want to expose the user data via RESTful APIs. The user data are stored in the user DB table,
and you have already created the ActiveRecord class `app\models\User` to access the user data.

Qiang Xue committed
32
First, create a controller class `app\controllers\UserController` as follows,
Qiang Xue committed
33 34 35 36 37 38 39 40

```php
namespace app\controllers;

use yii\rest\ActiveController;

class UserController extends ActiveController
{
Qiang Xue committed
41
    public $modelClass = 'app\models\User';
Qiang Xue committed
42 43 44
}
```

Qiang Xue committed
45
Then, modify the configuration about the `urlManager` component in your application configuration:
Qiang Xue committed
46 47 48 49 50 51 52

```php
'urlManager' => [
    'enablePrettyUrl' => true,
    'enableStrictParsing' => true,
    'showScriptName' => false,
    'rules' => [
Qiang Xue committed
53
        ['class' => 'yii\rest\UrlRule', 'controller' => 'user'],
Qiang Xue committed
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
    ],
]
```

With the above minimal amount of effort, you have already finished your task of creating the RESTful APIs
for accessing the user data. The APIs you have created include:

* `GET /users`: list all users page by page;
* `HEAD /users`: show the overview information of user listing;
* `POST /users`: create a new user;
* `GET /users/123`: return the details of the user 123;
* `HEAD /users/123`: show the overview information of user 123;
* `PATCH /users/123` and `PUT /users/123`: update the user 123;
* `DELETE /users/123`: delete the user 123;
* `OPTIONS /users`: show the supported verbs regarding endpoint `/users`;
* `OPTIONS /users/123`: show the supported verbs regarding endpoint `/users/123`.

You may access your APIs with the `curl` command like the following,

```
Qiang Xue committed
74
curl -i -H "Accept:application/json" "http://localhost/users"
Qiang Xue committed
75 76 77 78 79 80 81 82 83 84 85 86 87
```

which may give the following output:

```
HTTP/1.1 200 OK
Date: Sun, 02 Mar 2014 05:31:43 GMT
Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y
X-Powered-By: PHP/5.4.20
X-Pagination-Total-Count: 1000
X-Pagination-Page-Count: 50
X-Pagination-Current-Page: 1
X-Pagination-Per-Page: 20
Qiang Xue committed
88 89 90
Link: <http://localhost/users?page=1>; rel=self, 
      <http://localhost/users?page=2>; rel=next, 
      <http://localhost/users?page=50>; rel=last
Qiang Xue committed
91 92 93
Transfer-Encoding: chunked
Content-Type: application/json; charset=UTF-8

Qiang Xue committed
94 95 96 97 98 99 100 101 102 103 104
[
    {
        "id": 1,
        ...
    },
    {
        "id": 2,
        ...
    },
    ...
]
Qiang Xue committed
105 106 107
```

Try changing the acceptable content type to be `application/xml`, and you will see the result
Qiang Xue committed
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
is returned in XML format:

```
curl -i -H "Accept:application/xml" "http://localhost/users"
```

```
HTTP/1.1 200 OK
Date: Sun, 02 Mar 2014 05:31:43 GMT
Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y
X-Powered-By: PHP/5.4.20
X-Pagination-Total-Count: 1000
X-Pagination-Page-Count: 50
X-Pagination-Current-Page: 1
X-Pagination-Per-Page: 20
Qiang Xue committed
123 124 125
Link: <http://localhost/users?page=1>; rel=self, 
      <http://localhost/users?page=2>; rel=next, 
      <http://localhost/users?page=50>; rel=last
Qiang Xue committed
126 127 128 129
Transfer-Encoding: chunked
Content-Type: application/xml

<?xml version="1.0" encoding="UTF-8"?>
Qiang Xue committed
130 131 132 133 134 135 136 137 138 139 140
<response>
    <item>
        <id>1</id>
        ...
    </item>
    <item>
        <id>2</id>
        ...
    </item>
    ...
</response>
Qiang Xue committed
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
```

> Tip: You may also access your APIs via Web browser by entering the URL `http://localhost/users`.

As you can see, in the response headers, there are information about the total count, page count, etc.
There are also links that allow you to navigate to other pages of data. For example, `http://localhost/users?page=2`
would give you the next page of the user data.

Using the `fields` and `expand` parameters, you may also request to return a subset of the fields in the result.
For example, the URL `http://localhost/users?fields=id,email` will only return the `id` and `email` fields in the result:


> Info: You may have noticed that the result of `http://localhost/users` includes some sensitive fields,
> such as `password_hash`, `auth_key`. You certainly do not want these to appear in your API result.
> You can/should filter out these fields as described in the following sections.


In the following sections, we will explain in more details about implementing RESTful APIs.


General Architecture
--------------------

Using the Yii RESTful API framework, you implement an API endpoint in terms of a controller action, and you use
a controller to organize the actions that implement the endpoints for a single type of resource.

Resources are represented as data models which extend from the [[yii\base\Model]] class.
If you are working with databases (relational or NoSQL), it is recommended you use ActiveRecord to represent resources.

You may use [[yii\rest\UrlRule]] to simplify the routing to your API endpoints.
Qiang Xue committed
171

Qiang Xue committed
172 173
While not required, it is recommended that you develop your RESTful APIs as an application, separated from
your Web front end and back end.
Qiang Xue committed
174

Qiang Xue committed
175

Qiang Xue committed
176 177
Creating Resource Classes
-------------------------
Qiang Xue committed
178

Qiang Xue committed
179 180 181
RESTful APIs are all about accessing and manipulating resources. In Yii, a resource can be an object of any class.
However, if your resource classes extend from [[yii\base\Model]] or its child classes (e.g. [[yii\db\ActiveRecord]]),
you may enjoy the following benefits:
Qiang Xue committed
182

Qiang Xue committed
183 184 185
* Input data validation;
* Query, create, update and delete data, if extending from [[yii\db\ActiveRecord]];
* Customizable data formatting (to be explained in the next section).
Qiang Xue committed
186 187 188 189 190 191


Formatting Response Data
------------------------

By default, Yii supports two response formats for RESTful APIs: JSON and XML. If you want to support
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
other formats, you should configure the `contentNegotiator` behavior in your REST controller classes as follows,


```php
use yii\helpers\ArrayHelper;

public function behaviors()
{
    return ArrayHelper::merge(parent::behaviors(), [
        'contentNegotiator' => [
            'formats' => [
                // ... other supported formats ...
            ],
        ],
    ]);
}
```
Qiang Xue committed
209

Qiang Xue committed
210
Formatting response data in general involves two steps:
Qiang Xue committed
211 212 213 214 215 216 217 218

1. The objects (including embedded objects) in the response data are converted into arrays by [[yii\rest\Serializer]];
2. The array data are converted into different formats (e.g. JSON, XML) by [[yii\web\ResponseFormatterInterface|response formatters]].

Step 2 is usually a very mechanical data conversion process and can be well handled by the built-in response formatters.
Step 1 involves some major development effort as explained below.

When the [[yii\rest\Serializer|serializer]] converts an object into an array, it will call the `toArray()` method
219
of the object if it implements [[yii\base\Arrayable]]. If an object does not implement this interface,
Qiang Xue committed
220
its public properties will be returned instead.
Qiang Xue committed
221 222

For classes extending from [[yii\base\Model]] or [[yii\db\ActiveRecord]], besides directly overriding `toArray()`,
Qiang Xue committed
223
you may also override the `fields()` method and/or the `extraFields()` method to customize the data being returned.
Qiang Xue committed
224

Qiang Xue committed
225 226 227 228 229 230 231
The method [[yii\base\Model::fields()]] declares a set of *fields* that should be included in the result.
A field is simply a named data item. In a result array, the array keys are the field names, and the array values
are the corresponding field values. The default implementation of [[yii\base\Model::fields()]] is to return
all attributes of a model as the output fields; for [[yii\db\ActiveRecord::fields()]], by default it will return
the names of the attributes whose values have been populated into the object.

You can override the `fields()` method to add, remove, rename or redefine fields. For example,
Qiang Xue committed
232 233

```php
Qiang Xue committed
234 235 236
// explicitly list every field, best used when you want to make sure the changes
// in your DB table or model attributes do not cause your field changes (to keep API backward compatibility).
public function fields()
Qiang Xue committed
237
{
Qiang Xue committed
238 239 240 241 242 243 244 245 246 247
    return [
        // field name is the same as the attribute name
        'id',
        // field name is "email", the corresponding attribute name is "email_address"
        'email' => 'email_address',
        // field name is "name", its value is defined by a PHP callback
        'name' => function () {
            return $this->first_name . ' ' . $this->last_name;
        },
    ];
Qiang Xue committed
248
}
Qiang Xue committed
249

Qiang Xue committed
250 251 252 253
// filter out some fields, best used when you want to inherit the parent implementation
// and blacklist some sensitive fields.
public function fields()
{
Qiang Xue committed
254
    $fields = parent::fields();
Qiang Xue committed
255

Qiang Xue committed
256 257
    // remove fields that contain sensitive information
    unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);
Qiang Xue committed
258

Qiang Xue committed
259
    return $fields;
Qiang Xue committed
260 261 262
}
```

Qiang Xue committed
263 264 265
The return value of `fields()` should be an array. The array keys are the field names, and the array values
are the corresponding field definitions which can be either property/attribute names or anonymous functions
returning the corresponding field values.
Qiang Xue committed
266

Qiang Xue committed
267 268 269 270
> Warning: Because by default all attributes of a model will be included in the API result, you should
> examine your data to make sure they do not contain sensitive information. If there is such information,
> you should override `fields()` or `toArray()` to filter them out. In the above example, we choose
> to filter out `auth_key`, `password_hash` and `password_reset_token`.
271

Qiang Xue committed
272 273
You may use the `fields` query parameter to specify which fields in `fields()` should be included in the result.
If this parameter is not specified, all fields returned by `fields()` will be returned.
274

Qiang Xue committed
275
The method [[yii\base\Model::extraFields()]] is very similar to [[yii\base\Model::fields()]].
Qiang Xue committed
276 277
The difference between these methods is that the latter declares the fields that should be returned by default,
while the former declares the fields that should only be returned when the user specifies them in the `expand` query parameter.
278

Qiang Xue committed
279
For example, `http://localhost/users?fields=id,email&expand=profile` may return the following JSON data:
280

Qiang Xue committed
281 282
```php
[
Qiang Xue committed
283 284 285 286 287 288 289 290 291
    {
        "id": 100,
        "email": "100@example.com",
        "profile": {
            "id": 100,
            "age": 30,
        }
    },
    ...
Qiang Xue committed
292 293
]
```
294

Qiang Xue committed
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
You may wonder who triggers the conversion from objects to arrays when an action returns an object or object collection.
The answer is that this is done by [[yii\rest\Controller::serializer]] in the [[yii\base\Controller::afterAction()|afterAction()]]
method. By default, [[yii\rest\Serializer]] is used as the serializer that can recognize resource objects extending from
[[yii\base\Model]] and collection objects implementing [[yii\data\DataProviderInterface]]. The serializer
will call the `toArray()` method of these objects and pass the `fields` and `expand` user parameters to the method.
If there are any embedded objects, they will also be converted into arrays recursively.

If all your resource objects are of [[yii\base\Model]] or its child classes, such as [[yii\db\ActiveRecord]],
and you only use [[yii\data\DataProviderInterface]] as resource collections, the default data formatting
implementation should work very well. However, if you want to introduce some new resource classes that do not
extend from [[yii\base\Model]], or if you want to use some new collection classes, you will need to
customize the serializer class and configure [[yii\rest\Controller::serializer]] to use it.
You new resource classes may use the trait [[yii\base\ArrayableTrait]] to support selective field output
as explained above.


Qiang Xue committed
311 312 313 314 315 316 317
### Pagination

For API endpoints about resource collections, pagination is supported out-of-box if you use 
[[yii\data\DataProviderInterface|data provider]] to serve the response data. In particular,
through query parameters `page` and `per-page`, an API consumer may specify which page of data
to return and how many data items should be included in each page. The corresponding response
will include the pagination information by the following HTTP headers (please also refer to the first example
318
in this section):
Qiang Xue committed
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388

* `X-Pagination-Total-Count`: The total number of data items;
* `X-Pagination-Page-Count`: The number of pages;
* `X-Pagination-Current-Page`: The current page (1-based);
* `X-Pagination-Per-Page`: The number of data items in each page;
* `Link`: A set of navigational links allowing client to traverse the data page by page.

The response body will contain a list of data items in the requested page.

Sometimes, you may want to help simplify the client development work by including pagination information
directly in the response body. To do so, configure the [[yii\rest\Serializer::collectionEnvelope]] property
as follows:

```php
use yii\rest\ActiveController;

class UserController extends ActiveController
{
    public $modelClass = 'app\models\User';
    public $serializer = [
        'class' => 'yii\rest\Serializer',
        'collectionEnvelope' => 'items',
    ];
}
```

You may then get the following response for request `http://localhost/users`:

```
HTTP/1.1 200 OK
Date: Sun, 02 Mar 2014 05:31:43 GMT
Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y
X-Powered-By: PHP/5.4.20
X-Pagination-Total-Count: 1000
X-Pagination-Page-Count: 50
X-Pagination-Current-Page: 1
X-Pagination-Per-Page: 20
Link: <http://localhost/users?page=1>; rel=self, 
      <http://localhost/users?page=2>; rel=next, 
      <http://localhost/users?page=50>; rel=last
Transfer-Encoding: chunked
Content-Type: application/json; charset=UTF-8

{
    "items": [
        {
            "id": 1,
            ...
        },
        {
            "id": 2,
            ...
        },
        ...
    ],
    "_links": {
        "self": "http://localhost/users?page=1", 
        "next": "http://localhost/users?page=2", 
        "last": "http://localhost/users?page=50"
    },
    "_meta": {
        "totalCount": 1000,
        "pageCount": 50,
        "currentPage": 1,
        "perPage": 20
    }
}
```


Qiang Xue committed
389 390
### HATEOAS Support

391 392 393 394 395 396 397 398 399 400 401
[HATEOAS](http://en.wikipedia.org/wiki/HATEOAS), an abbreviation for Hypermedia as the Engine of Application State,
promotes that RESTful APIs should return information that allow clients to discover actions supported for the returned
resources. The key of HATEOAS is to return a set of hyperlinks with relation information when resource data are served
by APIs.

You may let your model classes to implement the [[yii\web\Linkable]] interface to support HATEOAS. By implementing
this interface, a class is required to return a list of [[yii\web\Link|links]]. Typically, you should return at least
the `self` link, for example:

```php
use yii\db\ActiveRecord;
Qiang Xue committed
402
use yii\web\Link;
403 404 405 406 407
use yii\web\Linkable;
use yii\helpers\Url;

class User extends ActiveRecord implements Linkable
{
Qiang Xue committed
408 409 410
    public function getLinks()
    {
        return [
Qiang Xue committed
411
            Link::REL_SELF => Url::to(['user', 'id' => $this->id], true),
Qiang Xue committed
412 413
        ];
    }
414 415 416 417 418 419 420 421
}
```

When a `User` object is returned in a response, it will contain a `_links` element representing the links related
to the user, for example,

```
{
Qiang Xue committed
422 423 424 425 426 427
    "id": 100,
    "email": "user@example.com",
    ...,
    "_links" => [
        "self": "https://example.com/users/100"
    ]
428 429
}
```
Qiang Xue committed
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473


Creating Controllers and Actions
--------------------------------

So you have the resource data and you have specified how the resource data should be formatted, the next thing
to do is to create controller actions to expose the resource data to end users.

Yii provides two base controller classes to simplify your work of creating RESTful actions:
[[yii\rest\Controller]] and [[yii\rest\ActiveController]]. The difference between these two controllers
is that the latter provides a default set of actions that are specified designed to deal with
resources represented as ActiveRecord. So if you are using ActiveRecord and you are comfortable with
the provided built-in actions, you may consider creating your controller class by extending from
the latter. Otherwise, extending from [[yii\rest\Controller]] will allow you to develop actions
from scratch.

Both [[yii\rest\Controller]] and [[yii\rest\ActiveController]] provide the following features which will
be described in detail in the next few sections:

* Response format negotiation;
* API version negotiation;
* HTTP method validation;
* User authentication;
* Rate limiting.

[[yii\rest\ActiveController]] in addition provides the following features specifically for working
with ActiveRecord:

* A set of commonly used actions: `index`, `view`, `create`, `update`, `delete`, `options`;
* User authorization in regard to the requested action and resource.

When creating a new controller class, a convention in naming the controller class is to use
the type name of the resource and use singular form. For example, to serve user information,
the controller may be named as `UserController`.

Creating a new action is similar to creating an action for a Web application. The only difference
is that instead of rendering the result using a view by calling the `render()` method, for RESTful actions
you directly return the data. The [[yii\rest\Controller::serializer|serializer]] and the
[[yii\web\Response|response object]] will handle the conversion from the original data to the requested
format. For example,

```php
public function actionSearch($keyword)
{
Qiang Xue committed
474 475
    $result = SolrService::search($keyword);
    return $result;
Qiang Xue committed
476 477 478 479 480 481 482 483 484 485 486 487 488
}
```

If your controller class extends from [[yii\rest\ActiveController]], you should set
its [[yii\rest\ActiveController::modelClass||modelClass]] property to be the name of the resource class
that you plan to serve through this controller. The class must implement [[yii\db\ActiveRecordInterface]].

With [[yii\rest\ActiveController]], you may want to disable some of the built-in actions or customize them.
To do so, override the `actions()` method like the following:

```php
public function actions()
{
Qiang Xue committed
489
    $actions = parent::actions();
Qiang Xue committed
490

Qiang Xue committed
491 492
    // disable the "delete" and "create" actions
    unset($actions['delete'], $actions['create']);
Qiang Xue committed
493

Qiang Xue committed
494 495
    // customize the data provider preparation with the "prepareDataProvider()" method
    $actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider'];
Qiang Xue committed
496

Qiang Xue committed
497
    return $actions;
Qiang Xue committed
498 499 500 501
}

public function prepareDataProvider()
{
Qiang Xue committed
502
    // prepare and return a data provider for the "index" action
Qiang Xue committed
503 504 505 506 507 508 509 510 511 512 513 514
}
```

The following list summarizes the built-in actions supported by [[yii\rest\ActiveController]]:

* [[yii\rest\IndexAction|index]]: list resources page by page;
* [[yii\rest\ViewAction|view]]: return the details of a specified resource;
* [[yii\rest\CreateAction|create]]: create a new resource;
* [[yii\rest\UpdateAction|update]]: update an existing resource;
* [[yii\rest\DeleteAction|delete]]: delete the specified resource;
* [[yii\rest\OptionsAction|options]]: return the supported HTTP methods.

515 516 517 518

Routing
-------

Qiang Xue committed
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
With resource and controller classes ready, you can access the resources using the URL like
`http://localhost/index.php?r=user/create`. As you can see, the format of the URL is the same as that
for Web applications.

In practice, you usually want to enable pretty URLs and take advantage of HTTP verbs.
For example, a request `POST /users` would mean accessing the `user/create` action.
This can be done easily by configuring the `urlManager` application component in the application
configuration like the following:

```php
'urlManager' => [
    'enablePrettyUrl' => true,
    'enableStrictParsing' => true,
    'showScriptName' => false,
    'rules' => [
Qiang Xue committed
534
        ['class' => 'yii\rest\UrlRule', 'controller' => 'user'],
Qiang Xue committed
535 536 537 538 539 540 541 542 543 544 545
    ],
]
```

Compared to the URL management for Web applications, the main new thing above is the use of
[[yii\rest\UrlRule]] for routing RESTful API requests. This special URL rule class will
create a whole set of child URL rules to support routing and URL creation for the specified controller(s).
For example, the above code is roughly equivalent to the following rules:

```php
[
Qiang Xue committed
546 547 548 549 550 551 552
    'PUT,PATCH users/<id>' => 'user/update',
    'DELETE users/<id>' => 'user/delete',
    'GET,HEAD users/<id>' => 'user/view',
    'POST users' => 'user/create',
    'GET,HEAD users' => 'user/index',
    'users/<id>' => 'user/options',
    'users' => 'user/options',
Qiang Xue committed
553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
]
```

And the following API endpoints are supported by this rule:

* `GET /users`: list all users page by page;
* `HEAD /users`: show the overview information of user listing;
* `POST /users`: create a new user;
* `GET /users/123`: return the details of the user 123;
* `HEAD /users/123`: show the overview information of user 123;
* `PATCH /users/123` and `PUT /users/123`: update the user 123;
* `DELETE /users/123`: delete the user 123;
* `OPTIONS /users`: show the supported verbs regarding endpoint `/users`;
* `OPTIONS /users/123`: show the supported verbs regarding endpoint `/users/123`.

You may configure the `only` and `except` options to explicitly list which actions to support or which
actions should be disabled, respectively. For example,

```php
[
Qiang Xue committed
573 574 575
    'class' => 'yii\rest\UrlRule',
    'controller' => 'user',
    'except' => ['delete', 'create', 'update'],
Qiang Xue committed
576 577 578
],
```

Qiang Xue committed
579 580
You may also configure `patterns` or `extraPatterns` to redefine existing patterns or add new patterns supported by this rule.
For example, to support a new action `search` by the endpoint `GET /users/search`, configure the `extraPatterns` option as follows,
Qiang Xue committed
581 582 583

```php
[
Qiang Xue committed
584 585
    'class' => 'yii\rest\UrlRule',
    'controller' => 'user',
Qiang Xue committed
586
    'extraPatterns' => [
Qiang Xue committed
587 588
        'GET search' => 'search',
    ],
Qiang Xue committed
589 590 591 592 593 594 595
```

You may have noticed that the controller ID `user` appears in plural form as `users` in the endpoints.
This is because [[yii\rest\UrlRule]] automatically pluralizes controller IDs for them to use in endpoints.
You may disable this behavior by setting [[yii\rest\UrlRule::pluralize]] to be false, or if you want
to use some special names you may configure the [[yii\rest\UrlRule::controller]] property.

596 597 598 599

Authentication
--------------

Qiang Xue committed
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619
Unlike Web applications, RESTful APIs should be stateless, which means sessions or cookies should not
be used. Therefore, each request should come with some sort of authentication credentials because
the user authentication status may not be maintained by sessions or cookies. A common practice is
to send a secret access token with each request to authenticate the user. Since an access token
can be used to uniquely identify and authenticate a user, **the API requests should always be sent
via HTTPS to prevent from man-in-the-middle (MitM) attacks**.

There are different ways to send an access token:

* [HTTP Basic Auth](http://en.wikipedia.org/wiki/Basic_access_authentication): the access token
  is sent as the username. This is should only be used when an access token can be safely stored
  on the API consumer side. For example, the API consumer is a program running on a server.
* Query parameter: the access token is sent as a query parameter in the API URL, e.g.,
  `https://example.com/users?access-token=xxxxxxxx`. Because most Web servers will keep query
  parameters in server logs, this approach should be mainly used to serve `JSONP` requests which
  cannot use HTTP headers to send access tokens.
* [OAuth 2](http://oauth.net/2/): the access token is obtained by the consumer from an authorization
  server and sent to the API server via [HTTP Bearer Tokens](http://tools.ietf.org/html/rfc6750),
  according to the OAuth2 protocol.

620
Yii supports all of the above authentication methods. You can also easily create new authentication methods.
Qiang Xue committed
621 622 623

To enable authentication for your APIs, do the following two steps:

624
1. Specify which authentication methods you plan to use by configuring the `authenticator` behavior
625
   in your REST controller classes.
Qiang Xue committed
626 627
2. Implement [[yii\web\IdentityInterface::findIdentityByAccessToken()]] in your [[yii\web\User::identityClass|user identity class]].

Qiang Xue committed
628 629

For example, to use HTTP Basic Auth, you may configure `authenticator` as follows,
Qiang Xue committed
630 631

```php
Qiang Xue committed
632 633 634
use yii\helpers\ArrayHelper;
use yii\filters\auth\HttpBasicAuth;

635
public function behaviors()
Qiang Xue committed
636
{
Qiang Xue committed
637
    return ArrayHelper::merge(parent::behaviors(), [
638
        'authenticator' => [
Qiang Xue committed
639
            'class' => HttpBasicAuth::className(),
640 641
        ],
    ]);
Qiang Xue committed
642 643 644
}
```

Qiang Xue committed
645
If you want to support all three authentication methods explained above, you can use `CompositeAuth` like the following,
646 647

```php
Qiang Xue committed
648 649 650 651 652 653
use yii\helpers\ArrayHelper;
use yii\filters\auth\CompositeAuth;
use yii\filters\auth\HttpBasicAuth;
use yii\filters\auth\HttpBearerAuth;
use yii\filters\auth\QueryParamAuth;

654 655
public function behaviors()
{
Qiang Xue committed
656
    return ArrayHelper::merge(parent::behaviors(), [
657
        'authenticator' => [
Qiang Xue committed
658 659 660 661 662 663
            'class' => CompositeAuth::className(),
            'authMethods' => [
                HttpBasicAuth::className(),
                HttpBearerAuth::className(),
                QueryParamAuth::className(),
            ],
664 665 666 667 668
        ],
    ]);
}
```

Qiang Xue committed
669 670
Each element in `authMethods` should be an auth method class name or a configuration array.

671

Qiang Xue committed
672 673 674 675 676 677 678 679 680 681
Implementation of `findIdentityByAccessToken()` is application specific. For example, in simple scenarios
when each user can only have one access token, you may store the access token in an `access_token` column
in the user table. The method can then be readily implemented in the `User` class as follows,

```php
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;

class User extends ActiveRecord implements IdentityInterface
{
682
    public static function findIdentityByAccessToken($token, $type = null)
Qiang Xue committed
683
    {
Alexander Makarov committed
684
        return static::findOne(['access_token' => $token]);
Qiang Xue committed
685
    }
Qiang Xue committed
686 687 688 689 690 691 692 693 694 695 696 697
}
```

After authentication is enabled as described above, for every API request, the requested controller
will try to authenticate the user in its `beforeAction()` step.

If authentication succeeds, the controller will perform other checks (such as rate limiting, authorization)
and then run the action. The authenticated user identity information can be retrieved via `Yii::$app->user->identity`.

If authentication fails, a response with HTTP status 401 will be sent back together with other appropriate headers
(such as a `WWW-Authenticate` header for HTTP Basic Auth).

698 699 700 701

Authorization
-------------

Qiang Xue committed
702 703
After a user is authenticated, you probably want to check if he has the permission to perform the requested
action for the requested resource. This process is called *authorization* which is covered in detail in
704
the [Authorization section](authorization.md).
705

Qiang Xue committed
706
You may use the Role-Based Access Control (RBAC) component to implementation authorization.
707

Qiang Xue committed
708 709 710
To simplify the authorization check, you may also override the [[yii\rest\Controller::checkAccess()]] method
and then call this method in places where authorization is needed. By default, the built-in actions provided
by [[yii\rest\ActiveController]] will call this method when they are about to run.
711

Qiang Xue committed
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
```php
/**
 * Checks the privilege of the current user.
 *
 * This method should be overridden to check whether the current user has the privilege
 * to run the specified action against the specified data model.
 * If the user does not have access, a [[ForbiddenHttpException]] should be thrown.
 *
 * @param string $action the ID of the action to be executed
 * @param \yii\base\Model $model the model to be accessed. If null, it means no specific model is being accessed.
 * @param array $params additional parameters
 * @throws ForbiddenHttpException if the user does not have access
 */
public function checkAccess($action, $model = null, $params = [])
{
}
```
729 730 731 732 733


Rate Limiting
-------------

Qiang Xue committed
734 735 736 737
To prevent abuse, you should consider adding rate limiting to your APIs. For example, you may limit the API usage
of each user to be at most 100 API calls within a period of 10 minutes. If too many requests are received from a user
within the period of the time, a response with status code 429 (meaning Too Many Requests) should be returned.

738
To enable rate limiting, the [[yii\web\User::identityClass|user identity class]] should implement [[yii\filters\RateLimitInterface]].
Qiang Xue committed
739 740 741 742 743 744 745 746 747 748 749 750 751
This interface requires implementation of the following three methods:

* `getRateLimit()`: returns the maximum number of allowed requests and the time period, e.g., `[100, 600]` means
  at most 100 API calls within 600 seconds.
* `loadAllowance()`: returns the number of remaining requests allowed and the corresponding UNIX timestamp
  when the rate limit is checked last time.
* `saveAllowance()`: saves the number of remaining requests allowed and the current UNIX timestamp.

You may use two columns in the user table to record the allowance and timestamp information.
And `loadAllowance()` and `saveAllowance()` can then be implementation by reading and saving the values
of the two columns corresponding to the current authenticated user. To improve performance, you may also
consider storing these information in cache or some NoSQL storage.

752 753 754 755
Once the identity class implements the required interface, Yii will automatically use [[yii\filters\RateLimiter]]
configured as an action filter for [[yii\rest\Controller]] to perform rate limiting check. The rate limiter
will thrown a [[yii\web\TooManyRequestsHttpException]] if rate limit is exceeded. You may configure the rate limiter
as follows in your REST controller classes,
Qiang Xue committed
756

757
```php
Qiang Xue committed
758 759 760
use yii\helpers\ArrayHelper;
use yii\filters\RateLimiter;

761 762
public function behaviors()
{
Qiang Xue committed
763
    return ArrayHelper::merge(parent::behaviors(), [
764
        'rateLimiter' => [
Qiang Xue committed
765
            'class' => RateLimiter::className(),
766 767 768 769 770 771 772
            'enableRateLimitHeaders' => false,
        ],
    ]);
}
```

When rate limiting is enabled, by default every response will be sent with the following HTTP headers containing
Qiang Xue committed
773 774 775 776 777 778
the current rate limiting information:

* `X-Rate-Limit-Limit`: The maximum number of requests allowed with a time period;
* `X-Rate-Limit-Remaining`: The number of remaining requests in the current time period;
* `X-Rate-Limit-Reset`: The number of seconds to wait in order to get the maximum number of allowed requests.

779 780 781
You may disable these headers by configuring [[yii\filters\RateLimiter::enableRateLimitHeaders]] to be false,
like shown in the above code example.

Qiang Xue committed
782

Qiang Xue committed
783 784
Error Handling
--------------
Qiang Xue committed
785

Qiang Xue committed
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811
When handling a RESTful API request, if there is an error in the user request or if something unexpected
happens on the server, you may simply throw an exception to notify the user something wrong happened. 
If you can identify the cause of the error (e.g. the requested resource does not exist), you should 
consider throwing an exception with a proper HTTP status code (e.g. [[yii\web\NotFoundHttpException]] 
representing a 404 HTTP status code). Yii will send the response with the corresponding HTTP status
code and text. It will also include in the response body the serialized representation of the 
exception. For example,

```
HTTP/1.1 404 Not Found
Date: Sun, 02 Mar 2014 05:31:43 GMT
Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y
Transfer-Encoding: chunked
Content-Type: application/json; charset=UTF-8

{
    "type": "yii\\web\\NotFoundHttpException",
    "name": "Not Found Exception",
    "message": "The requested resource was not found.",
    "code": 0,
    "status": 404
}
```

The following list summarizes the HTTP status code that are used by the Yii REST framework:

Qiang Xue committed
812 813 814 815 816 817 818
* `200`: OK. Everything worked as expected.
* `201`: A resource was successfully created in response to a `POST` request. The `Location` header
   contains the URL pointing to the newly created resource.
* `204`: The request is handled successfully and the response contains no body content (like a `DELETE` request).
* `304`: Resource was not modified. You can use the cached version.
* `400`: Bad request. This could be caused by various reasons from the user side, such as invalid JSON
   data in the request body, invalid action parameters, etc.
Qiang Xue committed
819
* `401`: Authentication failed.
Qiang Xue committed
820 821 822 823 824 825 826 827 828
* `403`: The authenticated user is not allowed to access the specified API endpoint.
* `404`: The requested resource does not exist.
* `405`: Method not allowed. Please check the `Allow` header for allowed HTTP methods.
* `415`: Unsupported media type. The requested content type or version number is invalid.
* `422`: Data validation failed (in response to a `POST` request, for example). Please check the response body for detailed error messages.
* `429`: Too many requests. The request is rejected due to rate limiting.
* `500`: Internal server error. This could be caused by internal program errors.


829 830
API Versioning
--------------
Qiang Xue committed
831

832 833 834
Your APIs should be versioned. Unlike Web applications which you have full control on both client side and server side
code, for APIs you usually do not have control of the client code that consumes the APIs. Therefore, backward
compatibility (BC) of the APIs should be maintained whenever possible, and if some BC-breaking changes must be
835
introduced to the APIs, you should bump up the version number. You may refer to [Semantic Versioning](http://semver.org/)
836 837 838 839 840 841 842 843 844 845 846 847 848 849 850
for more information about designing the version numbers of your APIs.

Regarding how to implement API versioning, a common practice is to embed the version number in the API URLs.
For example, `http://example.com/v1/users` stands for `/users` API of version 1. Another method of API versioning
which gains momentum recently is to put version numbers in the HTTP request headers, typically through the `Accept` header,
like the following:

```
// via a parameter
Accept: application/json; version=v1
// via a vendor content type
Accept: application/vnd.company.myapp-v1+json
```

Both methods have pros and cons, and there are a lot of debates about them. Below we describe a practical strategy
851
of API versioning that is kind of a mix of these two methods:
852 853 854 855 856 857 858

* Put each major version of API implementation in a separate module whose ID is the major version number (e.g. `v1`, `v2`).
  Naturally, the API URLs will contain major version numbers.
* Within each major version (and thus within the corresponding module), use the `Accept` HTTP request header
  to determine the minor version number and write conditional code to respond to the minor versions accordingly.

For each module serving a major version, it should include the resource classes and the controller classes
Qiang Xue committed
859 860
serving for that specific version. To better separate code responsibility, you may keep a common set of
base resource and controller classes, and subclass them in each individual version module. Within the subclasses,
861 862 863
implement the concrete code such as `Model::fields()`.

Your code may be organized like the following:
864 865 866

```
api/
Qiang Xue committed
867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888
    common/
        controllers/
            UserController.php
            PostController.php
        models/
            User.php
            Post.php
    modules/
        v1/
            controllers/
                UserController.php
                PostController.php
            models/
                User.php
                Post.php
        v2/
            controllers/
                UserController.php
                PostController.php
            models/
                User.php
                Post.php
889 890 891 892 893 894
```

Your application configuration would look like:

```php
return [
Qiang Xue committed
895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913
    'modules' => [
        'v1' => [
            'basePath' => '@app/modules/v1',
        ],
        'v2' => [
            'basePath' => '@app/modules/v2',
        ],
    ],
    'components' => [
        'urlManager' => [
            'enablePrettyUrl' => true,
            'enableStrictParsing' => true,
            'showScriptName' => false,
            'rules' => [
                ['class' => 'yii\rest\UrlRule', 'controller' => ['v1/user', 'v1/post']],
                ['class' => 'yii\rest\UrlRule', 'controller' => ['v2/user', 'v2/post']],
            ],
        ],
    ],
914 915 916 917 918 919 920
];
```

As a result, `http://example.com/v1/users` will return the list of users in version 1, while
`http://example.com/v2/users` will return version 2 users.

Using modules, code for different major versions can be well isolated. And it is still possible
Qiang Xue committed
921
to reuse code across modules via common base classes and other shared classes.
922

923 924 925 926 927 928 929
To deal with minor version numbers, you may take advantage of the content negotiation
feature provided by the [[yii\filters\ContentNegotiator|contentNegotiator]] behavior. The `contentNegotiator`
behavior will set the [[yii\web\Response::acceptParams]] property when it determines which
content type to support.

For example, if a request is sent with the HTTP header `Accept: application/json; version=v1`,
after content negotiation, [[yii\web\Response::acceptParams]] will contain the value `['version' => 'v1']`.
930

931 932
Based on the version information in `acceptParams`, you may write conditional code in places
such as actions, resource classes, serializers, etc.
933 934 935 936 937 938 939 940

Since minor versions require maintaining backward compatibility, hopefully there are not much
version checks in your code. Otherwise, chances are that you may need to create a new major version.


Caching
-------

Qiang Xue committed
941

942 943 944 945 946
Documentation
-------------

Testing
-------
Qiang Xue committed
947