DEV Community

Cover image for SOLID Principles in Laravel: A Beginner's Series
Shamsuddeen Abdulkadir
Shamsuddeen Abdulkadir

Posted on

SOLID Principles in Laravel: A Beginner's Series

Robert C. Martin, also known as Uncle Bob, emphasizes in his book Clean Architecture the importance of good code as the foundation of good software systems. He compares well-made code to high-quality bricks in building construction. Even with excellent architectural plans, poorly made bricks can undermine the entire structure. On the other hand, well-made bricks can still result in a messy construction if not assembled properly. This is where SOLID principles play a crucial role.

SOLID is an acronym representing five fundamental principles in object-oriented programming and design. Let's break it down:

  1. S stands for Single Responsibility Principle (SRP)
  2. O stands for Open/Closed Principle (OCP)
  3. L stands for Liskov Substitution Principle (LSP)
  4. I stands for Interface Segregation Principle (ISP)
  5. D stands for Dependency Inversion Principle (DIP)

These principles, coined by Robert C. Martin, provide guidelines for writing clean, maintainable, and extensible code. Each principle focuses on a specific aspect of software design, aiming to improve modularity, flexibility, and scalability. Understanding and applying these principles can lead to more robust and adaptable software systems.

Single Responsibility Principle
In this series kickoff, we'll explore SRP with example using PHP & Laravel framework, showcasing how it fosters cleaner, more maintainable code.
The Single Responsibility Principle (SRP) emphasizes that a class or method should have only one reason to change, meaning it should have a single responsibility or purpose. By adhering to SRP, we ensure that each entity in our codebase is focused on doing one thing well, making our code more maintainable, understandable, and flexible.

Imagine writing a Laravel controller for product management that not only validates & store product data but also dispatches email notifications to interested customers.

<?php

namespace App\Http\Controllers;

use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use App\Mail\ProductRequestedNotification;

class ProductController extends Controller
{
    public function store(Request $request)
    {
        // Validation rules
        $validatedData = $request->validate([
            'name' => 'required|string|max:255',
            'description' => 'required|string',
            'price' => 'required|numeric|min:0',
            'quantity' => 'required|integer|min:0',
            'email' => 'required|email',
        ]);

        // Create new product
        $product = Product::create([
            'name' => $validatedData['name'],
            'description' => $validatedData['description'],
            'price' => $validatedData['price'],
            'quantity' => $validatedData['quantity'],
        ]);

        // Send notification to customer
        Mail::to($validatedData['email'])->send(new ProductRequestedNotification($product));

        // Return success response
        return response()->json(['message' => 'Product stored successfully'], 200);
    }
}
Enter fullscreen mode Exit fullscreen mode

In the provided example, the ProductController is handling multiple responsibilities, including data validation, product storage, and email notification. This violates the Single Responsibility Principle (SRP), which states that a class should have only one reason to change.

To adhere to SRP and improve the controller's clarity and extensibility, we can refactor it to delegate each responsibility to separate classes. This separation of concerns enhances maintainability and allows for easier extension in the future:

  1. Request Validation: Extract the validation rules to a separate request class.
  2. Product Creation: Move the product creation logic to a service class.
  3. Notification: Handle the notification logic in a separate class.

First, create a new Form Request class using the artisan command:
php artisan make:request StoreProductRequest

This will generate a new file StoreProductRequest.php in the app/Http/Requests directory.

Open the StoreProductRequest.php file and update the rules() method with the validation rules:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreProductRequest extends FormRequest
{
    public function authorize()
    {
        return true; // Authorization logic can be added if needed
    }

    public function rules()
    {
        return [
            'name' => 'required|string|max:255',
            'description' => 'required|string',
            'price' => 'required|numeric|min:0',
            'quantity' => 'required|integer|min:0',
            'email' => 'required|email',
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

To create the ProductService class, follow these steps:

  1. Navigate to the app/Http/ directory in your Laravel application.
  2. Create a new folder named Services if it doesn't exist already.
  3. Inside the Services folder, create a new file named ProductService.php.
  4. Open the ProductService.php file and add the following code:
<?php

namespace App\Services;

use App\Models\Product;

class ProductService
{
    public function create(array $data): Product
    {
        return Product::create([
            'name' => $data['name'],
            'description' => $data['description'],
            'price' => $data['price'],
            'quantity' => $data['quantity'],
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Lastly, we will generate a Laravel Notification

<?php

namespace App\Notifications;

use App\Models\Product;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;

class ProductRequestedNotification extends Notification implements ShouldQueue
{
    use Queueable;

    protected $product;

    public function __construct(Product $product)
    {
        $this->product = $product;
    }

    public function via($notifiable)
    {
        return ['mail'];
    }

    public function toMail($notifiable)
    {
        return (new MailMessage)
            ->line('Your product request has been received successfully.')
            ->line('Product Name: ' . $this->product->name)
            ->line('Description: ' . $this->product->description)
            ->line('Price: ' . $this->product->price)
            ->line('Quantity: ' . $this->product->quantity);
    }
}
Enter fullscreen mode Exit fullscreen mode

Here's what the ProductController looks like after refactoring:

<?php

namespace App\Http\Controllers;

use App\Http\Requests\ProductRequest;
use App\Services\ProductService;
use App\Notifications\ProductRequestedNotification;

class ProductController extends Controller
{
    protected $productService;

    public function __construct(ProductService $productService)
    {
        $this->productService = $productService;
    }

    public function store(ProductRequest $request)
    {
        // Create new product
        $product = $this->productService->create($request->validated());

        // Send notification to customer
        $product->notify(new ProductRequestedNotification($product));

        // Return success response
        return response()->json(['message' => 'Product stored successfully'], 200);
    }
}
Enter fullscreen mode Exit fullscreen mode

With this refactoring, the ProductController is now responsible only for handling HTTP requests and responses, while the validation rules are encapsulated in the StoreProductRequest class, the email notification is handled by ProductRequestedNotification class, and the business logic for creating a product is handled by the ProductService class. This separation of concerns improves code readability, maintainability, and testability.

In the upcoming part of the series, we'll delve into another fundamental principle: the Open/Closed Principle.

Top comments (1)

Collapse
 
beelal profile image
Bilal Mohammed Olagunju

👍