MVC and Model 2. Component Knowledge and Responsibilities
For a long time I studied the MVC pattern. More than a year and a half has passed since I first met him and during all this time I couldn’t sort in my head the areas of responsibility of the three components of the pattern.
MVC is a sophisticated yet stunningly elegant architectural solution. I can’t imagine what modern applications would turn into without this pattern.
On the Internet, all the information is scattered in some pieces, and now, after a year and a half of acquaintance and endless research, I can finally say: yes, I know this pattern far and wide.
I decided to collect all the missing information in one place. This was the reason for writing the article.
tl; dr: read the result. I ask the rest to get comfortable.
According to the Gang of Four, MVC is nothing more than strategy + linker + observer. The controller and view are registered with the model as observers. A view registers with itself a controller as an object of strategy, realizing its behavior through composition. The model is engaged in alerting observers when a state changes. A view embeds other views within itself, which usually depend on their models and present different behavior to the user through other controllers. This is not an HMVC. This is a classic of the genre.
In web development, MVC is the most popular pattern. Today, the concept of an MVC framework is rather commonplace, and we are used to believing that people around us know about these frameworks enough to correctly divide the code into three or more components. Unfortunately, due to the widespread prevalence of MVC frameworks and their attempts to provide their users with ease of development, application development and support, the basics of the underlying pattern are lost.
To begin with, MVC, as it is, cannot be implemented in a client-server application due to the lack of a constant runtime during the change of state of models. I am talking specifically about the observer. There is no point in updating the view after changing the model, because we get the view on the client, and the model lies far on the server. At one time, to solve this problem in Java Web Applications, the Model 2 pattern was developed , based on the good old MVC.
The essence of the pattern is to fine tune MVC to the http protocol and the realities of the Internet. This pattern considered the controller as an object that receives a request from a user. The controller must parse the request, based on it, instantiate a specific model and derive a specific view by forwarding the model and other data to the view. The view itself is a regular JSP. The model was used to communicate with the database and display table rows in the object (ORM). Looks damn familiar, right? And you don’t have any observers and linkers.
If you look closely, the difference in these two patterns is huge. Forwarding data from the controller to the view is already a gross violation of the MVC pattern. In the classical implementation, the view itself took data from the model directly or through the controller, but more often the controller was used to process user actions on the model using the user interface. That is, the controller, I repeat, is the behavior of the view. Slave. Not the master.
Unfortunately, this approach is not suitable for the http protocol. Connections to the server are disconnected, the application stops working, and in the case of PHP, it completely dies before the next request.
Model 2, like MVC, is inherent in all three patterns, only their implementation is somewhat different from the classical object implementation described by the “gang of four”.
The linker remained, but it is implemented through the creation of complex template files, whether it be JSP, PHP, ERB, and so on. But there are almost no methods for working with it. It is unlikely that in current applications we will be able to work with V as a part-whole relationship .
The strategy also did not go away, but everyone forgot about it, giving the controllers a new role - request handlers. In most current applications, a controller is a god who decides what, where and how to do. The controller calls the model, the controller saves the model, the controller writes to the model, the controller generates a view, the controller ... well, you understand. The controller does everything. From the usual behavior, the controller has turned into a GodObject.
Observer. This is the hardest part. Everyone knows how an observer works. Everyone knows how difficult and sometimes silly it is to implement a classic observer in the context of a single http request. This pattern was designed to update the data on the screen, and without web sockets, long pooling and other magic, this is extremely difficult to implement. In model 2, the observers are both the controller and the view, only now the reading of state changes occurs after the http request, and not all the time.
Very messy. However, if one talks about MVC and Model 2 in this way, many things themselves fall into place.
Next, I will try to describe the responsibilities of each component. I am not confined to a specific language, so in the text there may be combinations of the “class / structure” type, which are relevant for different languages at the present time. I want the article to be useful not only to ruby (railsl) / php developers. Everything written is also relevant for Golang with its structures, python, C #, Java, and, of course, JavaScript.
The article was written in several approaches, please do not judge duplicate information.
What can be said new about the models? Everything is perfectly said about them more than once, for example, in this wonderful article, the very essence of the model is perfectly revealed. A model is a state, it is a business logic, it is a single entry point to a table / mongo document / file. If you need to save state between user requests, the model is responsible for this. It is the model that must have fields, if we talk about it in the context of the object. The model cannot be stateless. The model is responsible for the validation and relevance of the data.
If there are entities that can describe any application without a UI, of course, then these are models.
In the classic MVC, a view is a separate object that displays information on the screen. If we talk about CLI applications, then this is some kind of stdout.println (). Web applications use Model 2 instead of the classic MVC, which implies that the view is just a file with markup and built-in template code. At first glance, it seems that a simple template does not have much weight in the system. In fact, it is the view that determines which actions are available to the user.
A view is part of two patterns: strategy and layout. The behavior of the view is implemented in the controller. It turns out that the controller is only part of a certain idea. And it can be replaced by another controller if we need different behavior for a specific view.
In fact, there is a lot of magic that slips through the fingers of “inattentive travelers”.
Greedy and Lazy Controllers
Due to the concept of “thick models, thin controllers”, I noticed a tendency to simplify the actions of controllers to one line - return render ('view'). As a result of this, most actions and data sampling are carried out directly in the view. This approach is argued in that the less data is transferred to the view, the more independent the views and controllers become from each other. Simplifications reach the point of absurdity when they start manipulating models directly in the template files. I don’t understand why everyone is so afraid to write code in controllers. If I pass a dozen data to a view, this does not mean that my view should use all of them. Imagine that you collected some things, put them in a box and sent by mail to a specific person. You don’t need these things anymore, and what the recipient will do with them, absolutely not important. He can choose a couple or two, and throw the rest. I even coined the term for myself: "greedy and lazy controllers."
In this articleIt is said that the action of the controller in the terminology of the framework is the controller in the terminology of MVC. Despite the fact that the article itself is amazing, I strongly disagree with this statement. Action is the user's action on the view. This is a defining part of the behavior. If we rewrite action, the behavior will change, but we will infiltrate the code of the finished class, and I do not like to change the class code for nothing. If we replace the controller, this will also lead to a change in the behavior of the view, but without changing the existing objects of the strategy. Take, for example, Angular.js. It clearly traces where the controller begins, and where its methods are. Any controller method interacts with the presentation in one way or another through the directives ng-click, ng-submit, and so on. From which we can conclude: the controller is a full-fledged class, and its methods (actions) are the actions of the view through which the user interacts with our application. If we replace the controller in the ng-controller directive, the behavior of our view will change depending on the implementation of the methods (actions) of a particular controller.
The second statement that really scares me is that controllers are the most reused parts of the application. What? For me, a controller is an exceptionally one-time code. Actions (action's) can be reused, I agree. Especially CRUD actions, in which you can simply specify the class / structure of the model with which this action should be associated. Duplication of code in controllers is a normal practice, because they are subtle and only cause methods of models in which all logic is wired. If I need to write the same (or almost the same) action for different controllers twice in a row, I will do it without hesitation. If I have to call the same model methods in different controllers, I will do it. The controller can be thrown away and rewritten again, as, in principle, and representation.
The model is difficult to throw away and is not necessary. The model interface, as a rule, does not change during application development or during the transfer of the model from application to application, but is only supplemented. Controllers and views in almost every application are their own. Try moving the controller from one framework to another, and you will understand what I'm talking about. And the model can be transferred from a web application to a mobile or desktop without (or almost without) a change in the code right along with preference tests and courtesans.
The controller should lend itself to easy refactoring. Perhaps the removal of identical actions into separate objects is just the very reuse of the controller, but it is not so critical as to pay a lot of attention to it. Certainly not 50 action classes for all occasions.
Controller as behavior
Let's take our favorite mp3 player (I mean the application on the smartphone). The presentation here is the buttons for switching to adjacent songs, stop, play and playlist. With the stop and loss buttons, everything is clear, I doubt that their behavior will change depending on the change of the controller, but the buttons for switching to songs can also be buttons for fast forward / backward of the current song. In total, having the same idea, we can change its behavior simply by replacing the controller.
You can imagine that on the phone’s lock screen while playing music, the rewind buttons change the composition, as it’s convenient, and in the application itself, rewind the song forward / backward. Also, we can change controllers in runtime through events. The event of holding a finger on the rewind button activates the controller with the rewind of the current song, a quick touch - activates the controller for switching to various songs. Reminds a router, right? In both controllers, we have a duplicate implementation of the play and stop actions, but this is quite acceptable for the pattern strategy. And both controllers use the same representation .
In MVC, we can replace not only the controllers of a view, but also the views of a single controller. A good example is displaying files in your favorite file manager: tile, list, table, etc. Looks like storefronts in online stores, right? For a second, imagine your file manager as a Data Provider, View and Behavior (controller). And you will see how MVC is applied around us in the real world.
The same principles work for a media center with a single interface, but with different collections of data (photos, music, videos). Each collection can change both the view and the behavior, moreover, we can mix several behavior (controllers) for the same view. For example, in the case of video, behaviors are suitable for both images (show action) and music (stop, play, rewind).
Widgets are V with all that it implies.
More often than desired, widgets are taken as a separate component of the system. Like, an object with a plug-in view is almost like a controller, which means it has a responsibility almost like a controller . Yes and no. Often they almost take it very literally and shove all the work with the model into the poor widget, that is, all the work of the organizer except filtering requests. Almost a controller , right?
Widget - God
I thought about the question of responsibility and knowledge of the widget when I combed GitHub in search of a convenient user-module for Yii. Before that, it seemed to me that the widget is a custom template, possibly with plug-in JavaScript, such as a drop-down menu, a Pjax container or a GridView from the same Yii. Based on this, we get a class that takes data as a model / collection of models and displays it / them according to its internal rules (convention) and settings (configuration). Or it doesn’t work with the model at all, it just adds design / behavior to the user interface.
I was shocked when I saw that in widgets people create, write, and perform other manipulations with the model directly. On the one hand, when we have a LoginForm widget and a LoginForm model, it would seem logical to use the class to validate and login the user into the system by manipulating the model. This is just a couple of lines. It may be somewhere in another Universe or in a small project, but in medium-sized projects or in whole modules this creates multiple code duplication and mixing of responsibilities of components of different nature.
One example of such a design is Bitrix, where all the logic and representations are embedded in widgets, which for some unknown reason are called components.
After similar examples on the net for various frameworks, I thought, “suddenly this is the right way”. And then suspicion crept into my head. If we pushed all the logic into widgets, then the controllers would deal only with the display of a specific view. But this is a gross violation of the pattern. If the widget is an element of V , then in no case should we manipulate the model in it. Even instantiating models is recommended to be done in controllers, let alone saving.
On the other hand, the concept of drop in and it's work does not fit in with the mandatory configuration of the widget. In addition, I really like convention over configuration, which, in my opinion, is so far best represented “out of the box” in Rails. As a result of which I made the following conclusions: a model can be instantiated in a widget only if the instance is not transferred to the widget, and the widget is the same name for a particular model, for example, LoginForm or NewsLine (in the news feed, it will be correct to create a DataProvider based on the selection) . But the widget should by no means be able to write to the model or save the model: this is the task of the controller.
All of the above seems quite logical until it comes to processing user input. If after entering data in LoginForm we need to authorize the user and stay on the page, then how do we do this without processing directly in the widget? Obviously, this requires a separate controller, but the controller takes the user away from the page if it does not specify an explicit redirect. On the Internet, for the most part, the following three scenarios are used:
In the first two cases, the widget is used only as a facade over the present presentation and processing. This option is the place to be. In the last example, the widget is implicitly connected to the controller, which can be replaced through the configuration.
In this case, the widget is a minimal logic - which template to show. Show the form if the user is not authorized; show greeting, if authorized; no more than if else in templates and any manipulations in the V .
All three scenarios are easy to implement, avoid code duplication, but each of them is more complicated than manipulating the model directly in the widget, the main thing is not to be tempted by the simplicity of the wrong approach .
Need more widgets!
Due to the large number of ready-made small solutions, frameworks cause degradation of developers. I noticed for myself how I start using some predefined widgets and solve the problems of these widgets so that they somehow fit into the design of my application, while writing a few of my lines of code in two or three languages would be much easier.
Reevaluated PJAX pops up everywhere, even where a regular AJAX request would suffice. Almost no one writes their JavaScript components in attempts to fit into the framework. I have seen on GitHub many times how developers discuss the JavaScript needed to reload Pjax or other work related to it, and that “you need to put more settings in a standard framework widget so as not to be distracted by JS, this is inconvenient!” But to create your own widget like PjaxReloader takes some 10 minutes along with testing. Honestly, I will spend more time on the discussion.
A widget is an object of one responsibility. In a good way, all objects in the OOP should be responsible for one duty, but in the real world this is not always the case. The list of comments for the article and the form for adding comments are different widgets. One may include the other (optional), but not mix with it. In the end, don't forget that V is a linker. You can combine small widgets into one large. Several large into one huge. And this will not go beyond the scope of the pattern.
The model stores state and business logic. She knows everything about her subject area, she does not give a damn which controller calls her and what representation she displays. A model can be instantiated outside the application as a separate and independent class. The model is easy to TDD, so all the application logic should be stored in the models (I remind you that components, services, repositories, etc. are also models).
The view knows absolutely everything about the model, except howit saves its states (database, file, REST, etc.). The view knows which fields the model stores, in which fields the properties formatted for the view are stored. The only thing that the idea should not know about the model is the methods of recording, changing and saving. The view has the right to instantiate the necessary objects and models, as well as collect Data Provider's, but in order to avoid code duplication it is better to take it to factories, helpers and widgets.
The controller knows nothing about the model or the view. The controller knows what kind of method to call the model, but it does not represent what is actually happening with the model at the moment. The controller asks for some actions on the model, but it should not be aware of the errors (I mean the runtime errors and exceptions) that the model generates, and in no case should it intercept and process them. The controller can offer (but this is not necessary) the model to be displayed through a certain view (after all, in fact, the controller is part of one or more views) and pass everything that it generated during its operation into the view, but it should not know whatneed a view to display. If he didn’t send something, the presentation should adequately respond to this and simply don’t show a piece of himself or throw an exception when it comes to the application being developed. The only exception the controller can handle is NotFound 404! The remaining errors of type 403 are generated in filters (request interceptors) or middleware.
The controller should at least be used (or not used at all) to read data . Reading can be organized by widgets and similar objects.
Widgets have no right to manipulate the model and write to the database, however, they can read data from the database / file through abstractions, helpers, factories, model classes and give them to the template. Widgets can instantiate a model within themselves if the model instance was not transferred to it from the top view (layout model) or from the controller. Widgets can collect Data Provider's, as this is a read operation.
In conclusion, I just want to quote from my favorite article on Habré. I gave a link at the very beginning of the article, in the section about models.
And may the composition come with you! Good to all!
MVC is a sophisticated yet stunningly elegant architectural solution. I can’t imagine what modern applications would turn into without this pattern.
On the Internet, all the information is scattered in some pieces, and now, after a year and a half of acquaintance and endless research, I can finally say: yes, I know this pattern far and wide.
I decided to collect all the missing information in one place. This was the reason for writing the article.
tl; dr: read the result. I ask the rest to get comfortable.
According to the Gang of Four, MVC is nothing more than strategy + linker + observer. The controller and view are registered with the model as observers. A view registers with itself a controller as an object of strategy, realizing its behavior through composition. The model is engaged in alerting observers when a state changes. A view embeds other views within itself, which usually depend on their models and present different behavior to the user through other controllers. This is not an HMVC. This is a classic of the genre.
In web development, MVC is the most popular pattern. Today, the concept of an MVC framework is rather commonplace, and we are used to believing that people around us know about these frameworks enough to correctly divide the code into three or more components. Unfortunately, due to the widespread prevalence of MVC frameworks and their attempts to provide their users with ease of development, application development and support, the basics of the underlying pattern are lost.
To begin with, MVC, as it is, cannot be implemented in a client-server application due to the lack of a constant runtime during the change of state of models. I am talking specifically about the observer. There is no point in updating the view after changing the model, because we get the view on the client, and the model lies far on the server. At one time, to solve this problem in Java Web Applications, the Model 2 pattern was developed , based on the good old MVC.
The essence of the pattern is to fine tune MVC to the http protocol and the realities of the Internet. This pattern considered the controller as an object that receives a request from a user. The controller must parse the request, based on it, instantiate a specific model and derive a specific view by forwarding the model and other data to the view. The view itself is a regular JSP. The model was used to communicate with the database and display table rows in the object (ORM). Looks damn familiar, right? And you don’t have any observers and linkers.
If you look closely, the difference in these two patterns is huge. Forwarding data from the controller to the view is already a gross violation of the MVC pattern. In the classical implementation, the view itself took data from the model directly or through the controller, but more often the controller was used to process user actions on the model using the user interface. That is, the controller, I repeat, is the behavior of the view. Slave. Not the master.
Unfortunately, this approach is not suitable for the http protocol. Connections to the server are disconnected, the application stops working, and in the case of PHP, it completely dies before the next request.
Model 2, like MVC, is inherent in all three patterns, only their implementation is somewhat different from the classical object implementation described by the “gang of four”.
The linker remained, but it is implemented through the creation of complex template files, whether it be JSP, PHP, ERB, and so on. But there are almost no methods for working with it. It is unlikely that in current applications we will be able to work with V as a part-whole relationship .
The strategy also did not go away, but everyone forgot about it, giving the controllers a new role - request handlers. In most current applications, a controller is a god who decides what, where and how to do. The controller calls the model, the controller saves the model, the controller writes to the model, the controller generates a view, the controller ... well, you understand. The controller does everything. From the usual behavior, the controller has turned into a GodObject.
Observer. This is the hardest part. Everyone knows how an observer works. Everyone knows how difficult and sometimes silly it is to implement a classic observer in the context of a single http request. This pattern was designed to update the data on the screen, and without web sockets, long pooling and other magic, this is extremely difficult to implement. In model 2, the observers are both the controller and the view, only now the reading of state changes occurs after the http request, and not all the time.
Very messy. However, if one talks about MVC and Model 2 in this way, many things themselves fall into place.
Next, I will try to describe the responsibilities of each component. I am not confined to a specific language, so in the text there may be combinations of the “class / structure” type, which are relevant for different languages at the present time. I want the article to be useful not only to ruby (railsl) / php developers. Everything written is also relevant for Golang with its structures, python, C #, Java, and, of course, JavaScript.
The article was written in several approaches, please do not judge duplicate information.
Models
What can be said new about the models? Everything is perfectly said about them more than once, for example, in this wonderful article, the very essence of the model is perfectly revealed. A model is a state, it is a business logic, it is a single entry point to a table / mongo document / file. If you need to save state between user requests, the model is responsible for this. It is the model that must have fields, if we talk about it in the context of the object. The model cannot be stateless. The model is responsible for the validation and relevance of the data.
If there are entities that can describe any application without a UI, of course, then these are models.
Views and Controllers
In the classic MVC, a view is a separate object that displays information on the screen. If we talk about CLI applications, then this is some kind of stdout.println (). Web applications use Model 2 instead of the classic MVC, which implies that the view is just a file with markup and built-in template code. At first glance, it seems that a simple template does not have much weight in the system. In fact, it is the view that determines which actions are available to the user.
A view is part of two patterns: strategy and layout. The behavior of the view is implemented in the controller. It turns out that the controller is only part of a certain idea. And it can be replaced by another controller if we need different behavior for a specific view.
In fact, there is a lot of magic that slips through the fingers of “inattentive travelers”.
Greedy and Lazy Controllers
Due to the concept of “thick models, thin controllers”, I noticed a tendency to simplify the actions of controllers to one line - return render ('view'). As a result of this, most actions and data sampling are carried out directly in the view. This approach is argued in that the less data is transferred to the view, the more independent the views and controllers become from each other. Simplifications reach the point of absurdity when they start manipulating models directly in the template files. I don’t understand why everyone is so afraid to write code in controllers. If I pass a dozen data to a view, this does not mean that my view should use all of them. Imagine that you collected some things, put them in a box and sent by mail to a specific person. You don’t need these things anymore, and what the recipient will do with them, absolutely not important. He can choose a couple or two, and throw the rest. I even coined the term for myself: "greedy and lazy controllers."
In this articleIt is said that the action of the controller in the terminology of the framework is the controller in the terminology of MVC. Despite the fact that the article itself is amazing, I strongly disagree with this statement. Action is the user's action on the view. This is a defining part of the behavior. If we rewrite action, the behavior will change, but we will infiltrate the code of the finished class, and I do not like to change the class code for nothing. If we replace the controller, this will also lead to a change in the behavior of the view, but without changing the existing objects of the strategy. Take, for example, Angular.js. It clearly traces where the controller begins, and where its methods are. Any controller method interacts with the presentation in one way or another through the directives ng-click, ng-submit, and so on. From which we can conclude: the controller is a full-fledged class, and its methods (actions) are the actions of the view through which the user interacts with our application. If we replace the controller in the ng-controller directive, the behavior of our view will change depending on the implementation of the methods (actions) of a particular controller.
The second statement that really scares me is that controllers are the most reused parts of the application. What? For me, a controller is an exceptionally one-time code. Actions (action's) can be reused, I agree. Especially CRUD actions, in which you can simply specify the class / structure of the model with which this action should be associated. Duplication of code in controllers is a normal practice, because they are subtle and only cause methods of models in which all logic is wired. If I need to write the same (or almost the same) action for different controllers twice in a row, I will do it without hesitation. If I have to call the same model methods in different controllers, I will do it. The controller can be thrown away and rewritten again, as, in principle, and representation.
The model is difficult to throw away and is not necessary. The model interface, as a rule, does not change during application development or during the transfer of the model from application to application, but is only supplemented. Controllers and views in almost every application are their own. Try moving the controller from one framework to another, and you will understand what I'm talking about. And the model can be transferred from a web application to a mobile or desktop without (or almost without) a change in the code right along with preference tests and courtesans.
The controller should lend itself to easy refactoring. Perhaps the removal of identical actions into separate objects is just the very reuse of the controller, but it is not so critical as to pay a lot of attention to it. Certainly not 50 action classes for all occasions.
Controller as behavior
Let's take our favorite mp3 player (I mean the application on the smartphone). The presentation here is the buttons for switching to adjacent songs, stop, play and playlist. With the stop and loss buttons, everything is clear, I doubt that their behavior will change depending on the change of the controller, but the buttons for switching to songs can also be buttons for fast forward / backward of the current song. In total, having the same idea, we can change its behavior simply by replacing the controller.
You can imagine that on the phone’s lock screen while playing music, the rewind buttons change the composition, as it’s convenient, and in the application itself, rewind the song forward / backward. Also, we can change controllers in runtime through events. The event of holding a finger on the rewind button activates the controller with the rewind of the current song, a quick touch - activates the controller for switching to various songs. Reminds a router, right? In both controllers, we have a duplicate implementation of the play and stop actions, but this is quite acceptable for the pattern strategy. And both controllers use the same representation .
In MVC, we can replace not only the controllers of a view, but also the views of a single controller. A good example is displaying files in your favorite file manager: tile, list, table, etc. Looks like storefronts in online stores, right? For a second, imagine your file manager as a Data Provider, View and Behavior (controller). And you will see how MVC is applied around us in the real world.
The same principles work for a media center with a single interface, but with different collections of data (photos, music, videos). Each collection can change both the view and the behavior, moreover, we can mix several behavior (controllers) for the same view. For example, in the case of video, behaviors are suitable for both images (show action) and music (stop, play, rewind).
Widgets
Widgets are V with all that it implies.
More often than desired, widgets are taken as a separate component of the system. Like, an object with a plug-in view is almost like a controller, which means it has a responsibility almost like a controller . Yes and no. Often they almost take it very literally and shove all the work with the model into the poor widget, that is, all the work of the organizer except filtering requests. Almost a controller , right?
Widget - God
I thought about the question of responsibility and knowledge of the widget when I combed GitHub in search of a convenient user-module for Yii. Before that, it seemed to me that the widget is a custom template, possibly with plug-in JavaScript, such as a drop-down menu, a Pjax container or a GridView from the same Yii. Based on this, we get a class that takes data as a model / collection of models and displays it / them according to its internal rules (convention) and settings (configuration). Or it doesn’t work with the model at all, it just adds design / behavior to the user interface.
I was shocked when I saw that in widgets people create, write, and perform other manipulations with the model directly. On the one hand, when we have a LoginForm widget and a LoginForm model, it would seem logical to use the class to validate and login the user into the system by manipulating the model. This is just a couple of lines. It may be somewhere in another Universe or in a small project, but in medium-sized projects or in whole modules this creates multiple code duplication and mixing of responsibilities of components of different nature.
One example of such a design is Bitrix, where all the logic and representations are embedded in widgets, which for some unknown reason are called components.
After similar examples on the net for various frameworks, I thought, “suddenly this is the right way”. And then suspicion crept into my head. If we pushed all the logic into widgets, then the controllers would deal only with the display of a specific view. But this is a gross violation of the pattern. If the widget is an element of V , then in no case should we manipulate the model in it. Even instantiating models is recommended to be done in controllers, let alone saving.
On the other hand, the concept of drop in and it's work does not fit in with the mandatory configuration of the widget. In addition, I really like convention over configuration, which, in my opinion, is so far best represented “out of the box” in Rails. As a result of which I made the following conclusions: a model can be instantiated in a widget only if the instance is not transferred to the widget, and the widget is the same name for a particular model, for example, LoginForm or NewsLine (in the news feed, it will be correct to create a DataProvider based on the selection) . But the widget should by no means be able to write to the model or save the model: this is the task of the controller.
All of the above seems quite logical until it comes to processing user input. If after entering data in LoginForm we need to authorize the user and stay on the page, then how do we do this without processing directly in the widget? Obviously, this requires a separate controller, but the controller takes the user away from the page if it does not specify an explicit redirect. On the Internet, for the most part, the following three scenarios are used:
- The user is redirected to the login page, where errors are displayed (incorrect password) or authorization and display of the personal account occurs.
- The user is redirected to the login page, where errors are displayed (incorrect password) or authorization and redirect to the previous / main page with (or without) the intermediate view are displayed (remember forums on phpBB and the like).
- The user logs in via an AJAX request, remains on the page. All blocks that depend on authorization (such as a basket) are updated through some kind of Pjax.reload or by simply reloading the page.
In the first two cases, the widget is used only as a facade over the present presentation and processing. This option is the place to be. In the last example, the widget is implicitly connected to the controller, which can be replaced through the configuration.
In this case, the widget is a minimal logic - which template to show. Show the form if the user is not authorized; show greeting, if authorized; no more than if else in templates and any manipulations in the V .
All three scenarios are easy to implement, avoid code duplication, but each of them is more complicated than manipulating the model directly in the widget, the main thing is not to be tempted by the simplicity of the wrong approach .
this is practically no different from the ancient method of using separate php files for each "page" of the application.
Need more widgets!
Due to the large number of ready-made small solutions, frameworks cause degradation of developers. I noticed for myself how I start using some predefined widgets and solve the problems of these widgets so that they somehow fit into the design of my application, while writing a few of my lines of code in two or three languages would be much easier.
Reevaluated PJAX pops up everywhere, even where a regular AJAX request would suffice. Almost no one writes their JavaScript components in attempts to fit into the framework. I have seen on GitHub many times how developers discuss the JavaScript needed to reload Pjax or other work related to it, and that “you need to put more settings in a standard framework widget so as not to be distracted by JS, this is inconvenient!” But to create your own widget like PjaxReloader takes some 10 minutes along with testing. Honestly, I will spend more time on the discussion.
$js = "jQuery(document).on('pjax:success', '#$id', function() { jQuery.pjax.reload('$reloadSelector'); });";
$view->registerJs($js);
A widget is an object of one responsibility. In a good way, all objects in the OOP should be responsible for one duty, but in the real world this is not always the case. The list of comments for the article and the form for adding comments are different widgets. One may include the other (optional), but not mix with it. In the end, don't forget that V is a linker. You can combine small widgets into one large. Several large into one huge. And this will not go beyond the scope of the pattern.
Total
The model stores state and business logic. She knows everything about her subject area, she does not give a damn which controller calls her and what representation she displays. A model can be instantiated outside the application as a separate and independent class. The model is easy to TDD, so all the application logic should be stored in the models (I remind you that components, services, repositories, etc. are also models).
The view knows absolutely everything about the model, except howit saves its states (database, file, REST, etc.). The view knows which fields the model stores, in which fields the properties formatted for the view are stored. The only thing that the idea should not know about the model is the methods of recording, changing and saving. The view has the right to instantiate the necessary objects and models, as well as collect Data Provider's, but in order to avoid code duplication it is better to take it to factories, helpers and widgets.
The controller knows nothing about the model or the view. The controller knows what kind of method to call the model, but it does not represent what is actually happening with the model at the moment. The controller asks for some actions on the model, but it should not be aware of the errors (I mean the runtime errors and exceptions) that the model generates, and in no case should it intercept and process them. The controller can offer (but this is not necessary) the model to be displayed through a certain view (after all, in fact, the controller is part of one or more views) and pass everything that it generated during its operation into the view, but it should not know whatneed a view to display. If he didn’t send something, the presentation should adequately respond to this and simply don’t show a piece of himself or throw an exception when it comes to the application being developed. The only exception the controller can handle is NotFound 404! The remaining errors of type 403 are generated in filters (request interceptors) or middleware.
The controller should at least be used (or not used at all) to read data . Reading can be organized by widgets and similar objects.
Widgets have no right to manipulate the model and write to the database, however, they can read data from the database / file through abstractions, helpers, factories, model classes and give them to the template. Widgets can instantiate a model within themselves if the model instance was not transferred to it from the top view (layout model) or from the controller. Widgets can collect Data Provider's, as this is a read operation.
In conclusion, I just want to quote from my favorite article on Habré. I gave a link at the very beginning of the article, in the section about models.
Some people will simply laugh at all these fools who talk about the need for good independent domain models and continue to write confusing code. Let them laugh. After all, it is they who will have to maintain and test their mess.
And may the composition come with you! Good to all!