REST в Yii2

  1. 1. Для приема данных в формате JSON
  2. 2. Метод RESTful API включают себя
  3. 3. Важно
  4. 4. Ресурсы
    1. 4.1. Переопределение fields()
    2. 4.2. Переопределение extraFields()
  5. 5. Ссылки
  6. 6. Коллекция
  7. 7. Контролеры
    1. 7.1. Фильтры
    2. 7.2. Наследование от ActiveContorller
    3. 7.3. Контроль доступа для ActiveContorller
  8. 8. Машрутизация
  9. 9. Задания соответсвия:
  10. 10. Формирование ответа
  11. 11. Сериализация данных
  12. 12. Настройка форматирования JSON
  13. 13. Аутентификация
  14. 14. Ограничения частоты запросов:
  15. 15. Версионирование

REST (сокр. от англ. Representational State Transfer — «передача состояния представления») — архитектурный стиль взаимодействия компонентов распределённого приложения в сети. REST представляет собой согласованный набор ограничений, учитываемых при проектировании распределённой гипермедиа-системы.

  • Модель клиент-сервер
  • Отсутствие состояния
  • Кэширование
  • Единообразие интерфейса

Возможности Yii:

  • Быстрое создание прототипов с поддержкой распространенных API к Active Record;
  • Настройка формата ответа (JSON и XML реализованы по умолчанию);
  • Получение сериализованных объектов с нужной вам выборкой полей;
  • Надлежащее форматирование данных и ошибок при их валидации;
  • Эффективная маршрутизация с надлежащей проверкой HTTP методов;
  • Встроенная поддержка методов OPTIONS и HEAD;
  • Аутентификация и авторизация;
  • HTTP кэширование и кэширование данных;
  • Настройка ограничения для частоты запросов (Rate limiting);
1
2
3
4
5
6
use yii\rest\ActiveController;
class UserController extends ActiveController
{
public $modelClass = 'app\models\User'; //Модель
}

Класс контроллера наследуется от yii\rest\ActiveController.

Настройка комонета urlManager:

1
2
3
4
5
6
7
8
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
['class' => 'yii\rest\UrlRule', 'controller' => 'user'],
],
]

Для приема данных в формате JSON

1
2
3
4
5
'request' => [ //компонент application component на использование yii\web\JsonParser
'parsers' => [ //Свойство
'application/json' => 'yii\web\JsonParser',
]
]

Метод RESTful API включают себя

  • GET /users: получение постранично списка всех пользователей;
  • HEAD /users: получение метаданных листинга пользователей;
  • POST /users: создание нового пользователя;
  • GET /users/123: получение информации по конкретному пользователю с id равным 123;
  • HEAD /users/123: получение метаданных по конкретному пользователю с id равным 123;
  • PATCH /users/123 и PUT /users/123: изменение информации по пользователю с id равным 123;
  • DELETE /users/123: удаление пользователя с id равным 123;
  • OPTIONS /users: получение поддерживаемых методов, по которым можно обратится к /users;
  • OPTIONS /users/123: получение поддерживаемых методов, по которым можно обратится к /users/123.

Важно

  • Ресурсы представлены в виде моделей данных, которые наследуются от класса yii\base\Model. Если необходима работа с базами данных (как с реляционными, так и с NoSQL), рекомендуется использовать для представления ресурсов ActiveRecord.
  • yii\rest\UrlRule для упрощения маршрутизации точек входа API

Ресурсы

1 Ресурс конвертируеться в массив yii\rest\Serializer

2 Массив сериализуется в строку заданного формата (например, JSON или XML) при помощи форматтера ответа.

Представление ресурса в виде массива путём переопределения методов fields() и/или extraFields().
fields - определяет набор полей, которые всегда будут включены в массив

extraFields - определяет дополнительные поля, которые пользователь может запросить через параметр expand

Пример:

1
2
3
4
5
6
7
8
// вернёт все поля объявленные в fields()
http://localhost/users
// вернёт только поля id и email, если они объявлены в методе fields()
http://localhost/users?fields=id,email
// вернёт только id, email и profile, если они объявлены в fields() и extraFields()
http://localhost/users?fields=id,email&expand=profile

Переопределение fields()

По умолчанию, yii\base\Model::fields() возвращает все атрибуты модели как поля, а yii\db\ActiveRecord::fields() возвращает только те атрибуты, которые были объявлены в схеме БД.

Пример переопределения полей

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// явное перечисление всех атрибутов лучше всего использовать когда вы хотите быть уверенным что изменение
// таблицы БД или атрибутов модели не повлияет на изменение полей, отдаваемых API (что важно для поддержки обратной
// совместимости API).
public function fields()
{
return [
// название поля совпадает с названием атрибута
'id',
// имя поля "email", атрибут "email_address"
'email' => 'email_address',
// имя поля "name", значение определяется callback-ом PHP
'name' => function () {
return $this->first_name . ' ' . $this->last_name;
},
];
}
// отбрасываем некоторые поля. Лучше всего использовать в случае наследования
public function fields()
{
$fields = parent::fields();
// удаляем не безопасные поля
unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);
return $fields;
}

Переопределение extraFields()

По умолчанию, yii\base\Model::extraFields() ничего не возвращает, а yii\db\ActiveRecord::extraFields() возвращает названия заданных в БД связей.

1
2
3
4
5
6
7
8
9
public function fields()
{
return ['id', 'email'];
}
public function extraFields()
{
return ['profile'];
}

Ссылки

Согласно HATEOAS, расшифровывающемуся как Hypermedia as the Engine of Application State, RESTful API должны возвращать достаточно информации для того, чтобы клиенты могли определить возможные действия над ресурсами.

HATEOAS релализован интерфейс yii\web\Linkable и содержить один метод getLinks()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use yii\db\ActiveRecord;
use yii\web\Link;
use yii\web\Linkable;
use yii\helpers\Url;
class User extends ActiveRecord implements Linkable
{
public function getLinks()
{
return [
Link::REL_SELF => Url::to(['user/view', 'id' => $this->id], true),
];
}
}

Приме ответа:

1
2
3
4
5
6
7
8
"id": 100,
"email": "user@example.com",
// ...
"_links" => {
"self": {
"href": "https://example.com/users/100"
}
}

Коллекция

Объекты ресурсов могут группироваться в коллекции. Каждая коллекция содержит список объектов ресурсов одного типа.

Несмотря на то, что коллекции можно представить в виде массива, удобнее использовать провайдеры данных так как они поддерживают сортировку и постраничную разбивку

Пример:

1
2
3
4
5
6
7
8
9
10
11
12
13
use yii\rest\Controller;
use yii\data\ActiveDataProvider;
use app\models\Post;
class PostController extends Controller
{
public function actionIndex()
{
return new ActiveDataProvider([
'query' => Post::find(),
]);
}
}

При отправке ответа RESTful API, yii\rest\Serializer сериализует массив объектов ресурсов для текущей страницы. Кроме того, он добавит HTTP заголовки, содержащие информацию о страницах:

  • X-Pagination-Total-Count: общее количество ресурсов;
  • X-Pagination-Page-Count: количество страниц;
  • Link: набор ссылок, позволяющий клиенту пройти все страницы ресурсов.

Контролеры

В Yii есть два базовых класса контроллеров для упрощения вашей работы по созданию RESTful-действий: yii\rest\Controller и yii\rest\ActiveController.
yii\rest\ActiveController - есть набор действий по умолчанию, который специально создан для работы с ресурсами, представленными Active Record.

Фильтры

Большинство возможностей RESTful API, предоставляемых yii\rest\Controller, реализовано на основе фильтров.
Порядок работы фильтров:

  • contentNegotiator: обеспечивает согласование содержимого
    verbFilter: обеспечивает проверку HTTP-метода;
    authenticator: обеспечивает аутентификацию пользователя
    rateLimiter: обеспечивает ограничение частоты запросов,

Эти именованные фильтры объявлены в методе behaviors()

1
2
3
4
5
6
7
8
9
10
use yii\filters\auth\HttpBasicAuth;
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
];
return $behaviors;
}

Наследование от ActiveContorller

Действия:

  • index: постраничный список ресурсов;
  • view: возвращает подробную информацию об указанном ресурсе;
  • create: создание нового ресурса;
  • update: обновление существующего ресурса;
  • delete: удаление указанного ресурса;
  • options: возвращает поддерживаемые HTTP-методы.

Все эти действия объявляются в методе actions().

1
2
3
4
5
6
7
8
9
10
11
12
public function actions()
{
$actions = parent::actions();
// отключить действия "delete" и "create"
unset($actions['delete'], $actions['create']);
// настроить подготовку провайдера данных с помощью метода "prepareDataProvider()"
$actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider'];
return $actions;
}

Контроль доступа для ActiveContorller

Для yii\rest\ActiveController эта задача может быть решена переопределением метода checkAccess()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Этот метод должен быть переопределен, чтобы проверить, имеет ли текущий пользователь
* право выполнения указанного действия над указанной моделью данных.
* Если у пользователя нет доступа, следует выбросить исключение [[ForbiddenHttpException]].
*
* @param string $action ID действия, которое надо выполнить
* @param \yii\base\Model $model модель, к которой нужно получить доступ. Если `null`, это означает, что модель, к которой нужно получить доступ, отсутствует.
* @param array $params дополнительные параметры
* @throws ForbiddenHttpException если у пользователя нет доступа
*/
public function checkAccess($action, $model = null, $params = [])
{
// проверить, имеет ли пользователь доступ к $action и $model
// выбросить ForbiddenHttpException, если доступ следует запретить
if ($action === 'update' || $action === 'delete') {
if ($model->author_id !== \Yii::$app->user->id)
throw new \yii\web\ForbiddenHttpException(sprintf('You can %s articles that you\'ve created.', $action));
}
}

Машрутизация

Это может быть легко сделано с помощью настройки компонента приложения urlManager

1
2
3
4
5
6
7
8
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
['class' => 'yii\rest\UrlRule', 'controller' => 'user'],
],
]

Web-приложениях состоит в использовании yii\rest\UrlRule для маршрутизации запросов к RESTful API.

1
2
3
4
5
6
7
8
9
[
'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',
]

Другой метод отключения

1
2
3
4
5
[
'class' => 'yii\rest\UrlRule',
'controller' => 'user',
'except' => ['delete', 'create', 'update'],
],

Задания соответсвия:

В том случае, если автоматическое приведение к множественному числу вам не подходит, вы можете настроить свойство yii\rest\UrlRule::$controller, где указать явное соответствие имени в URL и ID контроллера. Например, код ниже ставит в соответствие имя u и ID контроллера user.

1
2
3
4
[
'class' => 'yii\rest\UrlRule',
'controller' => ['u' => 'user'],
]

Формирование ответа

При обработке RESTful API запросов приложение обычно выполняет следующие шаги, связанные с форматированием ответа:

  • Определяет различные факторы, которые могут повлиять на формат ответа, такие как media type, язык, версия и т.д.
  • Конвертирует объекты ресурсов в массивы
  • Конвертирует массивы в строки исходя из формата, определенного на этапе согласование содержимого.

Для того, чтобы добавить поддержку нового формата, вы должны установить свою конфигурацию для свойства formats у фильтра contentNegotiator, например, с использованием поведения такого вида:

1
2
3
4
5
6
7
8
use yii\web\Response;
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['contentNegotiator']['formats']['text/html'] = Response::FORMAT_HTML;
return $behaviors;
}

Сериализация данных

yii\rest\Serializer - это центральное место, отвечающее за конвертацию объектов ресурсов или коллекций в массивы. Он реализует интерфейсы yii\base\Arrayable и yii\data\DataProviderInterface. Для объектов ресурсов как правило реализуется интерфейс yii\base\Arrayable, а для коллекций - yii\data\DataProviderInterface.

Например:

Например, иногда вам может быть нужно помочь упростить разработку клиентской части приложения с помощью добавления информации о пагинации непосредственно в тело ответа.

1
2
3
4
5
6
7
8
9
10
use yii\rest\ActiveController;
class UserController extends ActiveController
{
public $modelClass = 'app\models\User';
public $serializer = [
'class' => 'yii\rest\Serializer',
'collectionEnvelope' => 'items',
];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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": {
"href": "http://localhost/users?page=1"
},
"next": {
"href": "http://localhost/users?page=2"
},
"last": {
"href": "http://localhost/users?page=50"
}
},
"_meta": {
"totalCount": 1000,
"pageCount": 50,
"currentPage": 1,
"perPage": 20
}
}

Настройка форматирования JSON

Ответ в формате JSON генерируется при помощи класса JsonResponseFormatter, который использует внутри хелпер JSON. Данный форматтер гибко настраивается.

  • опция $prettyPrint полезна на время разработки так как при её использовании ответы получаются более читаемыми.
  • $encodeOptions может пригодиться для более тонкой настройки кодирования
1
2
3
4
5
6
7
8
9
10
11
'response' => [
// ...
'formatters' => [
\yii\web\Response::FORMAT_JSON => [
'class' => 'yii\web\JsonResponseFormatter',
'prettyPrint' => YII_DEBUG, // используем "pretty" в режиме отладки
'encodeOptions' => JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
// ...
],
],
],

Аутентификация

В отличие от Web-приложений, RESTful API обычно не сохраняют информацию о состоянии, а это означает, что сессии и куки использовать не следует.

Есть различные способы отправки токена доступа:

  • HTTP Basic Auth: токен доступа отправляется как имя пользователя. Такой подход следует использовать только в том случае, когда токен доступа может быть безопасно сохранен на стороне абонента API. Например, если API используется программой, запущенной на сервере.
  • Параметр запроса: токен доступа отправляется как параметр запроса в URL-адресе API, т.е. примерно таким образом: https://example.com/users?access-token=xxxxxxxx. Так как большинство Web-серверов сохраняют параметры запроса в своих логах, такой подход следует применять только при работе с JSONP-запросами, которые не могут отправлять токены доступа в HTTP-заголовках.
  • OAuth 2: токен доступа выдается абоненту API сервером авторизации и отправляется API-серверу через HTTP Bearer Tokens, в соответствии с протоколом OAuth2.

Чтобы включить аутентификацию для ваших API, выполните следующие шаги:

  • У компонента приложения user установите свойство enableSession равным false.
  • Укажите, какие методы аутентификации вы планируете использовать, настроив поведение authenticator в ваших классах REST-контроллеров.
  • Реализуйте метод yii\web\IdentityInterface::findIdentityByAccessToken() в вашем классе UserIdentity.

Пример HTTP Basic:

1
2
3
4
5
6
7
8
9
10
use yii\filters\auth\HttpBasicAuth;
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
];
return $behaviors;
}

Включения всех методов:

1
2
3
4
5
6
7
8
9
10
11
12
13
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => CompositeAuth::className(),
'authMethods' => [
HttpBasicAuth::className(),
HttpBearerAuth::className(),
QueryParamAuth::className(),
],
];
return $behaviors;
}

Доступ по токену:
Реализация метода findIdentityByAccessToken()

1
2
3
4
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne(['access_token' => $token]);
}

Ограничения частоты запросов:

Чтобы включить ограничение частоты запросов, класс user identity должен реализовывать интерфейс yii\filters\RateLimitInterface. Этот интерфейс требует реализации следующих трех методов:

  • getRateLimit(): возвращает максимальное количество разрешенных запросов и период времени, например [100, 600], что означает не более 100 вызовов API в течение 600 секунд.
  • loadAllowance(): возвращает оставшееся количество разрешенных запросов и UNIX-timestamp последней проверки ограничения.
  • saveAllowance(): сохраняет оставшееся количество разрешенных запросов и текущий UNIX-timestamp.
1
2
3
4
5
6
7
8
9
public function getRateLimit($request, $action)
{
return [$this->rateLimit, 1]; // $rateLimit запросов в секунду
}
public function loadAllowance($request, $action)
{
return [$this->allowance, $this->allowance_updated_at];
}

Ограничения либо через поведения:

1
2
3
4
5
6
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['rateLimiter']['enableRateLimitHeaders'] = false;
return $behaviors;
}

Версионирование

В результате http://example.com/v1/users возвратит список пользователей API версии 1, в то время как http://example.com/v2/users вернет список пользователей версии 2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
return [
'modules' => [
'v1' => [
'class' => 'app\modules\v1\Module',
],
'v2' => [
'class' => 'app\modules\v2\Module',
],
],
'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']],
],
],
],
];
```
### Обработок ошибок
Изменения ошибок, `beforeSend` компонента `response` прямо в конфигурации приложения:
```php
return [
// ...
'components' => [
'response' => [
'class' => 'yii\web\Response',
'on beforeSend' => function ($event) {
$response = $event->sender;
if ($response->data !== null && !empty(Yii::$app->request->get('suppress_response_code'))) {
$response->data = [
'success' => $response->isSuccessful,
'data' => $response->data,
];
$response->statusCode = 200;
}
},
],
],
];

Приведённый выше код изменит формат ответа (как для удачного запроса, так и для ошибок) если передан GET-параметр suppress_response_code.