
Основы работы с REST в Yii2 можно прочитать тут.
В данной статье практический пример реализации REST API на основе существующих в фреймворке средств.
В файле REST контроллера vendor/yiisoft/yii2/rest/ActiveController.php можно посмотреть какие действия и типы запросов используются:
protected function verbs() { return [ 'index' => ['GET', 'HEAD'], 'view' => ['GET', 'HEAD'], 'create' => ['POST'], 'update' => ['PUT', 'PATCH'], 'delete' => ['DELETE'], ]; }
С этими действиями можно работать, например изменять, расширять, удалять и тд.
Для серьезного проекта с перспективой роста следует сразу определиться с архитектурой. Я предлагаю использовать такую структуру размещения каталогов и файлов API:

Данная структура похожа на указанную в разделе «Версионирование» документации Yii2: https://github.com/yiisoft/yii2/blob/master/docs/guide-ru/rest-versioning.md
Для примера буду использовать фреймворк версии «advanced».
Каталог «api» создается в корне проекта и представляет из себя отдельный раздел наряду с backend и frontend со своими конфигами, контроллерами, моделями и «точкой входа» - index.php. Таким образом нужно создать для него поддомен или же настроить перенаправление на WEB сервере, чтобы API открывалось, например, по ссылке: api.yoursite.com
Как видно из схемы, можно создать несколько версий API (v1, v2, v3...). Это делается для того, чтобы можно было создавать новый функционал, предоставлять данные в измененном виде и при этом пользователи которые уже используют ваше API могли дальше его использовать с теми же настройками и у них ничего не «ломалось».
Есть пример проекта использующего указанную структуру размещения файлов API: https://github.com/deerawan/yii2-advanced-api
который я брал за основу, но если будете его клонировать, то учтите, что он древний и сходу не заработает, нужно немного доработать. Поэтому я выкладываю в архиве
каталог «api» со всеми файлами, который совместим с текущей версией фреймворка Yii2 (2.0.15).
В архиве есть контроллер, модель и миграции указанного выше проекта (с демо данными) для демонстрации работы API, на базе которых можно создавать свои.
Таким образом, прежде всего нужно перенести папку «api» из архива в корень проекта. Далее перенести файлы миграций в папку с миграциями (console\migrations) и применить миграции.
Так же, желательно, дополнить папку environments проекта файлами из архива, чтобы после клонирования проекта и выполнения команды php init, локальные файлы типа «index.php», «main-local.php» и тд. создавались автоматически в своих папках. Если команда init уже выполнялась (т.е. API создается для уже действующего проекта), то на другом компьютере после команды git pull просто скопировать файлы из папки environments в соответствующие папки каталога «api».
Папка api подключается автоматически в автозагрузку классов фреймворка, т.к. в конфиге указан для нее алиас:
'aliases' => [ '@api' => dirname(dirname(__DIR__)) . '/api', ],
Основной конфигурационный файл api/config/main.php уже полностью настроен для работы с тестовым API, поэтому, после проверки работы остается только изменить некоторые строки на свои. Так, в секции urlManager указываем свой REST контроллер:
'rules' => [ [ 'class' => \yii\rest\UrlRule::class, 'controller' => ['v1/country'], 'prefix' => 'api', ]
В общем ничего сложного и вот уже вы можете тестировать работу REST API.
Получим список стран выполнив GET запрос без параметров:
http://YOUR_SITE/api/v1/countries
Результат:
<response>
<item>
<code>AU</code>
<country>Australia</country>
<population>18886000</population>
</item>
<item>
<code>BR</code>
<country>Brazil</country>
<population>170115000</population>
</item>
<item>
<code>CA</code>
<country>Canada</country>
<population>1147000</population>
</item>
...
Далее информация из практического применения.
Несколько версий (v1, v2…).
Если нужно будет сделать несколько версий, то просто создать в папке api/modules папку с новой версией, например «v2» со своим модулем и др. файлами по аналогии.
Подключить в api/config/main.php:
'modules' => [ 'v1' => [ 'basePath' => '@app/modules/v1', 'class' => \api\modules\v1\Module::class, ], 'v2' => [ 'basePath' => '@app/modules/v2', 'class' => \api\modules\v2\Module::class, ] ], … 'rules' => [ [ 'class' => \yii\rest\UrlRule::class, 'controller' => ['v1/country', 'v2/country'], 'prefix' => 'api', ] ],где «country» - название вашего REST контроллера.
Добавляем свой метод/действие
'rules' => [ [ 'class' => \yii\rest\UrlRule::class, 'controller' => ['v1/country'], 'prefix' => 'api', //api будет доступен по url, начинающимся с /api/ 'extraPatterns' => [ 'GET /' => 'new', ], ],
Тут указывается, что при вызове методом GET (без параметров) будет выполнен метод actionNew контроллера. Вызывать нужно во множественной форме, не country, а countries: http://YOUR_SITE/api/v1/countries
т.к. свойство 'pluralize' не определено в false.
Пример для других методов:
'extraPatterns' => [ 'POST {id}/your_preferred_url' => 'xxxxx', // 'xxxxx' refers to 'actionXxxxx' ],
Пример своего действия REST контроллера:
public function actionNew() { $requestParams = Yii::$app->getRequest()->getBodyParams(); if (empty($requestParams)) { $requestParams = Yii::$app->getRequest()->getQueryParams(); } /* @var $modelClass \yii\db\BaseActiveRecord */ $modelClass = $this->modelClass; $query = $modelClass::find(); if (!empty($filter)) { $query->andWhere($filter); } return Yii::createObject([ 'class' => ActiveDataProvider::className(), 'query' => $query, 'pagination' => [ 'params' => $requestParams, ], 'sort' => [ 'params' => $requestParams, ], ]); }
Тут я за основу взял метод prepareDataProvider из vendor/yiisoft/yii2/rest/IndexAction.php.
Метод возвращает объект ActiveDataProvider. Но можно в действии возвращать что угодно, например:
public function actionNew() { $result = $this->modelClass::find() ->where(['>', 'population', 70000000]) ->all(); return $result; }
Передаваемые данные сериализуются автоматически и пользователь получит ответ в формате JSON или XML.
Если данных может быть очень много стоит использовать ActiveDataProvider чтобы разбивать передачу на части (пагинация).
Переопределение стандартных действий REST
Стандартные действия (index, view, create и тд.) прописаны в vendor/yiisoft/yii2/rest/ActiveController.phpНапример нужно переопределить метод index. Для этого в своем контроллере (типа api/modules/v1/controllers/CountryController.php) переопределяем метод actions:
public function actions() { $actions = [ 'index' => [ 'class' => api\modules\v2\actions\IndexAction::class, 'modelClass' => $this->modelClass, 'checkAccess' => [$this, 'checkAccess'], ], ]; return array_merge(parent::actions(), $actions); }где указываем нужное действие и путь к его классу.
В переопределенном действии обязательным есть метод run. Пример api/modules/v1/actions/IndexAction.php:
<?php namespace api\modules\v1\actions; use Yii; use yii\data\ActiveDataProvider; use yii\rest\Action; class IndexAction extends Action { /** * @return ActiveDataProvider */ public function run() { if ($this->checkAccess) { call_user_func($this->checkAccess, $this->id); } return $this->prepareDataProvider(); } /** * Prepares the data provider that should return the requested collection of the models. * @return ActiveDataProvider */ protected function prepareDataProvider() { $requestParams = Yii::$app->getRequest()->getBodyParams(); if (empty($requestParams)) { $requestParams = Yii::$app->getRequest()->getQueryParams(); } /* @var $modelClass \yii\db\BaseActiveRecord */ $modelClass = $this->modelClass; $query = $modelClass::find(); if (!empty($filter)) { $query->andWhere($filter); } return Yii::createObject([ 'class' => ActiveDataProvider::className(), 'query' => $query, 'pagination' => [ 'params' => $requestParams, ], 'sort' => [ 'params' => $requestParams, ], ]); } }
Тут можно добавить какие-то фильтры в выборку из БД. Данные для фильтра можно получать из URL.
Так же можно удалить лишние действия или переопределить какое-то прямо в своем REST контроллере
public function actions() { $actions = parent::actions(); // disable the "delete" and "create" actions unset($actions['delete'], $actions['create']); // customize the data provider preparation with the "prepareDataProvider()" method $actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider']; return $actions; } public function prepareDataProvider() { // prepare and return a data provider for the "index" action }
Добавить условие выборки тут же в методе actions
public function actions() { $actions = parent::actions(); $actions['index']['prepareDataProvider'] = function ($action) { return new ActiveDataProvider([ 'query' => $this->modelClass::find()->where('code=:code', [':code' => $_GET['code']]), ]); }; return $actions; }
Метод поиска/выборки (тут - search) по БД можно разместить и в самой модели, особенно если используются дополнительные условия выборки:
public function actions() { return [ 'index' => [ 'class' => 'yii\rest\IndexAction', 'modelClass' => $this->modelClass, 'prepareDataProvider' => function () { $searchModel = new SearchModel(); return $searchModel->search(Yii::$app->request->queryParams); }, ], ]; }