Refactoring Legacy Middleware: From Facades to Dependency Injection

📖 2 minutes read

Legacy Laravel code often relies heavily on facades. While facades are convenient, they can make code harder to test and reason about. Here’s a pattern for refactoring middleware to use dependency injection instead.

The Legacy Pattern (Bad)

<?php

namespace App\Http\Middleware;

use Closure;
use App\Models\ActivityLog;

class TrackActivityStats
{
    public function handle($request, Closure $next)
    {
        try {
            $route = \Route::current();
            ActivityLog::create([
                'method' => $request->method(),
                'uri' => $route->uri(),
                'route_name' => $route->getName(),
                'user_id' => \Auth::id()
            ]);
        } catch (\Throwable $e) {}
        return $next($request);
    }
}

The Modern Pattern (Good)

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Psr\Log\LogLevel;
use App\Models\ActivityLog;

class TrackActivityStats
{
    private $logger;

    public function __construct(ActivityLog $logger)
    {
        $this->logger = $logger;
    }

    public function handle(Request $request, Closure $next): Response
    {
        $this->logRequest($request);
        return $next($request);
    }

    private function logRequest(Request $request)
    {
        try {
            $this->logger->log(LogLevel::INFO, 'Activity tracked', [
                'method' => $request->method(),
                'uri' => $request->route()->uri(),
                'route_name' => $request->route()->getName(),
                'user_id' => $request->user()->id ?? null
            ]);
        } catch (\Throwable $e) {
            // Silent failure for non-critical tracking
        }
    }
}

Why This Is Better

  • Testable: Easy to mock the logger dependency in tests
  • Type-safe: Return type hints catch errors early
  • Explicit dependencies: Constructor shows what the middleware needs
  • No global state: Uses request object instead of facades
  • Separation of concerns: Logic extracted into private method
  • Better IDE support: Type hints enable autocomplete

Key Changes

  • \Route::current()$request->route()
  • \Auth::id()$request->user()->id
  • Direct model call → Injected logger dependency
  • No type hints → Full type declarations

Next time you touch old middleware, consider this refactoring pattern. Your future self (and your test suite) will thank you.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *