SOLID Principles in Laravel…

techiydude
8 min readJun 23, 2023

--

Let’s quickly define the SOLID concepts before delving into them. The abbreviation SOLID stands for a group of five design principles intended to increase the flexibility, maintainability, and understandability of software. These principles help programmers create organized, modular code that ensures the scalability and extensibility of their programs.

SOLID is an acronym for the five principles it encompasses:

  • S — Single Responsibility Principle (SRP)
  • O — Open-Closed Principle (OCP)
  • L — Liskov Substitution Principle (LSP)
  • I — Interface Segregation Principle (ISP)
  • D — Dependency Inversion Principle (DIP)

SOLID is short for:

  1. Single Responsibility Principle (SRP).
  2. Open/Closed Principle (OCP).
  3. Liskov Substitution Principle (LSP).
  4. Interface Segregation Principle (ISP).
  5. Dependency Inversion Principle (DIP).

In this post will discuss the SOLID principles and how we can used in Laravel with examples.

  • Single Responsibility Principle (SRP): A class should have only one responsibility.
  • Open-Closed Principle (OCP): A class should be open for extension, but closed for modification.
  • Liskov Substitution Principle (LSP): A subclass should be substitutable for its parent class.
  • Interface Segregation Principle (ISP): A client should not be forced to depend on methods that it does not use.
  • Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules. Instead, both high-level and low-level modules should depend on abstractions.

Here’s an example implementation of the SOLID principles in a blog post module in Laravel:

  1. Single Responsibility Principle (SRP): The SRP states that a class should have only one reason to change. It means that a class should have a single responsibility or purpose. By adhering to this principle, we ensure that a class is focused on doing one thing well.
// BlogPostController.php
class BlogPostController extends Controller
{
public function store(Request $request)
{
// Validate the request data

// Delegate the processing to the BlogPostService
app(BlogPostService::class)->create($request->all());

// Return a response
}
}


Here’s a summary of the code functionality:

BlogPostController.php:

  • Handles HTTP requests related to blog posts.
  • The store method is responsible for creating a new blog post.
  • Validates the incoming request data.
  • Delegates the processing to the BlogPostService.
  • Returns a response to the client.
// BlogPostService.php
class BlogPostService
{

private $repository;

public function __construct(BlogPostRepository $repository)
{
$this->repository = $repository;
}


public function create(array $data)
{
// Perform business logic, such as creating a new blog post
$post = $this->repository->create($data);
// Perform additional operations, if needed

// Return the created blog post
return $post;
}
}

BlogPostService.php:
Represents a service layer in the application.

  • Encapsulates the business logic for blog posts.
  • The create method creates a new blog post.
  • Creates a new instance of the BlogPost model and saves it to the database.
  • Can perform additional operations if needed.
  • Returns the created blog post.
// BlogPostRepository.php
class BlogPostRepository
{
public function create(array $data)
{
// Create and persist the blog post in the database
return BlogPost::create($data);
}
}

BlogPostRepository.php:
Represents a repository for persisting and retrieving blog post data.

  • The create method creates a new blog post in the database.
  • Uses the BlogPost model’s create method internally.
  • Provides an abstraction for interacting with the data layer.

2. Open/Closed Principle (OCP):The OCP states that classes should be open for extension but closed for modification. It means that you should be able to add new functionality or behaviors to a class without modifying its existing code.


// ApprovedBlogPostService.php
class ApprovedBlogPostService extends BlogPostService
{
public function create(array $data)
{
// Perform business logic, such as creating a new blog post
$post = parent::create($data);

// Perform additional operations specific to approved blog posts
$this->notifySubscribers($post);

// Return the created blog post
return $post;
}

private function notifySubscribers(BlogPost $post)
{
// Logic to send email notifications to subscribers
}
}

ApprovedBlogPostService (extension for OCP):

  • Extends the BlogPostService class.
  • Represents a specialized behavior for creating approved blog posts.
  • Overrides the create method inherited from BlogPostService.
  • Calls the parent’s create method to perform the common business logic.
  • Performs additional operations specific to approved blog posts, such as notifying subscribers.
  • Implements a private notifySubscribers method to handle email notifications.

3.Liskov Substitution Principle (LSP):According to the LSP, objects of a superclass should be interchangeable with objects of its subclasses without impairing the program’s correctness. It guarantees that subclasses and their base classes can be used interchangeably without generating any unexpected behaviour.

class PremiumBlogPostController extends BlogPostController
{
public function store(Request $request)
{
// Validate the request data

// Delegate the processing to the PremiumBlogPostService
app(PremiumBlogPostService::class)->create($request->all());

// Return a response
}
}

class PremiumBlogPostService extends BlogPostService
{
public function create(array $data)
{
// Perform additional business logic specific to premium blog posts
// ...

// Delegate the remaining processing to the parent create method
return parent::create($data);
}
}

class PremiumBlogPostRepository extends BlogPostRepository
{
public function create(array $data)
{
// Perform additional data persistence specific to premium blog posts
// ...

// Delegate the remaining processing to the parent create method
return parent::create($data);
}
}

In this code, the PremiumBlogPostController, PremiumBlogPostService, and PremiumBlogPostRepository classes extend their respective base classes. However, they do not violate the behavior of their base classes. This means that instances of the derived classes can be substituted in place of their base class counterparts without causing any problems.

For example, the PremiumBlogPostController class extends the BlogPostController class. The PremiumBlogPostController class adds additional functionality for processing premium blog posts, but it does not change the behavior of the BlogPostController class. This means that an instance of the PremiumBlogPostController class can be used in place of an instance of the BlogPostController class without causing any problems.

The LSP is an important principle in object-oriented programming. It helps to ensure that code is reusable and maintainable.

4.Interface Segregation Principle (ISP): The Interface Segregation Principle (ISP) suggests that clients should not be compelled to depend on interfaces that they don’t use. This principle emphasizes the segregation of interfaces into smaller, more specific ones that are tailored to meet the precise needs of individual clients.

Example:

interface Createable
{
public function create(array $data);
}

interface Persistable
{
public function persist(array $data);
}

class BlogPostController extends Controller
{
private $blogPostService;

public function __construct(Createable $blogPostService)
{
$this->blogPostService = $blogPostService;
}

public function store(Request $request)
{
// Validate the request data

// Delegate the processing to the BlogPostService
$this->blogPostService->create($request->all());

// Return a response
}
}

class BlogPostService implements Createable
{
private $repository;

public function __construct(Persistable $repository)
{
$this->repository = $repository;
}

public function create(array $data)
{
// Perform business logic, such as creating a new blog post
$post = $this->repository->persist($data);

// Perform additional operations, if needed

// Return the created blog post
return $post;
}
}

class BlogPostRepository implements Persistable
{
public function persist(array $data)
{
// Create and persist the blog post in the database
return BlogPost::create($data);
}
}

In this code, the BlogPostController class only needs to create a blog post, so it should not have to depend on the Persistable interface.

The Persistable interface has been divided into the Createable and Persistable interfaces through refactoring of the code.The Createable interface only defines the method that is needed by the BlogPostController class, which is the create() method. The Persistable interface defines the method that is needed by the BlogPostService class, which is the persist() method.

  • The Persistable interface was split into two smaller interfaces: Createable and Persistable.
  • The BlogPostController class was updated to only depend on the Createable interface.
  • The BlogPostService class was updated to only depend on the Persistable interface.
  • This makes the code more modular and easier to understand.
  • It also makes it easier to test the code, because the BlogPostController class only needs to be tested for the create() method, and the BlogPostService class only needs to be tested for the persist() method.

5.Dependency Inversion Principle (DIP):High-level modules shouldn’t be dependent on low-level modules, according to the DIP. They ought to both rely on abstractions. It emphasises the use of interfaces or abstract classes to specify dependencies and encourages loose coupling between classes.

interface BlogPostRepositoryInterface
{
public function create(array $data);
}

class BlogPostController extends Controller
{
private $blogPostService;

public function __construct(BlogPostService $blogPostService)
{
$this->blogPostService = $blogPostService;
}

public function store(Request $request)
{
// Validate the request data

// Delegate the processing to the BlogPostService
$this->blogPostService->create($request->all());

// Return a response
}
}

class BlogPostService
{
private $repository;

public function __construct(BlogPostRepositoryInterface $repository)
{
$this->repository = $repository;
}

public function create(array $data)
{
// Perform business logic, such as creating a new blog post
$post = $this->repository->create($data);

// Perform additional operations, if needed

// Return the created blog post
return $post;
}
}

class BlogPostRepository implements BlogPostRepositoryInterface
{
public function create(array $data)
{
// Create and persist the blog post in the database
return BlogPost::create($data);
}
}
  • The BlogPostRepositoryInterface is defined as an abstraction representing the functionality of the BlogPostRepository class.
  • The BlogPostRepository class implements the BlogPostRepositoryInterface.
  • The BlogPostService class depends on the BlogPostRepositoryInterface through its constructor, adhering to the DIP.
  • By depending on the interface instead of the concrete implementation, the BlogPostService can work with any class that implements the BlogPostRepositoryInterface, promoting flexibility and decoupling.
  • The BlogPostController class depends on the BlogPostService through its constructor, again relying on the abstraction.
  • This allows the BlogPostController to work with any class that fulfills the BlogPostService contract, regardless of the specific implementation of the BlogPostService.
  • This approach enables easier maintenance, testing, and future extensibility.
  • New implementations of the BlogPostRepositoryInterface can be introduced without affecting the BlogPostService or BlogPostController classes.
  • Following the DIP results in loose coupling, modular code, and better separation of concerns.
  • Higher-level modules depend on abstractions (interfaces) rather than specific implementations of lower-level modules, improving code organization and flexibility.

Here are some practical examples where applying SOLID principles improved Laravel projects:

  1. Refactoring Monolithic Controller: By following the Single Responsibility Principle (SRP), you can refactor a monolithic controller into smaller, single-responsibility classes. For example, separating authentication logic into an AuthController and database operations into a DatabaseController. This promotes code organization, reusability, and maintainability.
  2. Utilizing Interfaces for Dependency Abstraction: Applying the Interface Segregation Principle (ISP) allows you to define interfaces that expose only the necessary methods for specific clients. For instance, creating an EmailSenderInterface that abstracts the email sending functionality. This enables different implementations for various email services, making it easier to switch between providers or mock them for testing.
  3. Implementing Dependency Injection: By adhering to the Dependency Inversion Principle (DIP) and utilizing dependency injection, you can achieve loose coupling and improve testability. For example, injecting a LoggerInterface into a class rather than creating a concrete logger instance internally. This allows for easier testing by substituting the logger with a mock implementation.
  4. Separating Database Operations into Repository Classes: Following the SRP, you can create Repository classes responsible for database operations, such as UserRepository or PostRepository. These repositories handle data persistence and provide a clean abstraction layer for interacting with the database. This enhances code organization, improves reusability, and simplifies maintenance.
  5. Applying Open-Closed Principle in Extension: Utilizing the Open-Closed Principle (OCP), you can design your code to be open for extension but closed for modification. For instance, creating an abstract PaymentGateway class with common payment methods and then extending it for specific payment gateways like PayPalGateway or StripeGateway. This allows for easily adding new payment gateways without modifying the existing codebase.

If you want to learn how laravel custom helper works in laravel , so you can read this tutorial:

And if you love the content and want to support more awesome articles, consider buying me a coffee! ☕️🥳 Your support means the world to me and helps keep the knowledge flowing. You can do that right here: 👉 [Buy Me a Coffee](https://buymeacoffee.com/jainaman)

One of my most productive days was throwing away 1000 lines of code. Ken Thompson

--

--

techiydude
techiydude

Written by techiydude

I’m a developer who shares advanced Laravel and Node.js insights on Medium.

Responses (4)