The Repository and Service Pattern is a powerful way to structure your Laravel applications. It promotes clean, maintainable, and testable code by separating concerns and decoupling your application logic. In this guide, we’ll walk through how to implement this pattern in Laravel with a simple CRUD example for managing products.
Step 1: Create Repository Interface
The first step is to define an interface for the repository. This ensures that the repository adheres to a contract, making it easier to swap implementations later.
Create the file app/Repositories/Interfaces/ProductRepositoryInterface.php:
namespace App\Repositories\Interfaces;
interface ProductRepositoryInterface
{
public function getAll();
public function findById($id);
public function create(array $data);
public function update($id, array $data);
public function delete($id);
}
Step 2: Create Repository Implementation
Next, implement the repository interface. This class will handle all database interactions for the Product model.
Create the file app/Repositories/ProductRepository.php:
namespace App\Repositories;
use App\Models\Product;
use App\Repositories\Interfaces\ProductRepositoryInterface;
class ProductRepository implements ProductRepositoryInterface
{
public function getAll()
{
return Product::all();
}
public function findById($id)
{
return Product::findOrFail($id);
}
public function create(array $data)
{
return Product::create($data);
}
public function update($id, array $data)
{
$product = Product::findOrFail($id);
$product->update($data);
return $product;
}
public function delete($id)
{
return Product::destroy($id);
}
}
Step 3: Create Service Interface
The service layer acts as an intermediary between the controller and the repository. It contains the business logic of your application.
Create the file app/Services/Interfaces/ProductServiceInterface.php:
namespace App\Services\Interfaces;
interface ProductServiceInterface
{
public function getAllProducts();
public function getProductById($id);
public function createProduct(array $data);
public function updateProduct($id, array $data);
public function deleteProduct($id);
}
Step 4: Create Service Implementation
Now, implement the service interface. This class will use the repository to perform operations.
Create the file app/Services/ProductService.php:
namespace App\Services;
use App\Repositories\Interfaces\ProductRepositoryInterface;
use App\Services\Interfaces\ProductServiceInterface;
class ProductService implements ProductServiceInterface
{
protected $productRepository;
public function __construct(ProductRepositoryInterface $productRepository)
{
$this->productRepository = $productRepository;
}
public function getAllProducts()
{
return $this->productRepository->getAll();
}
public function getProductById($id)
{
return $this->productRepository->findById($id);
}
public function createProduct(array $data)
{
return $this->productRepository->create($data);
}
public function updateProduct($id, array $data)
{
return $this->productRepository->update($id, $data);
}
public function deleteProduct($id)
{
return $this->productRepository->delete($id);
}
}
Step 5: Bind Interfaces to Implementations
To make the repository and service injectable, bind the interfaces to their implementations in the AppServiceProvider.
Modify app/Providers/AppServiceProvider.php:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Repositories\Interfaces\ProductRepositoryInterface;
use App\Repositories\ProductRepository;
use App\Services\Interfaces\ProductServiceInterface;
use App\Services\ProductService;
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(ProductRepositoryInterface::class, ProductRepository::class);
$this->app->bind(ProductServiceInterface::class, ProductService::class);
}
public function boot()
{
//
}
}
Step 6: Update the Controller
Now, update the ProductController to use the service layer. This keeps the controller lightweight and focused on handling HTTP requests.
Modify app/Http/Controllers/ProductController.php:
namespace App\Http\Controllers;
use App\Services\Interfaces\ProductServiceInterface;
use Illuminate\Http\Request;
class ProductController extends Controller
{
protected $productService;
public function __construct(ProductServiceInterface $productService)
{
$this->productService = $productService;
}
public function index()
{
return response()->json($this->productService->getAllProducts());
}
public function show($id)
{
return response()->json($this->productService->getProductById($id));
}
public function store(Request $request)
{
$data = $request->validate([
'name' => 'required|string|max:255',
'price' => 'required|numeric',
'stock' => 'required|integer',
]);
return response()->json($this->productService->createProduct($data));
}
public function update(Request $request, $id)
{
$data = $request->validate([
'name' => 'string|max:255',
'price' => 'numeric',
'stock' => 'integer',
]);
return response()->json($this->productService->updateProduct($id, $data));
}
public function destroy($id)
{
return response()->json($this->productService->deleteProduct($id));
}
}
Step 7: Define API Routes
Finally, define the API routes for the ProductController.
Modify routes/api.php:
use App\Http\Controllers\ProductController;
Route::prefix('products')->group(function () {
Route::get('/', [ProductController::class, 'index']);
Route::get('/{id}', [ProductController::class, 'show']);
Route::post('/', [ProductController::class, 'store']);
Route::put('/{id}', [ProductController::class, 'update']);
Route::delete('/{id}', [ProductController::class, 'destroy']);
});
Benefits of Using Repository and Service Pattern
Decoupling: The service layer is independent of the repository implementation.
Easy to Swap Implementations: You can switch to another repository (e.g., API-based repository) without changing the service or controller.
Better Testing: You can mock the repository or service in unit tests.
Cleaner Code: The controller is lightweight and follows SOLID principles.
Conclusion
By implementing the Repository and Service Pattern in Laravel, you can create a clean, maintainable, and scalable application. This pattern separates concerns, making your code easier to test and extend. In this guide, we walked through a simple CRUD example for managing products, but you can apply this pattern to any part of your application.
Next Steps
Explore advanced features like caching and pagination in the repository.
Implement validation and error handling in the service layer.
Use Dependency Injection to further decouple your application.
Top comments (0)