REST API на Yii2: RestController, ActiveController или GraphQL
Три способа отдать данные из Yii2 наружу: ручной контроллер, встроенный ActiveController и надстройка GraphQL. Разбираем, что каждый выигрывает и чем за это платите.

Задача звучит одинаково: отдать данные из Yii2-приложения наружу по HTTP. А вот реализаций три, и выбор между ними определяет, сколько кода вы напишете руками, насколько жёстко зафиксируете контракт и как больно будет менять API через полгода. ActiveController поднимает CRUD за десять строк, но диктует форму ответа. Ручной контроллер даёт полный контроль ценой ручной работы. GraphQL решает проблему избыточной выборки, но добавляет слой, который надо сопровождать. Ниже — цена каждого варианта на реальных операциях: список с фильтрами, вложенные связи, валидация и версионирование.
Статья написана для Yii2 версии 2.0.x (актуальная ветка на момент написания — 2.0.49+). Речь только о выборе способа построить REST API; вопросы аутентификации по токену и rate limiting — отдельная тема, здесь их не разворачиваем.
Три варианта в двух словах
Все три работают поверх одной инфраструктуры Yii2: компонента yii\web\Request, форматтеров ответа (yii\web\Response) и системы контроллеров. Отличаются они тем, сколько поведения даётся из коробки.
- ActiveController — класс
yii\rest\ActiveController. Привязываете к моделиActiveRecord, получаете шесть готовых действий: index, view, create, update, delete, options. Ноль строк логики для базового CRUD. - Ручной Controller — обычный
yii\rest\Controllerили дажеyii\web\Controllerс форматтером JSON. Каждое действие пишете сами: сами разбираете запрос, сами формируете ответ, сами решаете, что вернуть. - GraphQL — внешняя надстройка, чаще всего пакет
webonyx/graphql-php(интеграции с Yii2 в основном самописные или через community-пакеты). Клиент сам описывает, какие поля ему нужны, одним запросом на один эндпоинт.
ActiveController: быстро, но с рамками
Минимальный REST-ресурс для модели поста:
// controllers/PostController.php
namespace app\controllers;
use yii\rest\ActiveController;
class PostController extends ActiveController
{
// Полное имя класса модели, с которой работает ресурс
public $modelClass = 'app\\models\\Post';
}
После этого доступны GET /posts, GET /posts/1, POST /posts и остальные — при условии, что в конфиге настроен UrlManager с правилом yii\rest\UrlRule:
// config/web.php, секция components
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'rules' => [
['class' => 'yii\\rest\\UrlRule', 'controller' => 'post'],
],
],
Форму ответа контролируете через метод fields() и extraFields() в самой модели — какие атрибуты отдавать, какие связи подгружать по ?expand=author. Пагинация, сортировка (?sort=-created_at) и фильтрация через DataFilter идут почти бесплатно.
Цена: вы привязаны к тому, что ActiveController считает нормой. Как только логика ответа перестаёт совпадать с CRUD над одной таблицей — например, надо агрегировать данные из трёх моделей или вернуть кастомную структуру — вы переопределяете методы actions(), prepareDataProvider() и постепенно переписываете половину того, что дал фреймворк. В этот момент ActiveController перестаёт экономить и начинает мешать.
ActiveController хорош ровно до первого требования, которое не ложится на «одна модель — один ресурс». Дальше вы либо боретесь с ним, либо переходите на ручной контроллер. Оцените это до старта, а не на третьем спринте.
Ручной контроллер: полный контроль
Тот же список постов, но руками:
// controllers/PostController.php
namespace app\controllers;
use Yii;
use yii\rest\Controller;
use yii\data\ActiveDataProvider;
use app\models\Post;
class PostController extends Controller
{
public function actionIndex()
{
// Формируем выборку сами: только опубликованные, свежие сверху
$query = Post::find()
->where(['status' => Post::STATUS_PUBLISHED])
->orderBy(['created_at' => SORT_DESC]);
return new ActiveDataProvider([
'query' => $query,
'pagination' => ['pageSize' => 20],
]);
}
}
Возвращённый ActiveDataProvider Yii2 сам сериализует через yii\rest\Serializer — с корректными заголовками пагинации (X-Pagination-Total-Count и остальные). Формат ответа (JSON или XML) определяет content negotiation по заголовку Accept.
Что выигрываете: любую структуру ответа, любую логику доступа, произвольную агрегацию. Что теряете: каждое действие, каждую проверку, каждый формат ошибки пишете сами. Легко получить пять контроллеров, где один возвращает поле error, другой message, третий валит 500 вместо 422. Единообразие держится только на вашей дисциплине.
GraphQL: клиент решает, что забрать
GraphQL меняет саму модель: один эндпоинт, клиент присылает запрос с точным списком полей. Проблема, которую он снимает — over-fetching (тянем лишние поля) и under-fetching (делаем N запросов, чтобы собрать связанные данные). Для мобильного клиента на медленной сети это ощутимо.
В Yii2 нет официального компонента GraphQL. Обычно ставят webonyx/graphql-php и пишут тонкую интеграцию — контроллер, который принимает запрос, отдаёт его в схему и возвращает результат. Точные имена классов и способ описания схемы смотрите в документации пакета: API там менялся между мажорными версиями, и подставлять сигнатуры по памяти рискованно.
Цена GraphQL в Yii2:
- Схему и резолверы вы описываете и сопровождаете сами — из коробки фреймворк ничего не даёт.
- Проблема N+1 запросов не исчезает, а становится острее: клиент запрашивает вложенные связи, каждая порождает отдельный SQL. Нужен dataloader-паттерн или заранее продуманный
with(). - Кеширование по HTTP и стандартные инструменты мониторинга работают хуже: всё идёт через один POST-эндпоинт.
GraphQL оправдан, когда у вас много разных клиентов с разными потребностями в данных, и вы устали плодить эндпоинты вроде /posts-for-mobile и /posts-full. Для одного клиента и предсказуемого набора запросов он добавляет слой, который нечем окупить.
Сравнение по цене
| Критерий | ActiveController | Ручной Controller | GraphQL |
|---|---|---|---|
| Код для базового CRUD | ~5 строк | каждое действие руками | схема + резолверы |
| Гибкость структуры ответа | низкая, через переопределение | полная | полная, на стороне клиента |
| Единообразие формата ошибок | из коробки | на вашей дисциплине | по спецификации GraphQL |
| Риск N+1 | управляется через expand | управляется вручную | высокий, нужен dataloader |
| HTTP-кеш, мониторинг эндпоинтов | стандартные | стандартные | затруднены |
| Порог входа | низкий | средний | высокий |
Когда что выбирать
- ActiveController — админ-панель, внутренний сервис, MVP, где ресурсы почти один-в-один совпадают с таблицами. Начинайте отсюда: переписать на ручной контроллер позже дешевле, чем сразу городить абстракции.
- Ручной Controller — публичное API со сложной логикой доступа, кастомными форматами, агрегацией из нескольких моделей. Заведите базовый класс контроллера с единым форматом ошибок, иначе разъедется.
- GraphQL — несколько типов клиентов с разными выборками полей, и команда готова сопровождать схему и решать N+1. Не берите его «на будущее»: слой останется, а потребность может не наступить.
Что ломается на практике
Три ошибки, которые встречаются чаще остальных:
- Утечка полей. ActiveController по умолчанию отдаёт все атрибуты модели через
fields(). Если в таблице естьpassword_hashилиauth_key, они уедут в ответ. Всегда переопределяйтеfields()явным белым списком. - N+1 на expand. Запрос
?expand=commentsбезwith('comments')в выборке порождает отдельный SQL на каждую запись. На списке из 20 постов это 21 запрос вместо двух. - Форматы ошибок вразнобой. В ручных контроллерах легко вернуть то
throw new NotFoundHttpException, то['error' => '...']с кодом 200. Клиент не может обработать это единообразно.
Что делать дальше
Определитесь с форматом ошибок до первой строки контроллера — Yii2 по умолчанию отдаёт понятную структуру через errorHandler, зафиксируйте её и не отступайте. Настройте content negotiation и версионирование (обычно префикс /v1/ в правилах UrlManager) сразу: добавить версию в работающее API без ломки клиентов почти невозможно. Затем включите логирование медленных запросов и посмотрите в проде на реальный профиль with() — именно там всплывает N+1, которого не видно на десяти тестовых записях.
Создатель презентаций: Структура и текст каждого слайда: заголовки-утверждения, одна мысль на слайд, заметки докладчику.
Спросить за 150 ₽Источники: Yii2 Guide — REST APIs, graphql-php Documentation
Частые вопросы
А если у меня старая версия Yii, 2.0.13 например?
Можно ли смешать ActiveController и ручные действия в одном контроллере?
Что вернуть при ошибке валидации — какой HTTP-код?
GraphQL полностью заменяет REST в Yii2?
Не сработало: UrlRule настроил, а /posts отдаёт 404. В чём дело?
Как отдавать вложенные связи без N+1 запросов?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.