Контроллеры
Контроллеры являются частью 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\Application|Веб приложениях]], контроллеры должны быть унаследованы от [[yii\web\Controller]] или его потомков.
Аналогично для [[yii\console\Application|консольных приложений]], контроллеры должны быть унаследованы от [[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
в [[yii\base\Application::controllerNamespace|пространстве имен контроллеров]].
Допустимыми символами для префиксов подпапок являются: Английские буквы в нижнем и верхнем регистре, символы подчеркивания и
слэш, где слэш используется в качестве разграничителя для многовложенных подпапок (например panels/admin
).
Правила наименования классов контроллеров
Названия классов контроллеров могут быть получены из ID контроллеров следующими способами:
- Привести в верхний регистр первый символ в каждом слове, разделенном дефисами. Обратите внимание что, если ID контроллера содержит слэш, то данное правило распространяется только на часть после последнего слэша в ID контроллера;
- Убрать дефисы и заменить любой прямой слэш на обратный;
- Добавить суффикс
Controller
; - Добавить в начало [[yii\base\Application::controllerNamespace|пространство имен контроллеров]].
Ниже приведены несколько примеров, с учетом того, что [[yii\base\Application::controllerNamespace|пространство имен контроллеров]]
имеет значение по умолчанию равное 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
показывает каким образом вы можете расположить контроллер в подпапке [[yii\base\Application::controllerNamespace|пространства имен контроллеров]]. Это очень удобно, когда вы хотите организовать свои контроллеры в несколько категорий и не хотите использовать модули.
Карта контроллеров
Вы можете сконфигурировать [[yii\base\Application::controllerMap|карту контроллеров]] для того, чтобы преодолеть описанные выше ограничения именования ID контроллеров и названий классов. В основном это очень удобно, когда вы используете сторонние контроллеры, именование которых вы не можете контролировать.
Вы можете сконфигурировать [[yii\base\Application::controllerMap|карту контроллеров]] в настройках приложения следующим образом:
[
'controllerMap' => [
// объявляет "account" контроллер, используя название класса
'account' => 'app\controllers\UserController',
// объявляет "article" контроллер, используя массив конфигурации
'article' => [
'class' => 'app\controllers\PostController',
'enableCsrfValidation' => false,
],
],
]
Контроллер по умолчанию
Каждое приложение имеет контроллер по умолчанию, указанный через свойство [[yii\base\Application::defaultRoute]].
Когда в запросе не указан маршрут, тогда будет использован маршрут указанный в данном свойстве.
Для [[yii\web\Application|Веб приложений]], это значение 'site'
, в то время как для [[yii\console\Application|консольных приложений]],
это '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, который будет отослан конечному пользователю в качестве ответа.
- Для [[yii\web\Application|Веб приложений]], возвращаемое значение также может быть произвольными данными, которые будут присвоены [[yii\web\Response::data]], а затем сконвертированы в строку, представляющую тело ответа.
- Для [[yii\console\Application|Консольных приложений]], возвращаемое значение также может быть числом, представляющим [[yii\console\Response::exitStatus|статус выхода]] исполнения команды.
В вышеприведенных примерах, все результаты действий являются строками, которые будут использованы в качестве тела ответа, высланного пользователю. Следующий пример, показывает действие может перенаправить браузер пользователя на новый URL, с помощью возврата response объекта (т. к. [[yii\web\Controller::redirect()|redirect()]] метод возвращает response объект):
public function actionForward()
{
// перенаправляем браузер пользователя на http://example.com
return $this->redirect('http://example.com');
}
Параметры действий
Методы действий для встроенных действий и методы run()
для отдельных действий могут принимать параметры,
называемые параметры действий. Их значения берутся из запросов. Для [[yii\web\Application|Веб приложений]],
значение каждого из параметров действия берется из $_GET
, используя название параметра в качестве ключа;
для [[yii\console\Application|консольных приложений]], они соответствуют аргументам командной строки.
В приведенном ниже примере, действие 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');
}
}
Жизненный цикл контроллера
При обработке запроса, приложение создаст контроллер, основываясь на запрошенном маршруте. Для выполнения запроса, контроллер пройдет через следующие этапы жизненного цикла:
- Метод [[yii\base\Controller::init()]] будет вызван после того как контроллер будет создан и сконфигурирован;
- Контроллер создает объект действия, основываясь на запрошенном ID действия:
- Если ID действия не указан, то будет использовано [[yii\base\Controller::defaultAction|ID действия по умолчанию]];
- Если ID действия найдено в [[yii\base\Controller::actions()|карте действий]], то отдельное действие будет создано;
- Если ID действия соответствует методу действия, то встроенное действие будет создано;
- В противном случае, будет выброшено исключение [[yii\base\InvalidRouteException]].
- Контроллер последовательно вызывает метод
beforeAction()
приложения, модуля (если контроллер принадлежит модулю) и самого контроллера.- Если один из методов вернул
false
, то остальные, не вызванные методыbeforeAction
будут пропущены, а выполнение действия будет отменено; - По-умолчанию, каждый вызов метода
beforeAction()
вызовет событиеbeforeAction
, на которое вы можете назначить обработчики.
- Если один из методов вернул
- Контроллер запускает действие:
- Параметры действия будут проанализированы и заполнены из данных запроса.
- Контроллер последовательно вызывает методы
afterAction
контроллера, модуля (если контроллер принадлежит модулю) и приложения.- По-умолчанию, каждый вызов метода
afterAction()
вызовет событиеafterAction
, на которое вы можете назначить обработчики.
- По-умолчанию, каждый вызов метода
- Приложение, получив результат выполнения действия, присвоит его объекту response.
Лучшие практики
В хорошо организованных приложениях контроллеры обычно очень тонкие и содержат лишь несколько строк кода. Если ваш контроллер слишком сложный, то обычно это означает, что вам надо провести его рефакторинг и перенести часть кода в другие места.
В целом, контроллеры
- могут иметь доступ к данным запроса;
- могут вызывать методы моделей и других компонентов системы с данными запроса;
- могут использовать представления для формирования ответа;
- не должны заниматься обработкой данных, это должно происходить в слое моделей;
- должны избегать использования HTML или другой разметки, лучше это делать в представлениях.