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 Fat Controllers ( fat controllers
), condemned by all, and developers, and the frameworks themselves.
Why is that so? And is it possible to cope with this somehow? Let's figure it out.
Here is a typical Fat Controller:
class UserController { /** * * ID */ public function actionUserHello($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:
- Narrow interpretation of MVC-views (View) only as "View with a template in a PHP file" instead of "View with any renderer" .
- Narrow interpretation of the MVC model is only as “Database Domain Model” instead of “Any data compiler for presentation” .
- 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.
Look at the first flaw:
- 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 the new renderer
property to the View class:
class View { public $template, $data, $renderer; public function __costruct($template, $data, $renderer = NULL) {} }
So, we have defined a new renderer
property for the view. In the most general case, the value of this property can be any callable
function that forms an image of the data passed to it using the passed template.
Most applications use only one renderer, and even if they use several, one of them is preferred. Therefore, the renderer
argument is defined as optional, assuming the presence of some default renderer.
Simply? Simply. In fact, not so easy. The fact is that the View
, which in MVC is not quite that View
, which is in frameworks. The View
, which is in frameworks, cannot live without a template. But the View
, which 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 View
that is in MVC, without touching any View
that is in the frameworks!
But now everything is really simple: those View
, which in frameworks are a subset of those View
, which are in MVC. Moreover, a very narrow subset, namely, it is only template engines based on PHP files.
Summary: it is the , i.e. any data image designer is the
View
in MVC. And those View
, which are in frameworks, are just a kind of .
And now we will look at the second lack:
- 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: a domain model (DomainModel) and a 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.
The last flaw of frameworks remains:
- 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:
Go ahead. Two levels of responsibility are clearly visible:
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:
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 is needed. 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 is - for God's sake, how much is necessary, take as 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
, and for the links of the chain I have not yet decided on the name of the chain. 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 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 the user's request is done by specially trained classes like HTTP
and . As a result, the Controller does not receive the user's initial request, but some refined
, and this allows the controller to be isolated 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.
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 '>':
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' => ''], ]; return isset($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 located in the MVCSexample
directory.
Source: https://habr.com/ru/post/424595/
All Articles