Within the PHP MVC framework community (and the Laravel community in particular), it seems to be in vogue at the moment to write about dependency injection and how it can be used to make controllers testable. In general, I agree with these authors, but find some of the examples and discussions presented to be overly simplistic.
In this post, I’ll provide a description of how the repository pattern is typically presented. In a later post, I’ll discuss how the repository pattern can fit into a broader architectural scheme to build complex and scalable web applications.
Note that the examples in this tutorial will use the Laravel 4 framework, but the concepts are completely portable to any MVC based web application.
Controllers Without Repositories
In a typical MVC web application, a controller for performing CRUD actions against a resource might look something like the following:
The code here is easy to read and compact, but there’s an important thing to notice. Our controller is directly coupled to the model class used to perform database actions. This is a problem for two reasons.
Firstly, we can’t test the controller in isolation: any code that interacts with the controller is going to invoke database actions.
The second problem with this approach is that the controller now has a dependency on the ORM layer. If we wanted to swap out our ORM for another solution later on, it would involve a serious rewrite of all the controllers.
An Initial attempt
We can solve these problems by introducing repositories. These provide a layer of abstraction over the database, so instead of directly interacting with the model class, the controllers interact with a repository class that encapsulates all the model layer interactions.
Our repository class will look something like this:
And the controller will interact with the repository class as such:
As you can see from these code examples, the controller is no longer directly coupled to the model layer. So why is this a good thing? Haven’t we just replaced one dependency with another one, and added a lot of unnecessary boiler plate?
For small projects, you could argue that this is the case. But for larger projects with more complexity and changing requirements, this layer of abstraction is incredibly useful.
For one, this enables us to much more easily migrate to a different ORM or database layer technology. Instead of changing how each controller interacts with the model layer, we just make the changes in the repository classes.
The other major benefit of this approach is that we can easily “mock” the repository class in our unit tests:
Getting More Advanced
This is a good start in moving to a more testable, better architected software system, but it does raise some questions: how do we know which class to inject into the controller, and how do perform the injection during instantiation of the controller object?
Luckily, if we’re using Laravel, this can be easily solved using the built-in IoC container and service providers. Note that these examples are going to heavily lean on Laravel components, so if you aren’t familiar with the framework these examples may be slightly confusing. If this is the case for you, I recommend checking out Taylor Otwell’s (creator of Laravel) screencast on IoC and Unit Testing in Laravel.
The first thing we need to do to leverage Laravel’s IoC container and service providers is to code to an interface. This means that we define the contract of our repository in an interface class, as such:
We then provide a concrete implementation of that contract or interface:
Finally, we can use the implementation of the interface in our controller:
But as formulated above, this still won’t work. Laravel knows that the PostController needs an implementation of the PostRepositoryInterface in order to function, but it can’t use the interface itself – an interface provides no implementations of the functions in the contract (and hence cannot be instantiated).
In order to make this work, we need to create a service provider that tells Laravel’s IoC container which implementation of PostRepositoryInterface should be used when we need one.
The service provider is simply a class that extends Illuminate\Support\ServiceProvider, and contains a register() function. For the purposes of registering our implementation of PostRepositoryInterface, it will include the following:
We also need to modify the app.php configuration file so that Laravel knows to load the service provider:
Now when Laravel calls the controller, under the hood it will instantiate the right version of the PostRepositoryInterface and inject it into the constructor.
This makes it incredibly easy for us to test the controller in isolation: we simply need to tell the IoC container that we want to register a mocked class instead of the concrete implementation:
After wrestling with the repository pattern for quite some time across different projects of different scales, I don’t think the pattern by itself is as practical and Universally applicable as some authors imply. However, it is useful for applications of a certain level of complexity (somewhere in the grey area between “toy app” and enterprise application), and with some changes to the approach, it can serve as a part of a broader architectural scheme for more complicated applications.