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

Контроллеры являются частью 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`.

> Информация: Последний пример `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`.

> Примечание: Названия имен действий являются регистрозависимыми. Если у вас есть метод `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, который будет отослан конечному пользователю в качестве ответа.

- Для Веб приложений, возвращаемое значение также может быть произвольными данными, которые будут присвоены yii\web\Response::$data, а затем конвертированы в строку, представляющую тело ответа.
- Для Консольных приложений, возвращаемое значение также может быть числом, представляющим статус выхода исполнения команды.

В вышеприведенных примерах, все результаты действий являются строками, которые будут использованы в качестве тела ответа, высланного пользователю. Следующий пример, показывает действие может перенаправить браузер пользователя на новый 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 или другой разметки, лучше это делать в представлениях.