I like the repository pattern (link), but find it tedious in a lot of ways. We employ the pattern because it enables us to break the dependency between our controllers and/or service layer code and the ORM (typically Eloquent). Unfortunately, it usually involves writing a lot of redundant boilerplate code.
While working on a new project, I decided to work on a (partial) solution to my repository pattern woes. I used a combination of polymorphism, inheritance and dynamic programming to leverage the good parts of the pattern while limiting the boilerplate.
The basic idea of the pattern is to create repositories that implement a standard RepositoryInterface which covers the standard Eloquent methods in a non-static, non-facade driven way. This means we can use dependency inversion, increasing the testibility of our application. Also, because our data-layer code will be hidden behind repositories, we can isolate changes to queries and/or business logic to this layer.
But only implementing the Eloquent-specific functions is a little restrictive. I also wanted to add the ability for repositories to implement their own model-specific functions. In order to do this, we provide a model-specific interface for each repository, into which we can insert the model specific functions.
Finally, all the boilerplate is hidden behind an AbstractRepository class, which all repositories extend from. We limit the boilerplate by using dynamic programming to determine which Eloquent model to call at runtime.
In the repository interface, we define all the standard Eloquent methods. The method signatures here should match the signatures of the corresponding methods in “Illuminate\Database\Eloquent\Model” exactly.
Note: some of the Eloquent methods have been removed for simplicity. To add them back in, simply use the same pattern described below.
We define any methods in here that are specific to the User version of the repository. Notice that we extend from RepositoryInterface, so any class that implements this interface must also provide implementations to the methods in RepositoryInterface.
The concrete implementation of the UserRepository. We extend from AbstractRepository to get the implementations for the RepositoryInterface methods, and then implement any methods defined in UserRepositoryInterface in here.
Notice the $modelClassName variable. This is important, because we use it in the AbstractRepository to dynamically call methods on the corresponding Eloquent model.
Finally, AbstractRepository performs the dynamic implementation of the different Eloquent methods.
In the example below we can see two usages. The first uses the Eloquent method “find” to retrieve a single record. The second usage uses our custom “findByUsername” method to locate a user.
This approach enables us to gain the benefits of using a repository layer without requiring us to write loads of boilerplate code.
The standard process for adding new repositories is simple.
- If we only want the standard eloquent functions, we create a blank interface (“SomethingRepositoryInterface”) that extends from “RepositoryInterface”. We then Create the “SomethingRepository” class, implement the “SomethingRepositoryInterface”, extend from “AbstractRepository” and add the $modelClassName property.
- If we need to add methods that are specific to the model, we follow the process defined in the previous step, but then add the custom method signatures to “SomethingRepositoryInterface” and the concrete implementations of those methods in “SomethingRepository”