Контроллеры

Контроллеры являются частью MVC архитектуры. Это объекты классов, унаследованных от cot\controllers\BaseController, отвечающие за обработку запроса и генерирование ответа. В сущности, после обработки запроса приложением, контроллеры проанализируют входные данные, извлекают нужные данные из БД, передают результаты  в шаблонизатор, и в конечном итоге генерируют исходящие ответы.
Контроллеры - это альтернатива хукам module и standalone.

#1. Действия (Actions)

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

Действия - это методы класса контроллера, начинающиеся с префикса action

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

namespace cot\modules\post\controllers;

use cot\controllers\BaseController;
use cot\exceptions\NotFoundHttpException;

class PostController extends BaseController
{
   public function actionView(): string
   {
       $postId = cot_import('id', 'G', 'INT');
    
       $post = PostRepository::getById($id);
       if ($post === null) {
           throw new NotFoundHttpException;
       }
       $template = cot_tplfile('blog.post');
       $t = new XTemplate($template);
       $t->assign([
            'ID' => $post['id'],
            'TITLE' => htmlspecialchars($post['title']),
            'TEXT' => $post['text'],
       ]);    
        
       $t->parse('MAIN');
       return $t->text('MAIN');
   }
   
   public function actionCreate()
   {
       $title = cot_import('title', 'P', 'TXT');
       $text = cot_import('text', 'P', 'TXT');
        
       Cot::$db->insert(Cot::$db->posts, ['title' => $title, 'text' => $text]);
       $id = Cot::$db->lastInsertId();
        
       cot_redirect(cot_url('post', ['n' => 'post', 'a' => 'view', 'id' => $id], '', true));
   }
}

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

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

#2. Маршруты

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

  • Код расширения: GET параметр e;
  • ID контроллера: GET параметр n. Cтрока, которая уникально идентифицирует контроллер среди всех других контроллеров одного и того же расширения;
  • ID действия: GET параметр a. строка, которая уникально идентифицирует действие среди всех других действия одного и того же контроллера.

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

https://your-domain.tld?e=extensionCode&n=ControllerID&a=ActionID

Таким образом, если пользователь запрашивает URL https://your-domain.tld?e=blog&n=posts&a=view&id=123 то view действие контроллера posts расширения blog будет вызвано.

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

Контроллеры должны быть унаследованы от cot\controllers\BaseController или его потомков

Следующий код определяет post контроллер:

namespace cot\modules\blog\controllers;

use cot\controllers\BaseController;

class PostController extends BaseController
{
}

Контроллеры располагаются в папке controllers в корневой папке расширения. Контроллеры административной части сайта располагаются в папке controllers/admin.

#4. ID контроллеров

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

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

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

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

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

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

  • article соответствует cot\modules\blog\controllers\ArticleController;
  • post-comment соответствует cot\modules\blog\controllers\PostCommentController;

Классы контроллеров должны быть автозагружаемыми. Именно по этой причине, в вышеприведенном примере, контроллер article должен быть сохранен в файл modules/blog/controllers/ArticleController.php

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

Если в маршруте отсутствует ID контроллера, будет вызван IndexController, если он есть.

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

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

namespace cot\modules\blog\controllers;

use cot\controllers\BaseController;

class PostController extends BaseController
{
   public function actionIndex(): string
   {
       $template = cot_tplfile('blog.index');
       $t = new XTemplate($template);
        
       $t->parse('MAIN');
       return $t->text('MAIN');
   }
   
   public function actionHelloWorld(): string
   {
       return 'Hello World';
   }
}

#8. ID действий

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

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

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

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

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

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

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

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

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


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

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

Отдельные действия определяются в качестве классов, унаследованных от cot\controllers\BaseAction или его потомков.

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

public static function actions(): array
{
   return [
       // объявляет "error" действие с помощью названия класса
       'error' => cot\modules\blog\controllers\actions\ErrorAction::class,
       
       // объявляет "view" действие с помощью конфигурационного массива
       'view' => [
           'class' => cot\modules\blog\controllers\actions\ViewAction::class,
           'viewPrefix' => '',
       ],
   ];
}

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

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

namespace cot\modules\blog\controllers\actions;

use cot\controllers\BaseAction;

class HelloWorldAction extends BaseAction
{
   public function run(): string
   {
       return "Hello World";
   }
}

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

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

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

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

public function actionForward()
{
   // перенаправляем браузер пользователя на https://example.com
   cot_redirect('https://example.com');
}

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

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

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

namespace cot\modules\blog\controllers;

use cot\controllers\BaseController;

class PostController extends BaseController
{
   public static $defaultAction = 'home';
   
   public function actionHome()
   {
       return "Hello World";
   }
}

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

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

  1. Контроллер вызывает метод beforeAction(). Если метод вернул false, действие будет отменено.
  2. Контроллер запускает действие
  3. Контроллер вызывает метод afterAction() в который передается результат выполнения действия. Этот метод может использоваться для постобработки результата.
  4. Приложение получив результат отправит его пользователю.

Комментарии отсутствуют
Добавление комментариев доступно только зарегистрированным пользователям