Introduction
This article assumes that you have a basic understanding and fundamentals of PHP and the Laravel framework.
Before we get into the topic we first must understand what is Repository pattern:
The Repository pattern. Repositories are classes or components that encapsulate the logic required to access data sources. They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer.
Create a fresh Laravel project
Now open up a terminal and install a fresh Laravel project,
composer create-project laravel/laravel laravel-repository-pattern
Then open this in your code editor (VS Code).
I am not going to go through the installation process since this article does not aim to teach about installing a Laravel project. Go to the official docs for reference instead, https://laravel.com/docs/8.x#installation-via-composer
Creating a base interface and base repository class
Create a directory inside the app and name it as Repository and inside it create another directory Eloquent then create a file named BaseRepository.php
inside Eloquent directory and create an interface and name it EloquentRepositoryInterface.php
inside Repository . If you did it correctly you will have the following file structure.
- app
- Repository
- Eloquent
- BaseRepository.php
- EloquentRepositoryInterface.php
Then inside the interface EloquentRepositoryInterface.php , write an interface that will contain all the common methods that are used when we are accessing our database to retrieve, store, and remove data. (CRUD)
We also want to set standards when creating our repository interface, because we want the method names to be self-describing and that we should know what we get when we use the method. We can use the following verbs: 'find', 'get', 'remove', 'update' and etc that simply describes what it does.
The interface does not care about the implementation details, we only define the method names, what arguments are being passed into it and what we expect it returns. We can simply do that by type hinting it. This what makes it beautiful because whenever we had to change a Data Provider (I am talking about Eloquent ORM, DB, or any 3rd party APIs if there's any), we only have to create another class specifically for it and we do not need to replace code.
Defining methods inside the EloquentRepositoryInterface
Now let us define the methods that we will be using throughout our entire application in this interface.
Here is an example,
As you may have noticed the method names are verbose and contains the verbs we just described. Making method names verbose actually makes everything easier.
Implementing the EloquentRepositoryInterface inside the BaseRepository class
Inside the BaseRepository
we created a protected property named as $model and it will receive an instance of class Model. In our constructor we inject the Model class and assign the instance into the $model property so it will be available to all methods. These are the important part of our BaseRepository
class since any new repositories will be extending to this base repository and they won't have to rewrite what we just wrote regarding the implementation details that we interfaced against the EloquentRepositoryInterface
.
Here is an example,
Binding the interface and the repository class in RepositoryServiceProvider class
To your terminal and execute this command to create the class,
php artisan make:provider RepositoryServiceProvider
Then inside that class, inside register()
method is here me bind the EloquentRepositoryInterface.php
and the actual repository, in this case it it the BaseRepository.php
that we created.
Make sure to import these files into the RepositoryServiceProvider
class.
And now to make these changes available in our entire application, we register the RepositoryServiceProvider
class inside the AppServiceProvider
Then execute this composer command in your terminal to read the bindings,
composer dump-autoload
If it doesn't work you must delete the cache manually if you are having problems. These files should be deleted specifically as they will get auto-generated by Laravel whenever we run a caching command. And normally these files are being ignored by git.
- app
- bootstrap
- cache (Delete these files below)
- config.php
- packages.php
- services.php
Adding a UserRepository
Now that we have a our BaseRepository defined, when we create another module or feature it should give us a boost in development time since we eliminated it by applying abstraction using the Repository Pattern.
Now just create another interface and a repository class.
In your terminal execute these commands to create the files,
touch app/Repository/UserRepositoryInterface.php
touch app/Repository/Eloquent/UserRepository.php
Now inside UserRepositoryInterface
we only have to define the interface and extend on to the EloquentRepositoryInterface
And then inside the UserRepository
class, we still implement the UserRepositoryInterface
that we just created and extend BaseRepository
class, by doing this we are applying Inheritance, we inherit all the methods that we just defined from our BaseRepository
class and we will be able to access to the fields inside BaseRepository
class so we do not have to rewrite everything. One thing we only want to override is the first parameter in our constructor, from the base repository it is the Model class, but since it is a user repository then we should replace it with the User model.
And now inside our user repository also contains the methods that we implemented inside the base repository.
And now bind the UserRepositoryInterface with the UserRepository inside the RepositoryServiceProvider class so our application will recognize this new repository that we created in the entire application.
Then execute the command again,
composer dump-autoload
I typically execute this command every time I add a new repository, if I don't then Laravel does not know or recognize about this new repository that we just created.
Creating a UserController and applying Dependency Injection to use UserRepository
In your terminal execute this command,
php artisan make:controller UserController
and define a route inside routes/api.php
,
And now inside our UserController
let us define method index() but before that in our constructor we want to inject the UserRepository
into the Contoller
so that we will have all the methods available at our disposal. In this case the index() method in the Controller is commonly known to return a list of resources, so let's make that as is.
The Repository Pattern also allows us to write less code inside our Controllers and that makes it even better rather having a giant code in the Controller which isn't what we want if we are aiming for better maintainability and readability. Let's keep it that way, clean controllers.
In case you don't agree with me because I am only showing a simple example and it's for retrieving all users only, then for creating a new user and sometimes we have to put some logic into it, maybe some if-else statements, that actually is considered a code smell and we don't want to do that. And also what about if you have to use the same logic in a different Controller, without creating a repository you will have to copy and paste these code. So when we are using the Repository Pattern we just inject it to the class that we need it for without having to rewrite logic.
So let us test that the route that we created by accessing this link in your browser http://localhost:8000/api/users
We should see an empty set. That means everything we did worked and it makes sense because we haven't created any users yet.
So let us create users using the factory, open to your terminal and go to tinker CLI,
php artisan tinker
Then execute this command to create 10 users
User::factory(10)->create()
Then go back to your browser and hit refresh and you should see 10 users being returned from the API.
And there we have it!
Conclusion
In leveraging on this design pattern we took some time to set it up in exchange for better readability and maintainability of our codebase. By doing abstraction and encapsulation we eliminate code duplications as we created a BaseRepository
that shares all the common methods in every new repository that uses Eloquent ORM.
So for any small changes that we will make in the future, we don't have to bother updating every single file because we extended the common methods and by apply a single change saves us ample time and reduces cognitive load and that makes us happy.
On the other hand, this will make our code testable and we can easily scale it. But this isn't a silver bullet unfortunately, we might have sacrificed a little bit of performance as having these repositories are at the center between our data provider and our Controllers or any class that we might need to get data from.
If you aren't using Repository Pattern yet, make sure to try it out yourself. Thank you for taking the time to read and I hope this helps you out in getting started with Repository Pattern in Laravel.
Full source code: https://github.com/carlomigueldy/laravel-repository-pattern
Top comments (9)
thanks for the post, i will try
You're welcome!
And thanks for reading!
Where can we apply the pagination, filters and what if the relationships I want to eager load have some conditions?
Do we add new method on the interface for the above or?
Is this not recreating eloquent itself cause you have just repeated what eloquent does already ist for the sake of one day you will change from MySQL to MongoDB?
Why don't use services class if you don't wanna repeat code for example UserSevice that will have allUsers function and it will give us all users if we need it on UsersController and other controllers and so on and so on.
I feel this repository pattern is redundant and more work for no clear gains to be honest, but good article just you guys always show the simple part and don't show the reality.
Please give your thoughts on the above questions.
What do you think if we add another service layer between the controller and the repository, so the pattern becomes UserController -> UserService -> UserRepository ? where should we put more logic, for example if we want to fetch the data from the model by joining it with another table ?
I want to do the same, so is there any resource I could take a look at it?
What would be the approach if one wants to switch implementation at runtime ( say during a post / get request ) ?
At the moment I have no idea how we can change the implementation based on an HTTP request method, but we can definitely change the implementation based on the Controller. We can do that via Contextual Binding, I have actually written an article about that so here is the link dev.to/carlomigueldy/laravel-inver...
Hopefully that helps you :)
Excellent post!