Контролери

Контролери є частиною 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. Розділ Маршрутизація та створення URL містить більш детальну інформацію про те, як маршрути співставляються із діями.

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

У [[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/post2-comment має знаходитись у файлі @app/controllers/admin/Post2CommentController.php.

Інформація: Останній приклад admin/post2-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.

Примітка: Назви імен дій є регістрозалежними. Якщо у вас є метод 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]] або його нащадків, і реалізувати публічний ("public") метод run(). Роль метода run() аналогічна іншим методам дій. Наприклад,

<?php
namespace app\components;

use yii\base\Action;

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

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

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

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

  • Для [[yii\web\Application|веб додатків]], значення, що повертається, також може бути довільними даними, яке буде призначене до [[yii\web\Response::data]], а потім конвертоване у рядок, що представляє тіло відповіді.
  • Для [[yii\console\Application|консольних додатків]], значення, що повертається, також може бути числом, що представляє [[yii\console\Response::exitStatus|статус виходу]] виконання команди.

В вищенаведених прикладах всі результати дій є рядками, які будуть використані у якості тіла відповіді користувачу. Наступний приклад показує як дія може перенаправити браузер користувача на новий URL за допомогою повернення об’єкта відповіді (оскільки метод [[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');
    }
}

Життєвий цикл контролера

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

  1. Метод [[yii\base\Controller::init()]] буде викликаний після того, як контролер був створений і сконфігурований.
  2. Контролер створить об’єкт дії, базуючись на ID дії, яку було запитано:
  3. Якщо ID дії не вказано, то буде використано [[yii\base\Controller::defaultAction|ID дії за замовчуванням]];
  4. Якщо ID дії знайдено у [[yii\base\Controller::actions()|мапі дій]], то буде створено окрему дію;
  5. Якщо ID дії відповідає методу дії, то буде створено вбудовану дію;
  6. В іншому випадку, буде отримане виключення [[yii\base\InvalidRouteException]].
  7. Контролер послідовно викликає метод beforeAction() додатка, модуля (якщо контролер належить модулю) і самого контролера.
  8. Якщо один із методів повернув false, то решта невикликаних методів beforeAction будуть пропущені, а виконання дії буде відмінено;
  9. За замовчуванням, кожний виклик метода beforeAction() викликає подію beforeAction, на яку ви можете призначити обробники.
  10. Контролер виконує дію:
  11. Параметри дії будуть проаналізовані і заповнені із даних запиту.
  12. Контролер послідовно викликає методи afterAction контролера, модуля (якщо контролер належить модулю) і додатка.
  13. За замовчуванням, кожний виклик метода afterAction() викликає подію afterAction, на яку ви можете призначити обробники.
  14. Додаток, отримавши результат виконання дії, привласнює його об’єкту response.

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

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

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

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