2 · курс
0%
Главная/🔌REST API

38REST API — быстрый старт

ActiveController за пять минут — контроллер, UrlRule с pluralize, parsers/formatters; ресурсы и fields()/extraFields(), ссылки HATEOAS; контроллеры yii\rest\Controller, actions() и checkAccess(); маршрутизация UrlRule с except/extraPatterns; Yii как микрофреймворк.

⏱ ~6 мин 📚 5 глав руководства

yii\rest даёт CRUD-API поверх Active Record за десяток строк: ActiveController + одно правило UrlRule — и GET /users, POST /users, PATCH /users/1 работают с пагинацией, сортировкой, валидацией, JSON/XML по Accept, кодами ошибок и HATEOAS-ссылками.

Быстрый старт#

controllers/UserController.phpPHP
namespace app\controllers;

use yii\rest\ActiveController;

class UserController extends ActiveController
{
    public $modelClass = 'app\models\User';
}
config/web.phpPHP
'components' => [
    'request' => [
        'parsers' => ['application/json' => 'yii\web\JsonParser'],   // принимать JSON в теле
    ],
    'urlManager' => [
        'enablePrettyUrl' => true,
        'enableStrictParsing' => true,
        'showScriptName' => false,
        'rules' => [
            ['class' => 'yii\rest\UrlRule', 'controller' => 'user'],
        ],
    ],
],

Готово. Что получилось:

Запрос Действие Результат
GET /usersindexсписок постранично, заголовки X-Pagination-*, Link
HEAD /usersindexтолько заголовки
POST /userscreate201 + созданная модель; при ошибках 422 + список ошибок
GET /users/123viewмодель
PUT, PATCH /users/123updateобновлённая модель
DELETE /users/123delete204
OPTIONS /usersoptionsразрешённые методы в Allow
GET /users?fields=id,email&expand=profileindexвыбор полей и связей
Консоль
curl -i -H "Accept: application/json" "http://localhost/users?page=2&per-page=10&sort=-id"
curl -X POST -H "Content-Type: application/json" -d '{"username":"sam","email":"s@x.io"}' http://localhost/users

Ответ — JSON по умолчанию, XML при Accept: application/xml. Ошибки валидации — HTTP 422 с массивом [{field, message}].

Ресурсы#

Ресурс — объект Arrayable (все модели и AR). Что попадёт в ответ, решают:

PHP
class User extends ActiveRecord
{
    public function fields()
    {
        $fields = parent::fields();               // все атрибуты
        // 🔐 убрать секреты
        unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);
        $fields['full_name'] = function ($model) { return $model->first_name . ' ' . $model->last_name; };
        $fields['email_short'] = 'email';         // переименовать
        return $fields;
    }

    public function extraFields()
    {
        return ['profile', 'posts'];              // связи — только по ?expand=profile,posts
    }
}

fields() — по умолчанию в ответе; extraFields() — по запросу. Клиент управляет: ?fields=id,email сужает, ?expand=profile добавляет; можно вложенно: expand=posts.comments, fields=posts.title.

Ссылки (HATEOAS)#

PHP
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),
            'edit' => Url::to(['user/view', 'id' => $this->id], true),
        ];
    }
}
// в ответе появится: "_links": {"self": {"href": "http://…/users/1"}, ...}

Коллекции#

Действие index возвращает провайдер данных → сериализатор выводит элементы, а пагинацию — в заголовки X-Pagination-Total-Count, X-Pagination-Page-Count, X-Pagination-Current-Page, X-Pagination-Per-Page и Link. Чтобы метаданные попали в тело:

PHP
public $serializer = [
    'class' => 'yii\rest\Serializer',
    'collectionEnvelope' => 'items',   // {"items": [...], "_links": {...}, "_meta": {...}}
];

Контроллеры#

yii\rest\Controller — базовый, без привязки к модели; ActiveController — с готовыми CRUD-действиями. Оба уже подключают фильтры: ContentNegotiator (формат по Accept), VerbFilter, AuthMethod (если настроен), RateLimiter.

PHP
class UserController extends ActiveController
{
    public $modelClass = 'app\models\User';

    public function actions()
    {
        $actions = parent::actions();
        unset($actions['delete'], $actions['create']);                       // убрать
        $actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider'];   // свой провайдер
        return $actions;
    }

    public function prepareDataProvider()
    {
        return new ActiveDataProvider([
            'query' => User::find()->where(['status' => 1]),
            'pagination' => ['pageSize' => 20],
        ]);
    }

    public function checkAccess($action, $model = null, $params = [])
    {
        if ($action === 'update' || $action === 'delete') {
            if ($model->created_by !== \Yii::$app->user->id) {
                throw new \yii\web\ForbiddenHttpException('Можно менять только свои записи');
            }
        }
    }
}
actions()
index (IndexAction), view, create, update, delete, options; у index — dataFilter (2.0.13) для фильтрации коллекций
checkAccess($action, $model, $params)
вызывается каждым действием; бросайте ForbiddenHttpException
createScenario, updateScenario
сценарии моделей для create/update
serializer
как превращать модели в массив
verbs()
карта действий → HTTP-методов для VerbFilter

Своё действие — обычный метод с return $data; массив/объект сериализуются автоматически:

PHP
public function actionSearch($q)
{
    return new ActiveDataProvider(['query' => User::find()->where(['like', 'username', $q])]);
}

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

yii\rest\UrlRule создаёт набор правил под контроллер:

PHP
'rules' => [
    ['class' => 'yii\rest\UrlRule', 'controller' => 'user'],
    ['class' => 'yii\rest\UrlRule', 'controller' => ['user', 'post']],         // несколько
    ['class' => 'yii\rest\UrlRule', 'controller' => ['v1/user', 'v1/post']],   // в модуле
    [
        'class' => 'yii\rest\UrlRule',
        'controller' => ['users' => 'user'],       // свой URL-сегмент → контроллер
        'pluralize' => false,                      // не добавлять -s (по умолчанию user → users)
        'prefix' => 'api',                         // /api/users
        'except' => ['delete', 'create'],          // не создавать эти маршруты
        'only' => ['index', 'view'],
        'extraPatterns' => [                       // свои
            'GET search' => 'search',              // GET /users/search → user/search
            'POST {id}/activate' => 'activate',
        ],
        'tokens' => ['{id}' => '<id:\\d[\\d,]*>'], // формат ID: 1 или 1,2,3
    ],
],

Множественное число — через Inflector::pluralize() (person → people); нестандартное задавайте картой. Порядок: маршруты REST обычно выше остальных правил, enableStrictParsing = true отсекает всё, что не описано.

Yii как микрофреймворк#

Не обязательно брать шаблон приложения — можно собрать API из composer require yiisoft/yii2 и одного файла:

index.phpPHP
<?php
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/vendor/yiisoft/yii2/Yii.php';

$config = [
    'id' => 'micro-app',
    'basePath' => __DIR__,
    'controllerNamespace' => 'micro\controllers',
    'aliases' => ['@micro' => __DIR__],
    'components' => [
        'db' => ['class' => 'yii\db\Connection', 'dsn' => 'sqlite:@micro/db.sqlite'],
        'urlManager' => [
            'enablePrettyUrl' => true, 'showScriptName' => false, 'enableStrictParsing' => true,
            'rules' => [['class' => 'yii\rest\UrlRule', 'controller' => 'post']],
        ],
    ],
];

(new yii\web\Application($config))->run();

PostController extends ActiveController с $modelClass — и GET /posts уже отвечает. Плюс минимальная модель, миграция через ./yii — по сути тот же фреймворк, но без лишних папок. Хорошо для маленьких сервисов и чтобы понять, как устроено приложение.

Проверь себя5

Парсер запроса: 'parsers' => ['application/json' => 'yii\web\JsonParser'] в компоненте request.

Переопределить fields() модели и убрать чувствительные поля; связи вынести в extraFields().

fields() возвращаются всегда (если клиент не сузил ?fields=), extraFields() — только по ?expand=.

В checkAccess($action, $model) контроллера; бросить ForbiddenHttpException.

Опция prefix => 'api' у yii\rest\UrlRule (или префикс модуля в controller).