DEV Community

Claude Fassinou
Claude Fassinou

Posted on

After and Before Hooks with Laravel Controller

After and Before Hooks with Laravel Controller

Have you ever encountered a situation where you want to perform an action before or after certain controller methods, perhaps to avoid code duplication, writing an event listener, or creating validation files? You want to keep things simple but move faster. Here's how you can solve this problem. Unfortunately, without an optimized approach, these repetitive tasks can lead to code duplication, making your application harder to maintain.

Do you want to avoid duplicating code or repeatedly writing listeners? Are you looking for a simple and fast solution?

In this article, we will solve this issue using a powerful and flexible approach: controller hooks in Laravel. With this technique, you can automate cross-cutting actions without touching the code of your controller methods. It’s an elegant solution to improve the clarity and maintainability of your code.

The Problem: Code Duplication and Lack of Flexibility

Let’s take a classic example. Suppose you have a controller with several methods that require similar operations, like initializing a service or formatting responses. The following code illustrates this repetition:

class ExampleController extends Controller
{
    private $someService;

    public function __construct(SomeService $someService)
    {
        $this->someService = $someService;
    }

    public function methodA(Request $request)
    {
        $this->someService->init($request->some_id)->withModel();
        // Specific logic for methodA
        $result = // ...
        return $this->formatResponse($result);
    }

    public function methodB(Request $request)
    {
        $this->someService->init($request->some_id)->withModel();
        // Specific logic for methodB
        $result = // ...
        return $this->formatResponse($result);
    }

    private function formatResponse($data)
    {
        return response()->json(['success' => true, 'data' => $data]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Problems:

  1. Code duplication: The service initialization is duplicated in each method.
  2. Clutter: The response formatting is also duplicated, making long-term maintenance difficult.
  3. Lack of flexibility: Adding new cross-cutting features, such as logging or error handling, requires modifying each method.

The Solution: Using Hooks in Controllers

With controller hooks, you can centralize these operations and apply them automatically before or after the execution of the relevant methods. Here’s how to refactor the previous example using this approach.

Refactoring with Hooks

use LaravelHooks\Traits\HasControllerHooks;

class ExampleController extends Controller
{
    use HasControllerHooks;

    private $someService;

    public function __construct(SomeService $someService)
    {
        $this->someService = $someService;
    }

    public function methodA(Request $request)
    {
        // Specific logic for methodA
        return // ...
    }

    public function methodB(Request $request)
    {
        // Specific logic for methodB
        return // ...
    }

    public function useHooks()
    {
        $this->beforeCalling(['methodA', 'methodB'], function ($request, ...$params) {
            $this->someService->init($request->some_id)->withModel();
        });

        $this->afterCalling(['methodA', 'methodB'], function ($result) {
            return response()->json(['success' => true, 'data' => $result]);
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Benefits of Using Hooks

  1. Cleaner and more concise code: Each method focuses only on business logic. Repetitive actions are handled elsewhere.
  2. Centralized initialization: No need to duplicate service initialization in every method.
  3. Consistent response formatting: Formatting is applied uniformly without duplication.
  4. Easier to add new features: Hooks allow you to quickly add new functionalities like logging or error handling without modifying existing methods.

Practical Use Case: Payment Management

Let’s take a concrete use case to illustrate this approach. Imagine a payment management system where you need to check security, initiate services, and format responses. Here’s how to use hooks to make the code cleaner and more maintainable:

use LaravelHooks\Traits\HasControllerHooks;

class PaymentController extends Controller
{
    use HasControllerHooks;

    private $paymentService;

    public function __construct(PaymentService $paymentService)
    {
        $this->paymentService = $paymentService;
    }

    public function store(Request $request)
    {
        return $this->paymentService->createPayment($request->all());
    }

    public function verify(Request $request, $paymentId)
    {
        return $this->paymentService->verifyPayment($paymentId);
    }

    public function checkStatus(Request $request, $paymentId)
    {
        return $this->paymentService->checkPaymentStatus($paymentId);
    }

    public function useHooks()
    {
        $this->beforeCalling(['store', 'verify', 'checkStatus'], function ($request, ...$params) {
            $this->paymentService->init($request->payment_id);
            Log::info("Payment operation initiated", ['method' => $this->currentMethod, 'user_id' => auth()->id()]);

            // Security check
            if (!$this->securityService->verifyUserAllowed(auth()->user(), $request->payment_id)) {
                throw new UnauthorizedPaymentException("User not authorized for this payment operation");
            }
        });

        $this->afterCalling(['store', 'verify', 'checkStatus'], function ($result) {
            return response()->json(['success' => true, 'data' => $result]);
        });

        $this->addErrorHandler(PaymentException::class, function ($e) {
            return response()->json(['success' => false, 'error' => $e->getMessage()], 400);
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

What Hooks Bring to the Table:

  1. Simplified service initialization: No need to write initialization code in every method.
  2. Automatic logging: Each payment operation is logged without extra effort.
  3. Centralized security checks: Security is handled centrally before each payment-related method.
  4. Simplified error handling: A clear and centralized approach to handling specific exceptions.

Source

laravel-hooks

Top comments (0)