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.
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.
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 order 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 order management to a 3rd party application, we can build a new module that conforms to OrderRepositoryInterface
‘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 Interfaces. Then, in the Interfaces, create a new file called OrderRepositoryInterface.php and add the following code to it.
<?php
namespace App\Interfaces;
interface OrderRepositoryInterface
{
public function getAllOrders();
public function getOrderById($orderId);
public function deleteOrder($orderId);
public function createOrder(array $orderDetails);
public function updateOrder($orderId, array $newDetails);
public function getFulfilledOrders();
}
Next, in the app folder, create a new folder called Repositories. In this folder, create a new file called OrderRepository.php and add the following code to it.
<?php
namespace App\Repositories;
use App\Interfaces\OrderRepositoryInterface;
use App\Models\Order;
class OrderRepository implements OrderRepositoryInterface
{
public function getAllOrders()
{
return Order::all();
}
public function getOrderById($orderId)
{
return Order::findOrFail($orderId);
}
public function deleteOrder($orderId)
{
Order::destroy($orderId);
}
public function createOrder(array $orderDetails)
{
return Order::create($orderDetails);
}
public function updateOrder($orderId, array $newDetails)
{
return Order::whereId($orderId)->update($newDetails);
}
public function getFulfilledOrders()
{
return Order::where('is_fulfilled', true);
}
}
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.
If, in the future, we decide to retrieve only unfulfilled orders in the getAllOrders()
function, we would only have to make a change in one place instead of tracking down all the places where Order::all()
is declared while risking missing some.
Creating the controllers
With our repository in place, let’s add some code to our controller. Open app/Http/Controllers/OrderController.php and update the code to match the following.
<?php
namespace App\Http\Controllers;
use App\Interfaces\OrderRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class OrderController extends Controller
{
private OrderRepositoryInterface $orderRepository;
public function __construct(OrderRepositoryInterface $orderRepository)
{
$this->orderRepository = $orderRepository;
}
public function index(): JsonResponse
{
return response()->json([
'data' => $this->orderRepository->getAllOrders()
]);
}
public function store(Request $request): JsonResponse
{
$orderDetails = $request->only([
'client',
'details'
]);
return response()->json(
[
'data' => $this->orderRepository->createOrder($orderDetails)
],
Response::HTTP_CREATED
);
}
public function show(Request $request): JsonResponse
{
$orderId = $request->route('id');
return response()->json([
'data' => $this->orderRepository->getOrderById($orderId)
]);
}
public function update(Request $request): JsonResponse
{
$orderId = $request->route('id');
$orderDetails = $request->only([
'client',
'details'
]);
return response()->json([
'data' => $this->orderRepository->updateOrder($orderId, $orderDetails)
]);
}
public function destroy(Request $request): JsonResponse
{
$orderId = $request->route('id');
$this->orderRepository->deleteOrder($orderId);
return response()->json(null, Response::HTTP_NO_CONTENT);
}
}
The code injects an OrderRepositoryInterface
instance via the constructor and uses the relevant object’s methods in each controller method.
First, within the index()
method, it calls the getAllOrders()
method defined in the orderRepository
to retrieve the list of orders and returns a response in JSON format.
Next, the store()
method calls the createOrder()
method from the orderRepository
to create a new order. This takes the details of the order that needs to be created as an array and returns a successful response afterward.
Within the show()
method in the controller, it retrieves the unique order Id
from the route and passes it to the getOrderById()
as a parameter. This fetches the details of the order with a matching Id from the database and returns a response in JSON format.
Then, to update the details of an already created order, it calls the updateOrder()
method from the repository. This takes two parameters: the unique id of the order and the details that need to be updated.
Lastly, the destroy()
method retrieves the unique id of a particular order from the route and calls the deleteOrder()
method from the repository to delete it.
Adding the routes
To map each method defined in the controller to specific routes, add the following code to routes.
Route::get('orders', [OrderController::class, 'index']);
Route::get('orders/{id}', [OrderController::class, 'show']);
Route::post('orders', [OrderController::class, 'store']);
Route::put('orders/{id}', [OrderController::class, 'update']);
Route::delete('orders/{id}', [OrderController::class, 'delete']);
Remember to include the import
statement for the OrderController.
use App\Http\Controllers\OrderController;
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 for a large-scale project – one of which is loosely coupled code where we are coding to abstractions, not concrete implementations.