A repository can be defined as a layer of abstraction between the domain and data mapping layers, one that provides an avenue of mediation between both, via a collection-like interface for accessing domain objects.
Modern PHP frameworks, such as Laravel and Symfony, interact with databases via Object-relational mappers (ORMs); Symfony uses Doctrine as its default ORM and Laravel uses Eloquent.
Both take different approaches in how database interaction works. With Eloquent, Models are generated for each database table, forming the basis of interaction. Doctrine, however, uses the Repository pattern where each Entity has a corresponding repository containing helper functions to interact with the database. While Laravel doesn't provide this functionality out of the box, it is possible to use the Repository pattern in Laravel projects.
A key benefit of the Repository pattern is that it allows us to use the Principle of Dependency Inversion (or code to abstractions, not concretions). This makes our code more robust to changes, such as if a decision was made later on to switch to a data source that isn't supported by Eloquent.
It also helps with keeping the code organized and avoiding duplication, as database-related logic is kept in one place. While this benefit is not immediately apparent in small projects, it becomes more observable in large-scale projects which have to be maintained for many years.
In this article, I will show you how to implement the Repository pattern in your Laravel applications. To do that, we will build an API to manage orders received from clients for a company.
Getting started
Create a new Laravel project and cd into the directory using the following commands
laravel new note_block
cd note_block
Set up the database
For this tutorial, we'll use MySQL as our database. To do that, in the .env file, update the database-related parameters as shown below.
Finally, using your preferred database management application, create a new database called note_block.
Generate the initial data for the database
We are building an order management application, so let's create the model for it by running the following command.
php artisan make:model User -a
The -a
argument lets Artisan know that we want to create a migration file, seeder, factory, and controller for the User model.
The command above will create five new files:
- controller in app/Http/Controllers/UserController.php
- database factory in database/factories/UserFactory.php
- migration file in database/migrations/YYYY_MM_DD_HHMMSS_create_users_table.php
- model located in app/Models/User.php
- seeder file in database/seeders/UserSeeder.php
In database/migrations/YYYY_MM_DD_HHMMSS_create_users_table.php
, update the up function to match the following.
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->text('username');
$table->string('email');
$table->string('password');
$table->boolean('status')->default(false);
$table->timestamps();
});
}
As specified in the migration file, the order table will have the following columns:
Next, let's update UserFactory so that it can generate a dummy users to seed the database with. In database/factories/UserFactory.php
, update the definition function to match the information in your migration file.
Next, open database/seeders/UserSeeder.php
and update the run
function to match the following.
public function run()
{
User::factory()->times(50)->create();
}
In database/seeders/DatabaseSeeder.php
, add the following to the run function.
$this->call([UserSeeder::class]);
Finally, run your migrations and seed the database using the following command.
php artisan migrate --seed
Create the Repository
Before we create a repository for the Order model, let's define an interface to specify all the methods which the repository must declare. Instead of relying directly on the repository class, our controller (and any user component we may build in the future) will depend on the interface.
This makes our code flexible because, should it become necessary to make a change in the future, the controller remains unaffected. For instance, if we decided to outsource user management to a 3rd party application, we can build a new module that conforms to UserRepositoryInterface's signature and swap the binding declarations and our controller will work exactly as expected - without touching a single line of code in the controller.
In the app directory, create a new folder called Contract
. Then, in this folder, create a new file called UserRepositoryInterface.php
and add the following code to it.
<?php
namespace App\Contract;
use App\Models\User;
interface UserRepositoryInterface
{
public function getAllUsers();
public function getUserById(User $user);
public function deleteUser(User $user);
public function createUser(array $attributes);
public function updateUser(User $user, array $attributes);
}
After, in the app folder, create a new folder called Repositories
. In this folder, create a new file called UserRepository.php
and add the following code to it.
<?php
namespace App\Repositories;
use App\Contract\UserRepositoryInterface;
use App\Models\User;
class UserRepository implements UserRepositoryInterface
{
public function getAllUsers()
{
return User::all();
}
public function getUserById(User $user)
{
return $user;
}
public function deleteUser(User $user)
{
$user->delete();
}
public function createUser(array $attributes)
{
return User::create($attributes);
}
public function updateUser(User $user, array $attributes)
{
return $user->update($attributes);
}
}
Apart from the flexibility provided by the interface, encapsulating queries in this manner has the added advantage that we don't have to duplicate queries throughout the application.
Creating the controllers
With our repository in place, let's add some code to our controller. Open app/Http/Controllers/UsersController.php
and update the code to match the following.
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Contract\UserRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Hash;
class UsersController extends Controller
{
public function __construct(
private UserRepositoryInterface $repository
)
{
}
public function index(): JsonResponse
{
return response()->json([
'data' => $this->repository->getAllUsers()
]);
}
public function store(Request $request): JsonResponse
{
$attributes = $request->only([
'username',
'email'
]);
return response()->json(
[
'data' => $this->repository->createUser(array_merge($attributes, [
'password' => Hash:make($request->input('password')
]))
],
Response::HTTP_CREATED
);
}
public function show(User $user): JsonResponse
{
return response()->json([
'data' => $user
]);
}
public function update(User $user, Request $request): JsonResponse
{
$attributes = $request->only([
'username',
'email'
]);
return response()->json([
'data' => $this->repository->updateUser($user, $attributes)
]);
}
public function destroy(User $user): JsonResponse
{
$this->repository->deleteUser($user);
return response()->json(null, Response::HTTP_NO_CONTENT);
}
}
Bind the interface and the implementation
The last thing we need to do is bind UserRepository to UserRepositoryInterface in Laravel's Service Container; we do this via a Service Provider. Create one using the following command.
php artisan make:provider RepositoryServiceProvider
Open app/Providers/RepositoryServiceProvider.php
and update the register function to match the following.
public function register()
{
$this->app->bind(UserRepositoryInterface::class, UserRepository::class);
}
Remember to include the import statement for UserRepository and UserRepositoryInterface.
Finally, add the new Service Provider to the providers array in config/app.php
.
'providers' => [
// ...other declared providers
App\Providers\RepositoryServiceProvider::class,
];
Adding the routes
To map each method defined in the controller to specific routes, add the following code to routes/api.php
.
Route::get('users', [UsersController::class, 'index']);
Route::get('users/{user}', [UsersController::class, 'show']);
Route::post('users', [UsersController::class, 'store']);
Route::put('users/{user}', [UsersController::class, 'update']);
Route::delete('users/{user}', [UsersController::class, 'delete']);
Test the application
Run the application using the following command.
php artisan serve
By default, the served application will be available at localhost:8000. Using Postman or cURL, we can make requests to our newly created API.
Run the following command to test the /api/users
endpoint using cURL:
That's how to use the Repository Pattern in a Laravel application
In this article, we learned about the Repository pattern and how to use it in a Laravel application. We've also seen some of the benefits it offers a large-scale project - one of which is loosely coupled code where we are coding to abstractions, not concrete implementations.
Top comments (0)