Implementing Single Responsibility Principle in Laravel

Single responsibility Principle (SRP) is first of five Solid principles. It is defined as “A class should have only one reason to change.”

Single Controller Handling All Operations

<?php
namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;

class UserController extends Controller {
  public function index() { return User::all(); }
  public function store(Request $request) { /* Validation & Create Logic */ }
  public function show($id) { /* Find User by ID */ }
  public function update(Request $request, $id) { /* Update User Logic */ }
  public function destroy($id) { /* Delete User Logic */ }
}
?>

Above is the example of one class doing everything… and logic is inside every method. This breaks the SRP. To start with this looks faster and easy but as application grows, you will find it very difficult to make any changes.

In case we want to make any extensions then It will be vert difficult and testing is also not easy as all methods mix logic and validations and views

Single Responsibility Principle

To implement this controller should delegate responsibilities or various tasks that it is doing… and should confine it self with in the role of mediator between different parts of the application.

All database related tasks should be done by Repositories and repository should be invoked by service and in turn service should be invoked by Controller… that way all logic will be in repositories and services and controller will left with only task of mediator between requests and logic

We can do it in the following steps

Step 1: Extract Validation Logic Using Requests

php artisan make:request UserRequest

This will create a file with path as App/Requests/ UserRequest.php

This is used to decouple validation logic from controller

<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UserRequest extends FormRequest{
  public function authorize(): bool {
    return true; // Authorize all users for now
  }
  public function rules(): array {
    return [
      'name' => 'required|string|max:255',
      'email' => 'required|email|unique:users,email,' . $this->id,
    ];
  }
}
?>

Step 2: Format Responses Using Resources

> php artisan make:resource UserResource
This is used to do any formatting on the model.

<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource {
  public function toArray($request) {
    return [
      'id' => $this->id,
      'name' => $this->name,
      'email' => $this->email,
    ];
  }
}
?>

Step 3: Handle Database Logic with Repositories

Create repository file as app/Repositories/UserRepository.php
Repository directly interacts with the model and carries out db operations

<?php
namespace App\Http\Repositories;
use DB;

class UserRepository {

  public function getAllUsers(){
    return DB::table('users')->get(); // Query all users
  }
  public function getUserById($id){
    return DB::table('users')->where('id', $id)->first(); // Get user by ID
  }
  public function createUser(array $data){
    return DB::table('users')->insert($data); // Insert a new user
  }
  public function updateUser($id, array $data){
    return DB::table('users')->where('id', $id)->update($data); // Update user by ID
  }
  public function softDeleteUser($id){
    return DB::table('users')->where('id', $id)->update(['deleted_at' => now()]); // Soft delete user
  }
  public function getTrashedUsers(){
    return DB::table('users')->whereNotNull('deleted_at')->get(); // Get soft deleted users
  }
  public function restoreUser($id){
    return DB::table('users')->where('id', $id)->update(['deleted_at' => null]); // Restore user
  }

}
?>

Step 4: Delegate Business Logic to Services

Create service file as app/Services/UserService.php
This is directly linked with Controller and controller delegates its responsibility to services. This in turn delegates db operations to repository

<?php
namespace App\Http\Services;
use App\Http\Repositories\UserRepository;

class UserService{
  protected $repository;
  public function __construct(UserRepository $objRepo){
    $this->repository = $objRepo;
  }

  public function getAllUsers(){
    return $this->repository->getAllUsers();
  }

}
?>

Step 5: Simplify the Controller

Finally, the controller becomes a lightweight mediator:

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests\UserRequest;
use App\Http\Services\UserService;

class UserController extends Controller{

  protected $service;
  public function __construct(UserService $service){
    $this->service = $service;
  }

  public function index(){
    return $this->service->getAllUsers();
  }

  public function store(UserRequest $request){
    return $this->service->createUser($request->validated());
  }

  public function show($id){
    return $this->service->getUserById($id);
  }

  public function update(UserRequest $request, $id){
    return $this->service->updateUser($id, $request->validated());
  }

  public function destroy($id){
    return $this->service->deleteUser($id);
  }
}
?>

Conclusion

Many people say that single responsibility means single method per class. But for me SRP means class has a single purpose. By structuring Laravel controllers with SRP, your code becomes cleaner, more testable, and easier to extend.

Leave a Comment

Your email address will not be published. Required fields are marked *

Share via
Copy link
Powered by Social Snap