MVC + Scenario vs. Fat Controllers

MVC + Scenario Versus Thick Controllers


Modern PHP frameworks (Symphony, Laravel, further everywhere) convincingly show that implementing the Model-View-Controller pattern is not so easy. All implementations are for some reason inclined to Thick Controllers ( fat controllers), condemned by all, both developers and frameworks themselves.


Why is that so? And is it possible to cope with this somehow? Let's figure it out.


Terminology


  • Model - a model (driver of the requested data)
  • View - view (model data designer)
  • Controller - controller (model-presentation coordinator as requested)
  • Template - presentation template
  • Rendering - rendering (formation, design of the image of the presentation)
  • Renderer - renderer (shaper, designer of the presentation image)

Thick controller


Here is a typical Fat Controller:


classUserController{
    /**
     * Действие контроллера
     * Возвращает приветствие юзеру с заданным ID
     */publicfunctionactionUserHello($userId){
        // Получаем имя и фамилию юзера из модели юзера (База Данных)
        $user = UserModel::find($userId);
        // Шаблону представления нужно полное имя юзера - делаем его
        $name = $user->firstName.' '.$user->lastName;
        // Создаем представление с нужным шаблоном и полным именем
        $view = new View('hello', ['name' => $name]);
        // Рендерим (создаем образ) представление и возвращаем приветствиеreturn $view->render();
    }
}

What do we see? We see a vinaigrette! Everything that is possible is mixed in the controller - both the model and the presentation, and, actually, the controller itself!


We see the names of the model and the template, tightly wired into the controller. This is not good. We see the manipulations with the data of the model in the controller - the formation of a full name from the name and surname. And this is not good.


And yet: we do not see this example explicitly, but it is implicitly. Namely: there is only one way of rendering (image formation)! Only one: the pattern in the php file! And if I want a pdf? And if I want not in the file, but in the php line? I had projects with an elaborate design on a hundred small templates. It was necessary to blunder a renderer for string patterns. Not overheated of course, but the matter is in principle.


Brief summary:


Modern frameworks have common for all the flaws in the implementation of MVC:
  1. Narrow interpretation of MVC-views (View) only as "View with a template in a PHP file" instead of "View with any renderer" .
  2. Narrow interpretation of the MVC model is only as “Database Domain Model” instead of “Any data compiler for presentation” .
  3. They provoke the use of so-called "Fat Controllers" containing simultaneously all the logic: business, presentation and interaction. This completely destroys the main goal of MVC - the division of responsibilities between the components of the triad.

To eliminate these shortcomings, it would be nice to take a closer look at the MVC components.


View is a renderer


Look at the first flaw:


  1. Narrow interpretation of MVC-views (View) only as "View with a template in a PHP file" instead of "View with any renderer" .

Everything is quite simple here - the solution to the problem is already indicated in the problem statement itself. We just have to say that any renderer can use the view. To implement this, simply add a new property rendererto the View class:


classView{
    public $template, $data, $renderer;
    publicfunction__costruct($template, $data, $renderer = NULL){}
}

So, we have defined a new property rendererfor the view. In the most general case, the value of this property can be any callablefunction that forms an image of the data passed to it using the transferred template.


Most applications use only one renderer, and even if they use several, one of them is preferred. Therefore, the argument is rendererdefined as optional, assuming the presence of some default renderer.


Simply? Simply. In fact, not so easy. The fact is that the one Viewin MVC is not exactly the one Viewin the frameworks. The one Viewthat is in frameworks cannot live without a template. But the one Viewthat is in MVC for some reason does not know anything about these same templates. Why? Yes, because for MVC View, this is any data converter of the model into an image , and not only and exclusively a template engine. When we write something like this in the request handler:


$name = 'дядя Ваня';
return"Hello, {$name}!";

or even:


$return json_encode($name); // Ajax response

then we really define the one Viewthat is in MVC, without touching any of those Viewin the frameworks!


But now everything is really simple: those Viewin frameworks are a subset of those Viewin MVC. Moreover, a very narrow subset, namely, it is only template engines based on PHP files.


Summary: namely рендерер, i.e. any data image designer is one Viewin MVC. And those Viewin frameworks are just a variation рендереров.


Domain Model / View Model (ViewModel / DomainModel)


And now we will look at the second lack:


  1. Narrow interpretation of the MVC model is only as “Database Domain Model” instead of “Any data compiler for presentation” .

It is obvious to all that the MVC model is a complex thing that consists of other pieces. In the community there is agreement on the decomposition of the model into two components: the domain model (DomainModel) and the view model (ViewModel).


A domain model is what is stored in databases, i.e. normalized model data. Type, 'name' and 'last name' in different fields. The frameworks are occupied precisely with this part of the model simply because data storage is its own universe, well studied.


However, the application needs aggregated, not normalized data. Domain data must be compiled into images of the type: "Hello, Ivan!", Or "Dear Ivan Petrov!", Or even "For Ivan a Petrov and !". These converted data are attributed to another model - the representation model. So, this part of the model is so far ignored by modern frameworks. Ignored because there is no agreement on how to deal with it. And if the frameworks do not provide solutions, then programmers follow the simplest path - they throw the presentation model into the controller. And get the hated, but the inevitable Thick Controllers!


Total: to implement MVC, you must implement a view model. No other options. Considering that representations and their data can be any, we state that we have a problem.


Scenario vs Fat Controllers


The last flaw of frameworks remains:


  1. They provoke the use of so-called "Fat Controllers" containing simultaneously all the logic: business, presentation and interaction. This completely destroys the main goal of MVC - the division of responsibilities between the components of the triad.

Here we get to the basics of MVC. Let's bring clarity. So, MVC assumes such a distribution of responsibilities between the components of the triad:


  • The controller is the interaction logic , i.e. interactions with both the outside world (request - response) and the internal one (Model - Presentation),
  • Model - business logic , i.e. generating data for a specific query,
  • Presentation is presentation logic , i.e. decoration of data generated by the Model.

Go ahead. Two levels of responsibility are clearly visible:


  • The organizational level is the Controller,
  • The executive level is Model and View.

In simple terms, the Controller rules, Model and View plow. This is if in a simple way. And if not in a simple way, but more specifically? How exactly does a controller go? And how exactly do they plow Model and Presentation?


The controller steers so:


  • Receives a request from the application,
  • Decides which Model and which View to use for this request,
  • Calls the selected Model and receives data from it,
  • Calls the selected View with the data received from the Model,
  • Returns the data decorated by the View back to the application.

Something like that. Essential in this scheme is that the Model and Presentation are links in the query execution chain. Moreover, by successive links: first, the Model converts the request into some data, then this data of the Model is converted by the Presentation into an answer, decorated in the way a particular request should be. Like, the humanoid request is decorated with visual templates, the android request is decorated with JSON encoders.


Now let's try to figure out how exactly the performers are plowing - Model and Presentation. Above, we said that there is a consensus about the decomposition of the Model into two sub-components: the Domain Model and the Presentation Model. This means that there can be more performers - not two, but three. Instead of the execution chain


Модель >> Представление

may well be a chain


Модель домена>> Модель представления>>Представление

By itself, the question arises: why only two or three? And if you need more? The natural answer - yes, for God's sake, how much is necessary, and take so much!


Other useful artists can be seen right away: validators, redirectors, various renderers, and in general everything that is unpredictable, but anything.


Let's recap:


  • The MVC ( Модель- Представление) executive level can be implemented as a chain of links, where each link converts the output of the previous link to the input for the next.
  • The input of the first link is the application request.
  • The output of the last link is the application's response to the request.

I called this chain Сценарием( Scenario), but for the links of the chain with the name I have not yet decided. The current options are the scene (as part of the script), the filter (as the data converter), the action of the script. Generally speaking, the name for the link is not so important, there is a more significant thing.


The consequences of the Script are significant. Namely: The script assumed the main responsibility of the Controller - to determine the Model and Presentation required for the request and to launch them. Thus, the controller has only two responsibilities: interaction with the outside world (request-response) and the launch of the script. And this is good in the sense that all the components of the MVC triad are successively decomposed and become more specific and manageable. And another good thing is that the MVCS controller becomes a purely internal immutable class, and therefore, even in principle, it cannot become fat.


Using Scenarios leads to another variation of the MVC pattern, I called this variation as MVCS- Model-View-Controller-Scenario.


And a couple of lines about MVC decomposition. Modern frameworks, where all typical functions are decomposed to the limit, quite naturally took from the conceptual MVC some of the responsibilities for interacting with the outside world. Thus, the processing of a user's request is done by specially trained classes of type HTTP запросand Роутер. As a result, the Controller does not receive the user's initial request, but some refinedдействие, and it allows you to isolate the controller from the specifics of the request. Similarly, isolation from the HTTP response is done, allowing the MVC module to define its own type of response. In addition, the frameworks fully implemented the two components of MVC - Domain Model and View Template, however, we have already discussed this. All this means that the specification and specification of MVC is constantly and continuously, and this is good.


MVCS example


And now let's see how the example of the Fat Corroller at the beginning of this article can be implemented in MVCS.


We start by creating the MVCS controller:


$mvcs = new MvcsController();

The MVCS controller receives a request from an external router. Let the router convert the URI of the form 'user / hello / XXX' into such an action and request parameters:


$requestAction = 'user/hello';  // Действие запроса
$requestParams = ['XXX'];   // Параметры действия - ИД юзера

Considering that the MVCS controller accepts not URIs, but scripts, we need to match a certain script to the request action. This is best done in the MVCS container:


// Определяем сценарий MVCS для URI запроса
$mvcs->set('scenarios', [
    'user/hello' => 'UserModel > UserViewModel > view, hello',
    ...,
]);

Let's take a closer look at this scenario. This is a chain of three data converters separated by '>':


  • 'UserModel' is the name of the 'User' Domain Model, the model input will be the query parameters, the output is the model data itself,
  • 'UserViewModel' is the name of the View Model, which converts the domain data into view data,
  • 'view, hello' is the system view 'template' for a PHP template with the name 'hello'.

Now we just need to add two converter involved in the script as a function-circuit to the MVCS container:


// Модель домена UserModel
$mvcs->set('UserModel', function($id){
    $users = [
        1 => ['first' => 'Иван', 'last' => 'Петров'],
        2 => ['first' => 'Петр', 'last' => 'Иванов'],
    ];
    returnisset($users[$id]) ? $users[$id] : NULL;
});
// Модель представления UserViewModel
$mvcs->set('UserViewModel', function($user){
    // Слепить данные для PHP шаблона типа: 'echo "Hello, $name!"';return ['name' => $user['first'].' '.$user['last']];
});

And it's all! For each request, it is necessary to determine the corresponding script and all its scenes (except for the system ones, such as 'view'). And nothing more.


And now we are ready to test MVCS for different requests:


// Получить из контейнера сценарий для текущего запроса
$scenarios = $mvcs->get('scenarios');
$scenario = $scenarios[$requestAction];
// Исполнить сценарий с параметрами текущего запроса...// Для запроса 'user/hello/1' получим 'Иван Петров' декорированный шаблоном 'hello'
$requestParams = ['1'];
$response = $mvcs->play($scenario, $requestParams);
// Для запроса 'user/hello/2' получим 'Петр Иванов' декорированный шаблоном 'hello'
$requestParams = ['2'];
$response = $mvcs->play($scenario, $requestParams);

The PHP MVC implementation is hosted at github.com .
This example is in the exampleMVCS directory .

Also popular now: