Контроллеры

Контроллеры являются частью MVC архитектуры. Это объекты классов, унаследованных от yii\base\Controller, отвечающие за обработку запроса и генерирование ответа. В сущности, после обработки запроса приложениями, контроллеры проанализируют входные данные, передадут их в модели, вставят результаты модели в представления, и в конечном итоге сгенерируют исходящие ответы.

Действия

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

Следующий пример показывает post контроллер с двумя действиями: view и create:

namespace app\controllers;

use Yii;
use app\models\Post;
use yii\web\Controller;
use yii\web\NotFoundHttpException;

class PostController extends Controller
{
    public function actionView($id)
    {
        $model = Post::findOne($id);
        if ($model === null) {
            throw new NotFoundHttpException;
        }

        return $this->render('view', [
            'model' => $model,
        ]);
    }

    public function actionCreate()
    {
        $model = new Post;

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        } else {
            return $this->render('create', [
                'model' => $model,
            ]);
        }
    }
}

В действии view (определенном методом actionView()), код сначала загружает модель согласно запрошенному ID модели; Если модель успешно загружена, то код отобразит ее с помощью представления под названием view. В противном случае будет брошено исключение.

В действии create (определенном методом actionCreate()), код аналогичен. Он сначала пытается загрузить модель с помощью данных из запроса и сохранить модель. Если все прошло успешно, то код перенаправляет браузер на действие view с ID только что созданной модели. В противном случае он отобразит представление create, через которое пользователь может заполнить нужные данные.

Маршруты

Конечные пользователи обращаются к действиям через так называемые маршруты. Маршрут это строка, состоящая из следующих частей:

  • ID модуля: он существует, только если контроллер принадлежит не приложению, а модулю;
  • ID контроллера: строка, которая уникально идентифицирует контроллер среди всех других контроллеров одного и того же приложения (или одного и того же модуля, если контроллер принадлежит модулю);
  • ID действия: строка, которая уникально идентифицирует действие среди всех других действия одного и того же контроллера.

Маршруты могут иметь следующий формат:

ControllerID/ActionID

или следующий формат, если контроллер принадлежит модулю:

ModuleID/ControllerID/ActionID

Таким образом, если пользователь запрашивает URL http://hostname/index.php?r=site/index, то index действие в site контроллере будет вызвано. Секция Маршрутизация содержит более подробную информацию о том как маршруты сопоставляются с действиями.

Создание контроллеров

В Веб приложениях, контроллеры должны быть унаследованы от yii\web\Controller или его потомков. Аналогично для консольных приложений, контроллеры должны быть унаследованы от yii\console\Controller или его потомков. Следующий код определяет site контроллер:

namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
}

ID контроллеров

Обычно контроллер сделан таким образом, что он должен обрабатывать запросы, связанные с определенным ресурсом. Именно по этим причинам, ID контроллеров обычно являются существительные, ссылающиеся на ресурс, который они обрабатывают. Например, вы можете использовать article в качестве ID контроллера, которые отвечает за обработку данных статей.

По-умолчанию, ID контроллеров должны содержать только следующие символы: Английские буквы в нижнем регистре, цифры, подчеркивания, тире и слэш. Например, оба article и post-comment являются допустимыми ID контроллеров, в то время как article?, PostComment, admin\post не являются таковыми.

ID контроллеров также могут содержать префикс подпапки. Например, в admin/article часть article является контроллером в подпапке admin в пространстве имен контроллеров. Допустимыми символами для префиксов подпапок являются: Английские буквы в нижнем и верхнем регистре, символы подчеркивания и слэш, где слэш используется в качестве разграничителя для многовложенных подпапок (например panels/admin).

Правила наименования классов контроллеров

Названия классов контроллеров могут быть получены из ID контроллеров следующими способами:

  • Привести в верхний регистр первый символ в каждом слове, разделенном дефисами. Обратите внимание что, если ID контроллера содержит слэш, то данное правило распространяется только на часть после последнего слэша в ID контроллера;
  • Убрать дефисы и заменить любой прямой слэш на обратный;
  • Добавить суффикс Controller;
  • Добавить в начало пространство имен контроллеров.

Ниже приведены несколько примеров, с учетом того, что пространство имен контроллеров имеет значение по умолчанию равное app\controllers:

  • article соответствует app\controllers\ArticleController;
  • post-comment соответствует app\controllers\PostCommentController;
  • admin/post-comment соответствует app\controllers\admin\PostCommentController;
  • adminPanels/post-comment соответствует app\controllers\adminPanels\PostCommentController.

Классы контроллеров должны быть автозагружаемыми. Именно по этой причине, в вышеприведенном примере, контроллер article должен быть сохранен в файл, псевдоним которого @app/controllers/ArticleController.php; в то время как контроллер admin/post-comment должен находиться в файле @app/controllers/admin/PostCommentController.php.

Info: Последний пример admin/post-comment показывает каким образом вы можете расположить контроллер в подпапке пространства имен контроллеров. Это очень удобно, когда вы хотите организовать свои контроллеры в несколько категорий и не хотите использовать модули.

Карта контроллеров

Вы можете сконфигурировать карту контроллеров для того, чтобы преодолеть описанные выше ограничения именования ID контроллеров и названий классов. В основном это очень удобно, когда вы используете сторонние контроллеры, именование которых вы не можете контролировать.

Вы можете сконфигурировать карту контроллеров в настройках приложения следующим образом:

[
    'controllerMap' => [
        // объявляет "account" контроллер, используя название класса
        'account' => 'app\controllers\UserController',

        // объявляет "article" контроллер, используя массив конфигурации
        'article' => [
            'class' => 'app\controllers\PostController',
            'enableCsrfValidation' => false,
        ],
    ],
]

Контроллер по умолчанию

Каждое приложение имеет контроллер по умолчанию, указанный через свойство yii\base\Application::$defaultRoute. Когда в запросе не указан маршрут, тогда будет использован маршрут указанный в данном свойстве. Для Веб приложений, это значение 'site', в то время как для консольных приложений, это 'help'. Таким образом, если задан URL http://hostname/index.php, это означает, что контроллер site выполнит обработку запроса.

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

[
    'defaultRoute' => 'main',
]

Создание действий

Создание действий не представляет сложностей так же как и объявление так называемых методов действий в классе контроллера. Метод действия это public метод, имя которого начинается со слова action. Возвращаемое значение метода действия представляет собой ответные данные, которые будут высланы конечному пользователю. Приведенный ниже код определяет два действия index и hello-world:

namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
    public function actionIndex()
    {
        return $this->render('index');
    }

    public function actionHelloWorld()
    {
        return 'Hello World';
    }
}

ID действий

В основном действие разрабатывается для какой-либо конкретной обработки ресурса. По этой причине, ID действий в основном являются глаголами, такими как view, update, и т. д.

По-умолчанию, ID действия должен содержать только следующие символы: Английские буквы в нижнем регистре, цифры, подчеркивания и дефисы. Дефисы в ID действий используются для разделения слов. Например, view, update2, comment-post являются допустимыми ID действий, в то время как view?, Update не являются таковыми.

Вы можете создавать действия двумя способами: встроенные действия и отдельные действия. Встроенное действие является методом, определенным в классе контроллера, в то время как отдельное действие является экземпляром класса, унаследованного от yii\base\Action или его потомков. Встроенные действия требуют меньше усилий для создания и в основном используются если у вас нет надобности в повторном использовании действий. Отдельные действия, с другой стороны, в основном создаются для использования в различных контроллерах или при использовании в расширениях.

Встроенные действия

Встроенные действия это те действия, которые определены в рамках методов контроллера, как мы это уже обсудили.

Названия методов действий могут быть получены из ID действий следующим образом:

  • Привести первый символ каждого слова в ID действия в верхний регистр;
  • Убрать дефисы;
  • Добавить префикс action.

Например, index соответствует actionIndex, а hello-world соответствует actionHelloWorld.

Note: Названия имен действий являются регистрозависимыми. Если у вас есть метод ActionIndex, он не будет учтен как метод действия, таким образом, запрос к действию index приведет к выбросу исключения. Также следует учесть, что методы действий должны иметь область видимости public. Методы имеющие область видимости private или protected НЕ определяют методы встроенных действий.

Встроенные действия в основном используются, потому что для их создания не нужного много усилий. Тем не менее, если вы планируете повторно использовать некоторые действия в различных местах, или если вы хотите перераспределить действия, вы должны определить его как отдельное действие.

Отдельные действия

Отдельные действия определяются в качестве классов, унаследованных от yii\base\Action или его потомков. Например, в Yii релизах, присутствуют yii\web\ViewAction и yii\web\ErrorAction, оба из которых являются отдельными действиями.

Для использования отдельного действия, вы должны указать его в карте действий, с помощью переопределения метода yii\base\Controller::actions() в вашем классе контроллера, следующим образом:

public function actions()
{
    return [
        // объявляет "error" действие с помощью названия класса
        'error' => 'yii\web\ErrorAction',

        // объявляет "view" действие с помощью конфигурационного массива
        'view' => [
            'class' => 'yii\web\ViewAction',
            'viewPrefix' => '',
        ],
    ];
}

Как вы можете видеть, метод actions() должен вернуть массив, ключами которого являются ID действий, а значениями - соответствующие названия класса действия или конфигурация. В отличие от встроенных действий, ID отдельных действий могут содержать произвольные символы, до тех пор пока они определены в методе actions().

Для создания отдельного действия, вы должны наследоваться от класса yii\base\Action или его потомков, и реализовать метод run() с областью видимости public. Роль метода run() аналогична другим методам действий. Например,

<?php
namespace app\components;

use yii\base\Action;

class HelloWorldAction extends Action
{
    public function run()
    {
        return "Hello World";
    }
}

Результаты действий

Возвращаемое значение методов действий или метода run() отдельного действия очень важно. Оно является результатом выполнения соответствующего действия.

Возвращаемое значение может быть объектом response, который будет отослан конечному пользователю в качестве ответа.

В вышеприведенных примерах, все результаты действий являются строками, которые будут использованы в качестве тела ответа, высланного пользователю. Следующий пример, показывает действие может перенаправить браузер пользователя на новый URL, с помощью возврата response объекта (т. к. redirect() метод возвращает response объект):

public function actionForward()
{
    // перенаправляем браузер пользователя на http://example.com
    return $this->redirect('http://example.com');
}

Параметры действий

Методы действий для встроенных действий и методы run() для отдельных действий могут принимать параметры, называемые параметры действий. Их значения берутся из запросов. Для Веб приложений, значение каждого из параметров действия берется из $_GET, используя название параметра в качестве ключа; для консольных приложений, они соответствуют аргументам командной строки.

В приведенном ниже примере, действие view (встроенное действие) определяет два параметра: $id и $version.

namespace app\controllers;

use yii\web\Controller;

class PostController extends Controller
{
    public function actionView($id, $version = null)
    {
        // ...
    }
}

Для разных запросов параметры действий будут определены следующим образом:

  • http://hostname/index.php?r=post/view&id=123: параметр $id будет присвоено значение '123', в то время как $version будет иметь значение null, т. к. строка запроса не содержит параметра version;
  • http://hostname/index.php?r=post/view&id=123&version=2: параметрам $id и $version будут присвоены значения '123' и '2' соответственно;
  • http://hostname/index.php?r=post/view: будет брошено исключение yii\web\BadRequestHttpException, т. к. обязательный параметр $id не был указан в запросе;
  • http://hostname/index.php?r=post/view&id[]=123: будет брошено исключение yii\web\BadRequestHttpException, т. к. параметр $id получил неверное значение ['123'].

Если вы хотите, чтобы параметр действия принимал массив значений, вы должны использовать type-hint значение array, как показано ниже:

public function actionView(array $id, $version = null)
{
    // ...
}

Теперь, если запрос будет содержать URL http://hostname/index.php?r=post/view&id[]=123, то параметр $id примет значение ['123']. Если запрос будет содержать URL http://hostname/index.php?r=post/view&id=123, то параметр $id все равно будет содержать массив, т. к. скалярное значение '123' будет автоматически конвертировано в массив.

Вышеприведенные примеры в основном показывают как параметры действий работают для Веб приложений. Больше информации о параметрах консольных приложений представлено в секции Консольные команды.

Действие по умолчанию

Каждый контроллер имеет действие, указанное через свойство yii\base\Controller::$defaultAction. Когда маршрут содержит только ID контроллера, то подразумевается, что действие контроллера по умолчанию было запрошено.

По-умолчанию, это действие имеет значение index. Если вы хотите изменить это значение, просто переопределите данное свойство в классе контроллера следующим образом:

namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
    public $defaultAction = 'home';

    public function actionHome()
    {
        return $this->render('home');
    }
}

Жизненный цикл контроллера

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

  1. Метод yii\base\Controller::init() будет вызван после того как контроллер будет создан и сконфигурирован;
  2. Контроллер создает объект действия, основываясь на запрошенном ID действия:
    • Если ID действия не указан, то будет использовано ID действия по умолчанию;
    • Если ID действия найдено в карте действий, то отдельное действие будет создано;
    • Если ID действия соответствует методу действия, то встроенное действие будет создано;
    • В противном случае, будет выброшено исключение yii\base\InvalidRouteException.
  3. Контроллер последовательно вызывает метод beforeAction() приложения, модуля (если контроллер принадлежит модулю) и самого контроллера.
    • Если один из методов вернул false, то остальные, не вызванные методы beforeAction будут пропущены, а выполнение действия будет отменено;
    • По-умолчанию, каждый вызов метода beforeAction() вызовет событие beforeAction, на которое вы можете назначить обработчики.
  4. Контроллер запускает действие:
    • Параметры действия будут проанализированы и заполнены из данных запроса.
  5. Контроллер последовательно вызывает методы afterAction контроллера, модуля (если контроллер принадлежит модулю) и приложения.
    • По-умолчанию, каждый вызов метода afterAction() вызовет событие afterAction, на которое вы можете назначить обработчики.
  6. Приложение, получив результат выполнения действия, присвоит его объекту response.

Лучшие практики

В хорошо организованных приложениях контроллеры обычно очень тонкие и содержат лишь несколько строк кода. Если ваш контроллер слишком сложный, то обычно это означает, что вам надо провести его рефакторинг и перенести часть кода в другие места.

В целом, контроллеры

  • могут иметь доступ к данным запроса;
  • могут вызывать методы моделей и других компонентов системы с данными запроса;
  • могут использовать представления для формирования ответа;
  • не должны заниматься обработкой данных, это должно происходить в слое моделей;
  • должны избегать использования HTML или другой разметки, лучше это делать в представлениях.