While developing FuelPHP 2.0 we took a critical look at all established web application concepts. Nothing was considered out-of-bounds, not even the most well known of all: Model-View-Controller.
When attempting to explain the concept of ViewModels within our framework I often get the question shot back "but what advantage does that have over a controller?" The problem this signifies isn't that the ViewModel duplicates functionality, rather it's a symptom of a fundamental misunderstanding of the Controller concept - indeed the whole MVC pattern.
Don't feel bad if you are one of those people scratching your head over this, it's a logical consequence of the common and widespread implementation in PHP and other web frameworks. But that's not where MVC originates, the pattern was designed in the context of old fashioned PC applications. For this type of applications the environmental constraints are very different from those imposed on your web application by the HTTP protocol. And to understand this difference we have to start there.
The fundamentals are of course always the same: a seperation of concerns. You divide the logic between the concerns of your application (the controller), the data retrieval/manipulation (the model) and user interaction (the view). But the implications of this in a continous persistantly running application are very different from those on the web that need full request-response processing for each user interaction.
I have seen it argued that this very difference makes the MVC pattern unfit for the web, but I always felt that's missing the point. The seperation of concerns is always a good idea, a principle more logically sound than MVC is hard to find. The problem however is that the common implementation of a controller and a view no longer fit their original concepts. And when you don't separate the concept from the implementation, such a charge is actually more of a straw-man argument than an actual critisism.
With a display language (HTML, Javascript) that is different from the backend (PHP, Python, Ruby) some new hurdles need to be taken that were not a problem in the original implementations.
Views used to be entities that generated what was displayed and ran the user interaction. Not the actual actions that the interaction lead to, but it did make sure those interactions were routed to controllers that decide what to do with them. In PHP however the views were HTML, with which came the logical consequence that they are best kept logic-free. Otherwise you'd get an unreadable mix of HTML and PHP in a single file. Something you would expect a decent framework to prevent.
This however does create a problem: the View specific logic has no place anymore...
...and it is where the problem starts for Controllers. People tend to solve this problem by putting the display logic (that has little to do with running the application) into the controller instead. It prevents the unwanted mixture of HTML & PHP in the View, but creates a new mixture of application and view concerns in the Controller. Which completely breaks our original intend when implementing the MVC pattern: seperating concerns.
Another problem is that the concept of a Controller changed. A controller was meant to be a functional entity that handles the application flow and interacts with Models and Views. For the web the Controller was implemented as a class with methods that the routing might lead to. But in almost all cases this fundamentally changes the concept. No longer a single functional entity, but more a container for a set of entities. Entities that for most developers are not considered as smaller parts of a bigger whole. The routing ends up deciding which method is called and the controller has no say in this and is actually little more than a container label.
Which introduces the concept of routing in a web framework. Without the persistant state of a PC application we can't couple a View interaction directly to a Controller call, it needs a new HTTP request to do that. And the new request runs completely isolated from the previous one. Routing adds a new layer of logic to this that takes the input and decides which controller will be used.
Obviously these implementations were done for a reason and they are not bad in themselves. But the problem of an implementation that gets copied instead of the concept is that the concept and its intentions get lost. Which is what happened with MVC. Views became incapable of doing anything functional, and of the controller concept little more was left than buckets of loose sand.
Something new arose that shouldn't really bear the MVC label anymore.
But things also started to get a little bit confusing. The Laravel framework for example introduced the concept of MVR (Model-View-Router) with routes that contained Closures instead of Controller class paths. The problem here is not the executable routes themselves, a fine concept we have implemented as well. The confusing part is the label, which implies that this is something new and different while it isn't: An executable route is a functional entity that handles the application execution based on user interaction, also known as a Controller.
(note: this is not meant to be a dig at Laravel, it is just an example of how mixed up the terminology has become)
In our current stable 1.x branch we handle Controllers pretty much like everyone else. Routing is done to methods ("actions") and the Controller is little more than syntax. Closures/callbacks are also allowed and handled differently from Controller classes (implying they're not controllers).
ViewModels are already a part of the solution. They are functional entities that handle the view logic and decide which "view" (HTML template) to call. This allows you to remove any view-specific concerns from your controllers. In our implementation, ViewModels are not really a new concept. They are in fact more of an implementation of what Views should be in MVC. Making the common PHP implementation (of view files containing HTML and variables) little more than another resource loaded by the ViewModel, just like a config or language file might be loaded.
In the current alpha of 2.0 we went back to the original concept even further. Routing is no longer a simple bit of string manipulation to translate an input URI to something that maps to a Controller's classname. Instead, routing now always returns something executable, whether that is a Closure, a callback or... the common instance of a Controller class. This last part is handled by giving the Controllers a PHP magic method __invoke(). This will allow instances of the class to be called like a Closure.
The executable controller returned by routing gets passed an array of URI segments that may help it decide what to do. In the case of a controller class this will most likely be calling an 'action'-method.
While this will be mostly philosophical to many users, with little practical implications, it is not trivial. Interpretation and implementation of concepts is important. And to keep the concept of MVC relevant and useful we should remember how the pattern was meant to be implemented, not just the implementations we learned first. This is part of our job while continuing to develop our framework, especially when working on a new version and re-evaluating concepts and implementation.