Tom Butler's programming blog

Immutable MVC: MVC In PHP 2019 Edition (Part 1)

MVC Revisited

The most popular articles on this website are about MVC, the oldest of which is now nearing ten years old. A lot has changed in that time, and my own experience has also grown. My previous MVC series kind of fizzled out because I wanted to go back and tweak the examples in the earlier articles.

PHP has changed quite a lot since I wrote those articles. I'm going to start fresh and give some cleaner, complete and more modern code examples.

Nine years have passed since I wrote the first MVC article here and I have a few observations about it looking back:

  1. The PHP ecosystem hasn't changed. People still call the "controller as a mediator" approach MVC. If anything the situation is worse because the popular frameworks are now so embedded trying to do anything different just feels wrong.
  2. The points I made are still valid. Giving the view access to the model has significant benefits which I won't reiterate here. Take a look at my previous article if you're interested.
  3. The code examples I gave aren't clear or complete enough. I spent more time focusing on concepts than giving code examples. I'm going to rectify that here.

In addition to writing some more complete examples, it's time for an update with some newer PHP features and programming trends like immutability. I'm going to show you how to write a completely immutable MVC structure. Then demonstrate reusable controllers and a routing system.

Other than making use of new PHP features such as return type hinting, the two main changes I've made to my coding style are immutability and favouring arguments over constructors with related properties.

Hello World

The first thing I'm going to do is provide a demonstration of a completely immutable MVC triad.

Let's take my previous Hello World example and convert it to become immutable:

class Model {
    public 
$text;

    public function 
__construct() {
        
$this->text 'Hello world!';
    }
}

class 
View {
    private 
$model;

    public function 
__construct(Model $model) {
        
$this->model $model;
    }

    public function 
output() {
        return 
'<a href="mvc.php?action=textclicked">' $this->model->text '</a>';
    }

}

class 
Controller {
    private 
$model;

    public function 
__construct(Model $model) {
        
$this->model $model;
    }

    public function 
textClicked() {
        
$this->model->text 'Text Updated';
    }
}


$model = new Model();
//It is important that the controller and the view share the model
$controller = new Controller($model);
$view = new View($model);
if (isset(
$_GET['action'])) $controller->{$_GET['action']}();
echo 
$view->output();

This version of the architecture relies on the model being mutable. Both the controller and the view share a reference to the same model instance. The controller updates the model and the view reads the model's state to produce the output.

The Model

In this example, the model is mutable but the other components are not. Firstly, let's make the model immutable:

class Model {
    private 
$text;

    public function 
__construct($text 'Hello World') {
        
$this->text $text;
    }

    public function 
getText() {
        return 
$this->text;
    }

    public function 
setText($text) {
        return new 
Model($text);
    }
}

Now there is no way to change the model's state once it's been instantiated. Calling the setText method creates a new instance. Any place that the original instance is referenced will not see a change in the state of the model. The controller and view will need to be updated to use this updated model class.

Previously, the model and controller shared a model instance. The controller would amend the model's state and the view would read the state of the model instance. Now that the model is immutable, we cannot rely on the view and controller sharing the same model instance. Instead, the controller action will return a model instance which will then be passed to the view.

The Controller

Instead of updating the state in an existing, mutable, model instance, the controller will return a new model instance after making its changes:

class Controller {
    public function 
textClicked(Model $model): Model {
        return 
$model->setText('Text Clicked');
    }
}

Rather than relying on constructors and properties, I've opted to use arguments to pass in the model which is going to be updated. The controller action is now given an immutable model instance and returns a new instance with an changes that are required.

It should be noted that this isn't part of MVC, using either constructor arguments with related properties or arguments to the controller action have the same effect. But by avoiding constructors it is no longer necessary to keep the model as a property and the controller is significantly simplified. Less code doing the same job is always a good thing.

The View

The View needs only a minor tweak to call the method rather than reading the public property:

class View {
    public function 
output(Model $model) {
        return 
'<a href="mvc.php?action=textclicked">' $model->getText() . '</a>';
    }
}

The advantage of using arguments here is that the same View instance can be used to render multiple models. It's no longer necessary to instantiate a view object for each model that is rendered.

Putting it all together

The final piece of the puzzle is the code which instantiates all the components. There are no longer any constructor arguments but the correct method arguments need to be applied at each stage:

$model = new Model();
$controller = new Controller();
$view = new View();


if (isset(
$_GET['action'])) {
    
$model $controller->{$_GET['action']}($model);
}

echo 
$view->output($model);

In this immutable version, the model is passed into the controller action and an updated model instance returned. The model instance is then passed into the view's output method.

Immutable MVC Complete Example

The complete code looks like this:

class Model {
    private 
$text;

    public function 
__construct($text 'Hello World') {
        
$this->text $text;
    }

    public function 
getText() {
        return 
$this->text;
    }

    public function 
setText($text) {
        return new 
Model($text);
    }
}

class 
View {
    public function 
output(Model $model) {
        return 
'<a href="mvc.php?action=textclicked">' $model->getText() . '</a>';
    }
}

class 
Controller {
    public function 
textClicked(Model $model): Model {
        return 
$model->setText('Text Clicked');
    }
}



$model = new Model();
$controller = new Controller();
$view = new View();


if (isset(
$_GET['action'])) {
    
$model $controller->{$_GET['action']}($model);
}

echo 
$view->output($model);

Conclusion

This immutable implementation of MVC has some advantages over the mutable version:

  • State is better managed so that the application doesn't suffer from action at a distance where changing an object in one location (in the controller) then causes changes in a seemingly unrelated component (the view)
  • There is less state overall. There are no longer references to the different objects in multiple locations. The controller and view no longer have a reference to the model, they are given an instance to work with at the moment they need it, not before

What next?

Now that we have an immutable MVC structure to build on, I'm going to use this to build a more functional application. The first thing I'll do is demonstrate a more complete example with multiple controllers and actions that do some basic database manipulation.

Continue to part 2