Laravel Service Classes: Usage, Examples, and Best Practices

Service Classes are very important in managing specific operations. This guide covers everything you need to know about Service Classes, including usage, examples, and best practices.

What is a Service Class in Laravel?

In Laravel, a Service Class is a type of PHP class designed to handle specific operations within an application. Unlike other classes, they are not typically derived from or inherit properties from others. These classes are often named with a “Service” suffix, such as “UserService.”

Service Classes play a crucial role when additional logic needs to be integrated with a particular Eloquent model. For example, if you’re working with the “User” model, you might implement a “UserService” to manage user-related operations. Similarly, for specialized functions like payment processing, you might create a dedicated Service Class like “PaymentService.”

namespace App\Services;

class UserService {
    
    public function createUser(array $userData)
    {
        // Logic to create and return a user.
    }
    
}
namespace App\Services;

class CartService {
    
    public function getCartFromSession() 
    {
        // Logic to retrieve and return a cart from session.
    }
    
}
namespace App\Services;

class PaymentService {
    
    public function processPayment($amount) 
    {
        // Logic to process a payment.
    }
    
}

Each Service Class in these examples fulfills a unique function: managing user data, facilitating cart operations, or handling payment processing.

Service Classes vs. Models in Laravel

In Laravel, you might wonder where to put your code: inside a Model or a Service? Let’s keep it simple.

Models are like blueprints. They define how your data looks and how it’s related. But they can get messy if you put too many codes in them.

Services, on the other hand, are like helpers. They do specific tasks for you, like handling orders or processing payments.

For example, think about an Order. If you stuff all the order-related code into the Order Model, it can become messy, especially as your project grows.

A better idea is to use a service. Here’s how you can do it:

namespace App\Services;

use App\Models\Order;

class OrderService {
    // Methods to handle order-related tasks
    public function createOrder( $order ) 
    {
        // Logic to process a payment.
    }
}

And in your controller:

namespace App\Http\Controllers;

use App\Models\Order;
use App\Services\OrderService;

class OrderController extends Controller {

    protected $orderService;

    public function __construct(OrderService $orderService)
    {
        $this->orderService = $orderService;
    }

    public function store(Request $request) 
    {
        // Call methods from OrderService
        $this->orderService->createOrder( $request );
    }
}

How to Create a Service Class in Laravel?

In Laravel, creating a Service Class is simple, even though Laravel doesn’t provide a specific artisan command for it.
Here’s how you can do it:

  1. Set Up the Directory: Start by creating a directory to store your service classes. This directory is usually named “Services” and is in the app folder.

  2. Create the Service Class: Inside the Services directory, manually create your PHP class for the service. You can name it based on the task it will perform, such as UserService or PaymentService.

Using Service Classes in Controllers

In Laravel, there are three methods to call service classes from your controllers.

  1. Direct Instantiation : Imagine we have an OrderService class with a createOrder method designed to handle order creation.
namespace App\Http\Controllers;

use App\Services\OrderService;

class OrderController extends Controller {
    public function store(Request $request) 
    {
        $orderService = new OrderService();
        $orderService->createOrder($request);
        // More methods from the service can be called like this:
        // $orderService->anotherMethod();
    }
}

  1. Dependency Injection : Laravel provides dependency injection, allowing automatic generation of objects of our service classes. By adding OrderService as a parameter to the controller’s method, Laravel will automatically resolve and provide an instance:
namespace App\Http\Controllers;

use App\Services\OrderService;

class OrderController extends Controller {
    public function store(Request $request, OrderService $orderService) 
    {
        $orderService->createOrder($request);
    }
}

Using dependency injection keeps our method tidy since we don’t need to initialize the class within the function. This enhances readability and maintains a cleaner code structure in our controllers.

  1. Constructor Injection:
    Another method to use service classes in controllers is through constructor injection. This involves defining the service as a protected property in the controller’s constructor:
namespace App\Http\Controllers;

use App\Services\OrderService;

class OrderController extends Controller {

    protected $orderService;

    public function __construct(OrderService $orderService) 
    {
        $this->orderService = $orderService;
    }

    public function store(Request $request) 
    {
        // Call methods from OrderService
        $this->orderService->createOrder($request);
    }
}

With constructor injection, Laravel automatically injects the OrderService instance into the controller when it’s created, allowing us to access its methods throughout the controller’s methods.

Avoid Using Global Values in Service Classes

When developing Service Classes in Laravel, it’s crucial to ensure they act as “black boxes,” maintaining a clear separation of concerns. Here’s how they should ideally function:

  1. Controller Interaction: The controller provides specific inputs to the service.
  2. Processing: The service processes these inputs and performs the necessary actions.
  3. Response Handling: The service returns the result to the controller.

This implies that Service Classes should not depend on or manipulate global values such as Auth, Session, or Request URL. They should also refrain from sending responses directly to the browser.

Consider the following example:

namespace App\Services;

class OrderService
{
    public function createOrder($orderData)
    {
        // Process order creation
        throw_if(
            $orderData['user_id'] != auth()->id(),
            new \Exception('You are not allowed to create an order')
        );
    }
}

OR you can do it with regular if condition

namespace App\Services;

class OrderService
{
    public function createOrder($orderData)
    {
        // Process order creation
        if ($orderData['user_id'] != auth()->id()) {
            throw new \Exception('You are not allowed to create an order');
        }
    }
}

In this code, the service throws an exception using throw_if() based on the user’s authorization status.

Now You can catch an exception if it occurs in OrderService and return a 500 error response.

namespace App\Http\Controllers;

use App\Services\OrderService;

class OrderController extends Controller
{
    protected $orderService;

    public function __construct(OrderService $orderService)
    {
        $this->orderService = $orderService;
    }

    public function store(Request $request)
    {
        try {
            $this->orderService->createOrder($request->all());
            // Do other stuff
        } catch (\Exception $e) {
            abort(500, $e->getMessage());
        }
    }
}