SOLID Principles in Laravel: A Beginner's Series
Tags: laravel, softwareengineering, php, architecture
Published: 2024-03-28 22:16:33

_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](https://laravel.com/) 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);
    }
}
```
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](https://laravel.com/docs/11.x/validation#creating-form-requests).
2. Product Creation: Move the product creation logic to a [service class](https://medium.com/@laravelprotips/understanding-laravel-service-classes-a-comprehensive-guide-1f22310c70bd).
3. Notification: Handle the [notification](https://laravel.com/docs/11.x/notifications) 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',
        ];
    }
}
```

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'],
        ]);
    }
}
```

Lastly, we will generate a [Laravel Notification](https://laravel.com/docs/11.x/notifications) 

```
<?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);
    }
}
```

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);
    }
}
```
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**.