Mastering Laravel Context: Middleware Implementation and Testing Guide

Laravel is a powerful PHP framework that allows developers to build robust web applications with ease. One of its strengths is its flexibility and the ability to manage complex data flows using various tools and techniques. One such tool is the "Context" class, which helps manage data that needs to be accessed across different parts of your application. In this article, we'll dive into how to use Laravel context effectively, focusing on an example where user and company information is stored and retrieved from the context.

In the context of Laravel, the term "context" refers to the ability to store and retrieve data that is specific to a request or a process. This is particularly useful when you have data that needs to be shared across different services or components within your application. By using context, you can avoid passing data through multiple layers explicitly, making your code cleaner and easier to maintain.

Let's walk through an example where we store a user's ID and their associated company ID in the context. This can be useful in scenarios where you need to access these values in various parts of your application, such as when managing tasks that are associated with a specific user and company.

Setting Context using Middleware

The first step is to create a middleware that will add the user ID and company ID to the context whenever a request is made. Middleware in Laravel acts as a bridge between a request and a response, allowing you to perform various actions before the request is handled by the application.

Creating the Middleware

First, create a middleware by running the following Artisan command:

php artisan make:middleware AddUserCompanyToContext

This command generates a middleware file. Next, update the handle method in the middleware to add the user_id and company_id to the context:

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use App\Services\Context;
class AddUserCompanyToContext
{
    public function handle(Request $request, Closure $next): Response
    {
        if (\request()->user()) {
            Context::add('user_id', \request()->user()->id);
            Context::add('company_id', \request()->user()->company_id);
        }
        return $next($request);
    }
}

In this middleware, we're checking if the request has an authenticated user. If it does, we add the user's ID and company ID to the context using the Context::add method. This allows these values to be accessed later in the application without needing to pass them explicitly.

Implementing the Task Management Service

Now that we have the middleware in place to store the user_id and company_id in the context, let's implement a TasksService that will use these context values to store tasks in the database.

Here's how you can implement the storeTask method in the TasksService:

<?php
namespace App\Services;
use App\Models\Task;
use App\Services\Context;
class TasksService
{
    public function storeTask(string $name, string $description): Task
    {
        $userId = Context::get('user_id');
        $companyId = Context::get('company_id');
        $task = new Task();
        $task->name = $name;
        $task->description = $description;
        $task->company_id = $companyId;
        $task->created_by = $userId;
        $task->save();
        return $task;
    }
}

In this example, the storeTask method retrieves the user_id and company_id from the context and uses them to create a new task. The task is then saved to the database, with the company_id and created_by fields populated based on the context values.

Handling Context in Tests

When writing tests for your application, you need to ensure that the context variables are properly set up before executing your test cases. Here's an example of how you can set the context values in a test:

<?php
namespace Tests\Unit;
use Tests\TestCase;
use App\Services\Context;
use App\Services\TasksService;
use App\Models\User;
class TasksServiceTest extends TestCase
{
    public function testStoreTask()
    {
        $user = User::factory()->create();
        
        Context::add('user_id', $user->id);
        Context::add('company_id', $user->company_id);
        $tasksService = new TasksService();
        $task = $tasksService->storeTask('Test Task', 'This is a test task.');
        $this->assertEquals('Test Task', $task->name);
        $this->assertEquals($user->company_id, $task->company_id);
        $this->assertEquals($user->id, $task->created_by);
    }
}

In this test, we create a user and manually add the user_id and company_id to the context before calling the storeTask method. This ensures that the method has access to the necessary context data, just like it would in a real request.

Conclusion

Laravel context is a powerful tool that can help streamline data management within your application. By using middleware to store values like user IDs and company IDs in the context, you can simplify your code and make it easier to maintain. The example provided demonstrates how to implement this approach effectively, ensuring that key data is always accessible wherever it's needed in your application. When writing tests, don’t forget to set up the context variables to ensure your test cases run correctly.