As I mentioned in the previous article of this series, in most frameworks the Controller sets up the View by attaching variables to it. The issue with that is it adds a responsibility to the Controller, but it also adds binding logic between the Controller and the View, which inherently leads to more bugs and makes the View more tightly coupled to the Controller. The Controller can send any data, there is nothing to stop it to send erroneous data to the View.
By letting the View get its own data from the Model, the View has a contract with the Model.
This allows to:
How do we achieve this?
We can assign the responsibility of fetching the model to prepare the view to another class,
which I will call the View Model.
The View Model contains all the methods to fetch the needed data from the Domain Models.
It can be seen as an abstraction for the View of the Domain Models.
Each Controller has its associated View Model; there is a 1:1 relationship between Controller and View Model. The application executes the requested Controller's action, and the associated View Model is passed to the View. The View fetches its own data from the View Model, avoiding any binding logic between the Controller and the View.
Consider an example where the user's profile is requested.
The Controller UserInfoController
's action
gets the user id from the request query string, and passes it to the View Model UserInfoViewModel
.
The View gets the user details through the View Model, which fetches the user from the Domain Model.
<?php
class UserInfoController
{
public function index()
{
$this->viewModel->setUserId($this->request->getParam('user'));
}
}
class UserInfoViewModel
{
private $orm;
private $id;
public function __construct($orm)
{
$this->orm = $orm;
}
public function setUserId($id)
{
$this->id = $id;
}
public function getUser()
{
return $this->orm->find($this->id);
}
}
?>
<!-- user-info.phtml -->
<label>First name</label>
<div><?= $viewModel->getUser()->getFirstName() ?></div>
<label>Last name</label>
<div><?= $viewModel->getUser()->getLastName() ?></div>
If the View now needs to display the user's address, you won't have to edit both Controller and View. You would only need to edit the View as it fetches all the data it needs itself from the Model. The Controller and the View are no longer tightly-coupled and the components can potentially be re-usable independently.
This design offers us:
The controller is now only responsible for updating the Model,
and setting the application state.
The View Model is an abstraction of the Domain Model for the View.
It provides the View with a contract for fetching data from the Domain Model.