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.
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.
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.
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.
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.
Controller class names can be derived from controller IDs according to the following procedure:
ControllerThe 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
If the route does not contain a controller ID, IndexController will be called if it exists.
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';
}
}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.
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:
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.
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";
}
}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');
}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";
}
}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:
beforeAction() method. If the method returns false, the action will be canceled.afterAction() method, which passes the result of the action. This method can be used for post-processing the result.