Controllers

Controllers are part of the MVC architecture. They are objects of classes extending from cot\controllers\BaseController and are responsible for processing requests and generating responses. In particular, after taking over the control from applications, controllers will analyze incoming request data, retrieve the necessary data from the database, pass the results to the templating engine, and finally generate outgoing responses.

Controllersare an alternative to the moduleandstandalonehooks.

#1. Actions

Controllers are composed of actions which are the most basic units that end users can address and request for execution. A controller can have one or multiple actions.

Actionsaremethods of the controllerclassstartingwith the prefixaction.

The following example shows a post controller with two actions: view and 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));
   }
}

In the view action (defined by the actionView() method), the code first loads the data according to the requested post ID. If the data is successfully loaded, the code will render it using the template engine. Otherwise, an exception will be thrown.

In the create action (defined by the actionCreate() method), the code retrieves the data from the request and saves it. Then, it redirects the browser to the view action with the ID of the newly created post.

#2. Routes

End users address actions through the so-called routes. A route is a string that consists of the following parts:

- Extension code: GET parameter e;
- Controller ID: GET parameter n. A string that uniquely identifies the controller among all other controllers of the same extension;
- Action ID: GET parameter a. A string that uniquely identifies the action among all other actions of the same controller.

Routes take the following format:

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

So if a user requests with the URL https://your-domain.tld?e=blog&n=posts&a=view&id=123, the view action in theposts controller of the blog extension will be called.

#3. Creating Controllers

Controllers should extend from cot\controllers\BaseController or its child classes.

The following code defines a post controller:

namespace cot\modules\blog\controllers;

use cot\controllers\BaseController;

class PostController extends BaseController
{
}

The controllers are locatedin the controllersfolderin the rootfolder of the extension. The controllers of the adminpart of the site are locatedin the controllers/adminfolder.

#3.1. Controller IDs

Usually, a controller is designed to handle the requests regarding a particular type of resource. For this reason, controller IDs are often nouns referring to the types of the resources that they are handling. For example, you may use article as the ID of a controller that handles article data.

By default, controller IDs should contain these characters only: English letters in lower case, digits, underscores and hyphens. For example, article and post-comment are both valid controller IDs, while article?, PostComment, admin\post are not.

#3.2. Controller Class Naming

Controller class names can be derived from controller IDs according to the following procedure:

  1. Turn the first letter in each word separated by hyphens into upper case
  2. Remove hyphens
  3. Append the suffix Controller
  4. Prepend the controller namespace

The following are some examples, assuming the controller namespace takes the default value cot\modules\blog\controllers

  • article becomes cot\modules\blog\controllers\ArticleController;
  • post-comment becomes cot\modules\blog\controllers\PostCommentController;

Controller classes must be autoloadable. For this reason, in the above examples, the article controller class should be saved in the file modules/blog/controllers/ArticleController.php

#3.3. Default Controller

If the route does not contain a controller ID, IndexController will be called if it exists.

#4. Creating Actions

Creating actions can be as simple as defining the so-called action methods in a controller class. An action method is a public method whose name starts with the word action. The return value of the action method is the response data that will be sent to the end user. The following code defines two actions, index and 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';
   }
}

#4.1. Action IDs

An action is often designed to perform a particular manipulation of a resource. For this reason, action IDs are usually verbs, such as view, update, etc.

By default, action IDs should contain these characters only: English letters in lower case, digits, underscores, and hyphens (you can use hyphens to separate words). For example, view, update2, and comment-post are all valid action IDs, while view? and Update are not.

You can create actions in two ways: inline actions and standalone actions. An inline action is defined as a method in the controller class, while a standalone action is a class extending cot\controllers\BaseAction or its child classes. Inline actions take less effort to create and are often preferred if you have no intention to reuse these actions. Standalone actions, on the other hand, are mainly created to be used in different controllers.

#4.2. Inline Actions

Inline actions refer to the actions that are defined in terms of action methods as we just described.

The names of the action methods are derived from action IDs according to the following procedure:

  1. Turn the first letter in each word of the action ID into upper case.
  2. Remove hyphens.
  3. Prepend the prefix action.

For example, index becomes actionIndex, and hello-world becomes actionHelloWorld.

The names of the action methods are case-sensitive. If you have a method named ActionIndex, it will not be considered as an action method, and as a result, the request for the index action will result in an exception. Also note that action methods must be public. A private or protected method does NOT define an inline action.

Inline actions are the most commonly defined actions because they take little effort to create. However, if you plan to reuse the same action in different places, or if you want to redistribute an action, you should consider defining it as a standalone action.

#4.3. Standalone Actions

Standalone actions are defined in terms of action classes extending cot\controllers\BaseAction or its child classes.

To use a standalone action, you should declare it in the action map by overriding the cot\controllers\BaseController::actions() method in your controller classes like the following:

public static function actions(): array
{
   return [
       // declares "error" action using a class name
       'error' => cot\modules\blog\controllers\actions\ErrorAction::class,
       
       // declares "view" action using a configuration array
       'view' => [
           'class' => cot\modules\blog\controllers\actions\ViewAction::class,
           'viewPrefix' => '',
       ],
   ];
}

As you can see, the actions() method should return an array whose keys are action IDs and values the corresponding action class names or configurations. Unlike inline actions, action IDs for standalone actions can contain arbitrary characters, as long as they are declared in the actions() method.

To create a standalone action class, you should extend cot\controllers\BaseAction or a child class, and implement a public method named run(). The role of the run() method is similar to that of an action method. For example,

namespace cot\modules\blog\controllers\actions;

use cot\controllers\BaseAction;

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

#4.4. Action Results

The return value of an action method or of the run() method of a standalone action is significant. It stands for the result of the corresponding action.

The return value can be a string that will be used as the body of the response sent to the user.

The following example shows how an action can redirect the user's browser to a new URL:

public function actionForward()
{
   // redirect the user browser to https://example.com
   cot_redirect('https://example.com');
}

#4.5. Default Action

Each controller has a default action specified via thecot\controllers\BaseController::$defaultAction property. When a route contains the controller ID only, it implies that the default action of the specified controller is requested.

By default, the default action is set as index. If you want to change the default value, simply override this property in the controller class, like the following:

namespace cot\modules\blog\controllers;

use cot\controllers\BaseController;

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

#5. Controller Lifecycle

When processing a request, an application will create a controller based on the requested route. The controller will then undergo the following lifecycle to fulfill the request:

  1. The controller calls the beforeAction() method. If the method returns false, the action will be canceled.
  2. The controller runs the action.
  3. The controller calls the afterAction() method, which passes the result of the action. This method can be used for post-processing the result.
  4. After receiving the result, the application will send it to the user.

No comments yet
Only registered users can post new comments