Контролери

Контролери є частиною архітектури 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()) код спочатку завантажує модель, відповідно до запитуваного ідентифікатора моделі; Якщо модель успішно завантажена, то код відобразить її за допомогою представлення, під назвою view. В іншому випадку - буде отримане виключення.

У дії create (визначеній методом actionCreate()) подібний код. Він спочатку намагається завантажити модель за допомогою даних із запиту і зберегти модель. Якщо все пройшло успішно, то код перенаправить браузер на дію view із ідентифікатором щойно створеної моделі. В іншому випадку - він відобразить представлення create, через яке користувач зможе вказати необхідні дані.

Маршрути

Кінцеві користувачі звертаються до дій з допомогою так названих маршрутів. Маршрут це текстовий рядок, який складається з наступних частин:

  • ідентифікатор модуля: він існує, тільки якщо контролер належить не додатку, а модулю;
  • ідентифікатор контролера: текстовий рядок, який унікально ідентифікує контролер серед всіх інших контролерів одного і того ж додатка (або одного й того ж модуля, якщо контролер належить модулю);
  • ідентифікатор дії: текстовий рядок, який унікально ідентифікує дію серед всіх інших дій одного й того ж контролера.

Маршрути можуть мати наступний формат:

ControllerID/ActionID

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

ModuleID/ControllerID/ActionID

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

Створення контролерів

У веб-додатках контролери повинні бути успадкованими від класу yii\web\Controller або його нащадків. Аналогічно для консольних додатків, контролери повинні бути успадкованими від класу yii\console\Controller або його нащадків. Наступний код визначає контролер site:

namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
}

Ідентифікатори контролерів

Зазвичай контролер зроблений таким чином, що він повинен обробляти запити, які пов’язані з певним ресурсом. Саме з цієї причини, ідентифікатори контролерів зазвичай є іменниками, які посилаються на ресурс, який вони обробляють. Наприклад, ви можете використовувати article в якості ідентифікатора контролера, який відповідає за обробку даних статей.

За замовчуванням, ідентифікатори контролерів мають містити тільки наступні символи: англійські букви в нижньому регістрі, цифри, підкреслення, тире і слеш. Наприклад, обидва ідентифікатори контролера article та post-comment є прийнятними, в той час, як article?, PostComment, admin\post не є такими.

Ідентифікатор контролера також може містити префікс під-директорії. Наприклад, у admin/article частина article відповідає контролеру в під-директорії admin простору імен контролера. Допустимими символами для префіксів під-директорій є: англійські букви в нижньому і верхньому регістрах, цифри, символи підкреслення і слеш, де слеш використовується в якості роздільника для багаторівневих під-директорій (наприклад, panels/admin).

Іменування класів контролерів

Назви класів контролерів можуть бути отримані із ідентифікаторів контролерів наступним чином:

  1. Привести у верхній регістр перший символ в кожному слові, розділеному дефісами. Зверніть увагу, що, якщо ідентифікатор контролера містить слеш, то дане правило поширюється тільки на частину після останнього слеша в ідентифікаторі контролера.
  2. Прибрати дефіси і замінити будь-який прямий слеш на зворотний.
  3. Додати суфікс Controller.
  4. Додати на початок простір імен контролера.

Нижче наведено декілька прикладів, з урахуванням того, що простір імен контролера має значення за замовчуванням 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 показує яким чином ви можете розташувати контролер в під-директорії простору імен контролера. Це дуже зручно, коли ви хочете організувати свої контролери у декілька категорій і не хочете використовувати модулі.

Мапа контролерів

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

Ви можете налаштувати мапу контролерів в налаштуваннях додатка наступним чином:

[
    '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',
]

Створення дій

Створення дій може бути настільки ж простим, як і оголошення так званих методів дій у класі контролера. Метод дії це публічний метод, ім’я якого починається зі слова 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';
    }
}

Ідентифікатори дій

Частіше за все, дія розробляється для певної маніпуляції над ресурсом. З цієї причини ідентифікатори дій, в основному, є дієсловами, такими як view, update, і т. д.

За замовчуванням, ідентифікатори дій повинні містити тільки такі символи: англійські букви в нижньому регістрі, цифри, підкреслення і дефіси. (Дефіси можуть використовуються для поділу слів.) Наприклад, view, update2 і comment-post є допустимими ідентифікаторами дій, в той час, як view? та Update не є такими.

Ви можете створювати дії двома способами: вбудовані дії і автономні дії. Вбудована дія є методом, визначеним в класі контролера, тоді як автономна дія є класом, успадкованим від yii\base\Action або його нащадків. Вбудовані дії вимагають менше зусиль для створення і, в основному, використовуються якщо у вас немає потреби у повторному використанні цих дій. Автономні дії, з іншого боку, в основному створюються для використання в різних контролерах або для розподілення у вигляді розширень.

Вбудовані дії

Вбудовані дії це ті дії, які визначені у рамках методів контролера, як це було щойно описано.

Назви методів дій можуть бути отримані із ідентифікаторів дій наступним чином:

  1. Привести перший символ кожного слова в ідентифікаторі дії у верхній регістр.
  2. Прибрати дефіси.
  3. Додати префікс action.

Наприклад, index перетвориться у actionIndex, а hello-world перетвориться у actionHelloWorld.

Note: Назви імен методів дій є регістр-залежними. Якщо у вас є метод ActionIndex, то його не буде враховано як метод дії, і в результаті, запит до дії index призведе до отримання виключення. Також слід врахувати, що методи дій повинні бути публічними ("public"). Приватні ("private") або захищені ("protected") методи НЕ визначають вбудованих дій.

В основному використовуються вбудовані дії, оскільки для їх створення не потрібного багато зусиль. Тим не менше, якщо ви плануєте повторно використовувати деякі дії у різних місцях або якщо ви хочете перерозподілити дії, ви повинні визначити їх як автономні дії.

Автономні дії

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

Для використання автономної дії, ви маєте вказати її у мапі дій за допомогою перевизначення методу actions() у вашому класі контролера, наступним чином:

public function actions()
{
    return [
        // оголошує дію "error" за допомогою назви класу
        'error' => 'yii\web\ErrorAction',

        // оголошує дію "view" за допомогою конфігураційного масиву
        'view' => [
            'class' => 'yii\web\ViewAction',
            'viewPrefix' => '',
        ],
    ];
}

Як ви можете бачити, метод actions() повинен повернути масив, ключами якого є ідентифікатори дій, а значеннями - відповідні назви класів дій або конфігурації. На відміну від вбудованих дій, ідентифікатори автономних дій можуть містити довільні символи, доки вони визначені у методі actions().

Для створення класу автономної дії, ви повинні успадкуватись від класу yii\base\Action або його нащадків, і реалізувати публічний ("public") метод yii\base\Action::run(). Роль метода yii\base\Action::run() аналогічна іншим методам дій. Наприклад,

<?php
namespace app\components;

use yii\base\Action;

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

Результати дій

Значення, що повертається від методу дії або методу yii\base\Action::run() автономної дії дуже важливе. Воно є результатом виконання відповідної дії.

Значення, що повертається, може бути об’єктом відповіді, яке буде відправлено кінцевому користувачу.

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

У вищенаведених прикладах всі результати дій є текстовими рядками, які будуть використані у якості тіла відповіді для відправлення кінцевому користувачу. Наступний приклад показує як дія може перенаправити браузер користувача на новий URL за допомогою повернення об’єкта відповіді (оскільки метод redirect() повертає об’єкт response):

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

Параметри дій

Методи дій для вбудованих дій і методи yii\base\Action::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'].

Якщо ви хочете, щоб параметр дії приймав масив значень, ви повинні вказати тип 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. Коли маршрут містить тільки ідентифікатор контролера, то розуміється, що було запитана дія контролера за замовчуванням.

За замовчуванням, ця дія має значення 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. Контролер створить об’єкт дії, базуючись на ідентифікаторі дії, яку було запитано:
    • Якщо ідентифікатор дії не вказано, то буде використано ідентифікатор дії за замовчуванням;
    • Якщо ідентифікатор дії знайдено у мапі дій, то буде створено автономну дію;
    • Якщо ідентифікатор дії відповідає методу дії, то буде створено вбудовану дію;
    • В іншому випадку, буде отримане виключення yii\base\InvalidRouteException.
  3. Контролер послідовно викликає метод beforeAction() додатка, модуля (якщо контролер належить модулю) і самого контролера:
    • Якщо один із методів повернув false, то решта невикликаних методів beforeAction будуть пропущені, а виконання дії буде скасовано;
    • За замовчуванням, кожний виклик метода beforeAction() викликає подію beforeAction, на яку ви можете призначити обробник.
  4. Контролер виконує дію:
    • Параметри дії будуть проаналізовані та заповнені із даних запиту.
  5. Контролер послідовно викликає методи afterAction контролера, модуля (якщо контролер належить модулю) і додатка:
    • За замовчуванням, кожний виклик метода afterAction() викликає подію afterAction, на яку ви можете призначити обробник.
  6. Додаток, отримавши результат виконання дії, привласнює його об’єкту response.

Кращі практики

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

В цілому, контролери

  • можуть мати доступ до даних запиту;
  • можуть викликати методи моделей та інших компонентів системи із даними запиту;
  • можуть використовувати представлення для формування відповіді;
  • НЕ повинні займатись обробкою даних - це має відбуватися на рівні моделей;
  • мають уникати використання HTML або іншої розмітки - краще це робити у представленнях.