В современном мире, мире где доступ к Интернету имеют сотни видов самых разнообразных устройств, веб-приложения вышли далеко за пределы привычных браузеров. И не смотря на то, что большинство устройств имеют встроенные браузеры, все чаще в качестве клиентского приложения используются приложения, разработанные с учетом специфики конкретного устройства. В итоге для одного приложения может быть десяток типов клиентских приложений. Но суть самого приложения не меняется. REST API — это способ сделать универсальное бекенд-приложение, приложение, которое не придется переделывать при добавлении нового типа клиента. REST — это клиент-серверная распределенная архитектура со всеми вытекающими отсюда плюсами. А как же web? Веб-приложение — это еще один клиент. А с учетом того, как семимильными шагами развивается популярность JS-MV* фреимворков (Angular, Backbone, CanJS, Ember и тп) веб скоро окончательно перейдет на одно-страничные приложения, взаимодействующие с сервером посредством асинхронных HTTP-запросов.
Yii2 позволяет написать полноценное RESTful API за пару минут. Причем написать такое API может даже фронтенщик, мало знакомый с yii и php в принципе (если REST API используется для управления ресурсами, а логика всего приложения содержится в JS-фреимворке).
namespace app\controllers; use yii\rest\ActiveController; class UserController extends ActiveController { public $modelClass = 'app\models\User'; }
'urlManager' => [ 'enablePrettyUrl' => true, 'enableStrictParsing' => true, 'showScriptName' => false, 'rules' => [ ['class' => 'yii\rest\UrlRule', 'controller' => 'user'], ], ], 'request' => [ 'parsers' => [ 'application/json' => 'yii\web\JsonParser', ] ],
RESTful API готов 🙂 Больше ничего делать не нужно.
У вас теперь есть следующие url для доступа к API (пояснения о том, как это работает можно не читать).
new ActiveDataProvider([ 'query' => $modelClass::find(), ]);
$model = new $this->modelClass([ 'scenario' => $this->scenario, ]); $model->load(Yii::$app->getRequest()->getBodyParams(), ''); if ($model->save()) { $response = Yii::$app->getResponse(); $response->setStatusCode(201); $id = implode(',', array_values($model->getPrimaryKey(true))); $response->getHeaders()->set('Location', Url::toRoute([$this->viewAction, 'id' => $id], true)); } elseif (!$model->hasErrors()) { throw new ServerErrorHttpException('Failed to create the object for unknown reason.'); } return $model;
$model = $modelClass::findOne($id);
$model = $modelClass::findOne($id); $model->load(Yii::$app->getRequest()->getBodyParams(), ''); if ($model->save() === false && !$model->hasErrors()) { throw new ServerErrorHttpException('Failed to update the object for unknown reason.'); } return $model;
$model = $this->findModel($id); if ($model->delete() === false) { throw new ServerErrorHttpException('Failed to delete the object for unknown reason.'); } Yii::$app->getResponse()->setStatusCode(204);
Откуда столько action, мы еще ничего не написали?
В ActiveController от которого мы отнаследовали наш контроллер уже существуют все описанные выше actions. Вот так, к примеру, разработчики Yii за нас уже написали actionIndex
'index' => [ 'class' => 'yii\rest\IndexAction', 'modelClass' => $this->modelClass, 'checkAccess' => [$this, 'checkAccess'], ],
Как так получается, что REST контроллеры отдают то ActiveRecord модель, то и вовсе ActiveDataProvider, а в результате отдает xml или json-массив?
ActiveController отнаследован от yii\rest\Controller, в котором прописан метод afterAction. Метод сериализует полученный результат по умолчанию с помощью класса yii\rest\Serializer
public function serialize($data) { if ($data instanceof Model && $data->hasErrors()) { return $this->serializeModelErrors($data); } elseif ($data instanceof Arrayable) { return $this->serializeModel($data); } elseif ($data instanceof DataProviderInterface) { return $this->serializeDataProvider($data); } else { return $data; } }
Т.е в своем REST контроллере отнаследованном от yii\rest\ActiveController или yii\rest\Controller можно не думать об конвертировании данных. Можно смело возвращать ActiveRecord модель, любой класс, реализующий интерфейс Arrayable или DataProviderInterface или готовый массив.
За формат ответа (конвертацию массива в JSON или XML) отвечает yii\filters\ContentNegotiator в зависимости от того, какой формат ответа был запрошен, т.е вам, как разработчику об этом думать тоже не нужно.
Так как эта статья вводная, приведу лишь небольшую часть простых «настроек» для только что созданного за пару минут RESTful API:
Как запретить удаление модели через REST API?
В настройках yii\rest\UrlRule можно указать какие методы поддерживает ваше API через only или исключить отдельные методы через except
'urlManager' => [ 'enablePrettyUrl' => true, 'enableStrictParsing' => true, 'showScriptName' => false, 'rules' => [ ['class' => 'yii\rest\UrlRule', 'controller' => 'user' 'except' => ['delete'], ], ], ],
Как запретить доступ к определенным полям модель через REST API?
Для этого в модели нужно переопределить методы fields() и extraFields()
public function fields() { return ['id', 'email', 'username']; } public function extraFields() { return ['status']; }
Теперь при запросе GET /users будут переданы только поля id, email и username. А при запросе GET /users?expand=status вернет данные id, email, username и status.
Как добавить вычисляемые поля модели для REST API?
Все так-же определив их в методе fields() в виде анонимной функции
public function fields() { return [ 'id', 'email', 'username' => function ($model) { return $model->first_name . ' ' . $model->last_name; } ]; } public function extraFields() { return ['status']; }
Конечно возможности создания REST API в Yii2 не ограничиваются описанными в данной статье примерами. За пределами осталось много интересного — авторизация пользователя, проверка прав, сценарии при работе с моделью, лимитирование запросов и многое другое. Надеюсь позже удастся написать вторую часть и заполнить эти пробелы. Ну а пока, если есть вопросы — пишите в комментах, а я постараюсь ответить.
Как раз хотел попробовать совместить Ember.js и Yii2….
Только вот вопрос, я правильно понимаю, что для этого нужен будет один контроллер RestController который будет отдавать всё-всё ? Так как для Одностраничников нужна сразу вся инфа.
Нет. Контроллеров может быть сколько угодно. Один контроллер = 1 сущность, которой вы собираетесь управлять. Одностраничник — это не значит, что запрос на сервер всегда только один. Для посетителя сайт одностраничный, без перезагрузки страницы. Но ajax запросы идут на разные урлы. Но если не воспринимать REST как догму, а взять как основу архитектуры, то вполне может быть контроллер, который объединит нужные данные из нескольких объектов и вернет их одним запросом.
Недавно начал изучать Yii 2. С REST API есть недопонимание. Например если класс унаследован от yii\rest\ActiveController то можем запросто использовать GET, POST, PUT и DELETE запросы. GET запрос возвращает все записи пользователя (например статьи пользователя) в базе данных. Теперь если нам нужно чтобы при GET запросе отображались не все записи, а записи залогиненного пользователя. Что нужно делать в таком случае? B остальных POST, PUT и DELETE запросах тоже самое. Переопределить унаследованные методы (например actionIndex) или есть другой способ? Заранее спасибо.
Для этого в IndexAction есть переменная $prepareDataProvider, к ней нужно присвоить колбек функцию, которая будет возвращать ActiveDataProvider со всеми нужными вам условиями. В контроллере переопределить метод actions() и дописать туда свой колбек
Для PUT, POST и DELETE обрабатывается конкретная запись с неким ID. Так что в таких запросах нужно проверять имеет ли право пользователь менять что-либо в записи с этим ID. Для этого можно использовать checkAccess. Переопределите этот метод в своем контроллере. Добавьте туда нужную проверку и бросайте эксепшен (ForbiddenHttpException) если у пользователя нет прав на данную запись.
Хорошая статья, спасибо! Вопрос… Как делать лимитирование запросов?
Оформила ответ в виде статьи: http://developer.uz/blog/rate-limiter-yii2/если что не понятно — спрашивайте 🙂
с переоределением разобрался, но вот если нужно переопределение только для GET, а PUT должен работать по стандартной схеме? нигде не найду решение(
Вы наследуетесь от ActiveController. Все методы, которые вы переопределили в своем классе работают так как у вас написано. Те методы, что вы не переопределили, работают так, как указано в родительском классе.
Спасибо, очень помог Ваш материал, особенно раздел про fields(). Полдня искал, почему API не цепляет атрибуты, созданные геттерами, и как автоматически раскрывать связи. Рад что нашел в такой человекопонятной подаче и с примерами 🙂
У меня advanced приложение — я хотел бы что бы сайт работал как обычно но + rest api было, такое возможно? Попробовал перестроить на rest api и все встало (( получается что надо rest api отдельно поднимать? Спасибо
Нет, отдельное приложение поднимать не надо. Но отдельные rest контроллеры создать придется.
Я делала так:
1) создала контроллер отнаследованный от yii\rest\Controller в папке controllers/rest, со всеми нужными мне action
2) прописала роутинг
В итоге по адресу /rest/news у меня rest api новостей, а остальное приложение работает как обычно.
‘controller’ => \app\controllers\rest\NewsController::class — контроллер у вас создан в папке frontend или backend?
создала контроллер отнаследованный от yii\rest\Controller — это случайно не опечтка, может yii\rest\ActiveController ?
Спасибо
У меня в основном все примеры для base app. Куда положить зависит от того, откуда будет доступ. В advanced — это как два разных приложения в одном.
ActiveController если вам нужны готовые Rest action, yii\rest\Controller — если готовыми пользоваться не собираетесь и будите писать свои.
Подскажите пожалуйста: пытаюсь отправить из формы данные POST через api controller метод create — запись создается, возвращает id, но в базу пишутся нули, переопределил свой метод как create (спасибо вашей подсказочке) — то же самое, не могу понять где собака зарыта может быть…
Скорее всего поля у модели не safe, а вы присваиваете их через массовое присвоение
Поэтому часть полей у вас не присваивается и пишется в базу с дефолтным значением. Напомню, что safe считаются поля, упомянутые в правилах валидации, хотя бы так: [[‘name’, ‘age’], ‘safe’]
Есть форма с загрузкой файла который сделано с ActiveRecord. Как отправить запрос в restful api и обработать ошибки ??? Как правильно это сделать ?
Фаил отправляется вместе с остальными данными, т.е ничего особенного делать не нужно. Ошибки возвращать в формате json, обычно возвращают код ошибки и текстовое сообщение.
Подскажите, пожалуйста, как сделать чтобы при вызове обычного контроллера, этот контроллер взял данные по REST у другого, уже RESTfull контроллера, обработал их и вывел их максимально похоже как бы это было в стандартной реализации без REST’a? Спасибо!
Я думаю нужно делать запрос через curl к REST, а потом обрабатывать эти данные как надо. REST придуман как сервер для разных клиентов, теоретически веб-клиент и рест бывают на разных серверах, поэтому только через веб-запросы. Но когда это в одном приложении, я делаю общие методы для рест и не рест контроллеров и в веб-контроллере пользуюсь этим-же методом, без вызова rest. Нормального готового веб-клиента для Yii я не нашла. Если найдете — дайте знать.
А как проверить это на клиенте?
Сделать запрос в браузере или в консоли через curl.
Как отправить POST запрос в консоли через curl
data — ваши данные, которые нужно передать. Если параметров несколько, то объединяем через &: ‘id=2&type=3&status=1’
Как отправить DELETE запрос в консоли через curl
Надеюсь это поможет.
I have followed all actions needed. However, i got following error message:
Response content must not be array
I am a new yii developer. Please, help me?
My guess is that you use wrong parent Controller. It should be yii\rest\Controller instead of yii\web\Controller.
Rest controller can handle a response which is Arrayable, Model or DataProvider. Any other data should be converted to string, for example with json_encode()
Здравствуйте, подскажите как организовать выборку with relation? Например есть модели user and profile. Я так понял использование rest не дает возможности выбора связанных моделей.
Все правильно, true REST подразумевает работу с одной сущностью. Т.е user и profile — это разные сущности и должны быть разные запросы. Но имхо не всегда это разумно и иногда не стоит воротить новые REST запросы ради связаных данных. Поэтому можно отдавать связанные модели в том-же запросе
Но это уже не канонический REST, не REST в чистом виде. Если честно, чистого REST я еще на практике нигде не встречала.
Подскажите, возможно ли в Yii2 десериализовать приходящие данные и замапить их на свои модели, при этом не наследуясь от Active Record (Model)?
Можно:
Так это как раз с моделью (Model) или ее наследником работает, а мне надо для своих классов, которые от Model никак не наследуются. Есть ли общий механизм сериализации/десеарилизации данных (жел. разных форматов), как это реализовано, например, в jms serializer?
Не знаю как это реальзовано, но я бы получала аттрибуты класса, в цикле проходилась по ним и заполняла значения если они есть в переданном массиве. Если вы точно уверены что ключи массива точно соответствуют аттрибутам класса то можно так:
Ничего готового ни в одном фреимворке не встречала. Но только ради этого наследоваться от Active Record точно не стоит.
Здравствуйте, нигде не нашёл ваши контакты — как можно с вами связаться по вопросам сотрудничества?
В настоящий момент я не ищу работу и не беру никакие проекты на разработку, если ваше сотрудничество относится к моим Open Source проектам, то связаться со мной можно через Github
Добрый день. вот учусь, спасибо за полнзные статьи и не менее полезный опенсорс))
возник вопрос: основное приложение использует rbac, хочу подкрутить телеграм бота (сейчас он есть, но ходит напрямую в базу), логично его перевести на rest взаимодействие с yii2. собственно, вопрос: про отдельные rest контроллеры понятно, но как корректно и правильно организовать rbac аутентификацию для rest запросов?
С помощью этой статьи, с тем только дополнением что вам нужно не только убедится что юзер у вас есть, но еще и в том, что у юзера есть на это права.
Добрый день , делал все по указанному гайду в том числе по документации, https://www.yiiframework.com/doc/guide/2.0/en/rest-quick-start#trying-it-out , но при курлении выходит 404 ошибка (curl -i -H «Accept:application/json» «http://localhost/user»). В urlmanager выставил опцию ‘pluralize’ => false но все равно не помогает , подскажите пожалуйста в чем может быть причина ?
а ведете ли вы доп.занятие по yii2 ?если да как с вами связаться
Я не веду никаких занятий и уже больше 2-х лет не пишу ничего на Yii (так сложилось, что перешла на Laravel). Но время от времени отвечаю на комментарии в этом блоге 🙂 У Yii отличное коммюнити, пишите на форум, ребята отзывчивые, обязательно помогут если есть какие-то вопросы.
Как при обновлении вместо PUT использовать POST
API редактирования товара должен быть доступен только администратору POST-запросом на адрес {API}/product/,
Смотрим код: если все правила маршрутизации для REST actions прописаны как
значит ответ на ваш вопрос нужно искать в классе yii\rest\UrlRule. Открываем класс и видим, что есть два публичных аттрибута, которые можно переопределить: $patterns и $extraPatterns
Написано, что extraPatterns перекрывают patterns, так что должно сработать. Если нет — то придется переопределять все patterns, таким-же образом.