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]);
}
}
Problems:
- Code duplication: The service initialization is duplicated in each method.
- Clutter: The response formatting is also duplicated, making long-term maintenance difficult.
- 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]);
});
}
}
Benefits of Using Hooks
- Cleaner and more concise code: Each method focuses only on business logic. Repetitive actions are handled elsewhere.
- Centralized initialization: No need to duplicate service initialization in every method.
- Consistent response formatting: Formatting is applied uniformly without duplication.
- 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);
});
}
}
What Hooks Bring to the Table:
- Simplified service initialization: No need to write initialization code in every method.
- Automatic logging: Each payment operation is logged without extra effort.
- Centralized security checks: Security is handled centrally before each payment-related method.
- Simplified error handling: A clear and centralized approach to handling specific exceptions.
Source
Top comments (0)