I am creating an MVC inspired PHP framework, mainly for learning purposes. I basically have the framework created and am building an app on it and improving the framework as i go along.
I am still confused/not sure about certain aspects of this sort of architecture, and at the moment i am questioning my implementation of the View part.
How i have set up my framework:
Its a very simple set up, for example: you go to the url /post/post_id
, this will load index.php which will instantiate the router. The router will then check the url and instantiate the correct controller and method based on the url. In this case it would be PostController, and the method would be a default method that would use the post_id
to get the posts data from the relevant model. Next the controller would set up a "data" variable that will hold the data to pass on to the View, and this is where i am confused - should it send to its own View object (a view class file dedicated to the PostController), or to a generally used View class that is used by all controllers to load an html file?
At the moment my controller is sending data to the View class, this data includes what template file should be included/shown, and the actual data for the page (what we got from the Model through the controller).
My question is this:
Should this type of system have one View object that renders all of the views (html files) based on what data is given to the "render" method, or, should each controller that eventually sends data to the View have its own View object/class?
Meaning, should PostController send a request to the general view class, the same one that is used by all controllers to renders pages, or should the PostController send to a dedicated View Class (call it PostView if it makes it clearer), and this class will then render the specific html file?
Basically if it should be one View class for all controllers that will render what ever html file the controller tells it to, or if there should be many View classes, one for each page load.
NOTE: I know a lot of questions have already been asked about MVC in PHP, but i could not find an answer to my question in any of the answers.
The best way, IMO, is to have this View
or Template
class do all the work related to views with just one simple method: render(string $templateName, array $context = [])
.
This allows for easy extension or creation of adapters. You should make your controllers use this method. If you use a DI Container in your framework, you could do a TemplatingAwareInterface
and implement that with a trait that allows setter injection. That will inject the templating service on fetching from the service container. That way, you can use $this->templating->render()
in your controller, without having to either make it global, nor constructing the templating service inside the controller, nor injecting the container into the controller.
Having one view class for each type of controller is cumbersome, harder to maintain and I don't really see a reason for it.
A bit about MVC:
In the original MVC pattern (presented by Trygve Reenskaug in 1979), the controller updates the model, the model notifies the view about the changes, and the view pulls its data from it. Though the pattern was thought for desktop applications - each M-V-C "triad" beeing related to a single control in a window (a button, a textbox, a checkbox, etc). So, each control on the screen had an MVC "attached" to it.
Since, in web applications, the model-to-view notification step is not (or can not be) present, the original pattern can not be applied as is to them. But a most similar approach can still be relatively easily implemented: the controller updates the model, the view pulls data from it (irrespective of the controller). I think it's called "Web MVC Model 2".
There is a multitude of web MVC variations. You are using one in which the controller takes the role of an intermediary between the model and the view. So the controller is the only one component communicating with the model.
The view's responsibility:
The responsibility of the view component is the presentation logic - which should not be assumed by the controller at all. Beside loading and rendering template files, this kind of logic involves the preparation of the data fetched from the model for displaying purposes. The result of the preparation should, preferably, be a list of values of primitive types (strings, booleans, integers, arrays, etc) which can be easily "injected" into the template files during the load-and-render process.
Examples of presentation logic:
Example #1: If you would fetch the value 123.45
(from the column amount
of a table revenues
) from the model, the presentation logic would consist of formatting it to the string 123.45 USD
, in order to be displayed in a template file.
Example #2: Formatting a fetched date value of 28/05/2019
to 2019-05-28
by using a code snippet like this:
$fetchedDateFromModel = '28/05/2019';
$time = strtotime($fetchedDateFromModel);
$formattedDate = date('Y-m-d', $time);
The value of $formattedDate
would then be "injected" into a template file.
Example #3: Setting a boolean value based on some model data, in order to be used in a template file for deciding if a button ("Get Bonus") should be active or not.
$paidAmount = 512.79; /* model data */
$isActiveGetBonusButton = false;
if ($paidAmount > 500) {
$isActiveGetBonusButton = true;
}
The answer (in respect of your chosen MVC approach):
By using a View
instance in all controllers, you would be forced to perform specific presentation logic in each controller - before passing its result (e.g. the list of the prepared values) to the used View
instance, in order to further just be "injected" in a specific template file.
Whereas, if you are implementing a dedicated view class (like PostView
- which, preferably, inherit a base class View
containing the render()
method) for a controller class (like PostController
) - so a 1:1 relationship, but see it as a loose one! - you can pass the data fetched from the model, in an unprepared form, from the controller to the view. The view class would then correctly take the responsibility of preparing the data for displaying prior to actually load and render a specific template file. E.g. of performing the whole specific presentation logic.
Note: In "Web MVC Model 2" - where, ideally, the controller has no knowledge of the view component - the above argument is more obvious:
PostController
just updates the model (when an update step is required);PostView
fetches data from model, prepares it for display, and displays it (by loading & rendering a template file like posts.html.twig
, for example). In other words, the view component performs the whole presentation logic by itself.