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 *