Installation:
composer require tuupola/callable-handler
Add to composer.json if not using autoload:
"autoload": {
"psr-4": {
"App\\": "app/"
}
}
First Use Case:
Convert a PSR-7 middleware (e.g., Middleware\ExampleMiddleware) to work with PSR-15 by wrapping it:
use Tuupola\CallableHandler\CallableHandler;
$handler = new CallableHandler();
$middleware = $handler->wrap($this->middleware); // $this->middleware is PSR-7
Quick Integration with Laravel:
In app/Http/Kernel.php, replace PSR-7 middleware with PSR-15 equivalents:
protected $middleware = [
\Tuupola\CallableHandler\CallableHandler::class . ':wrap',
\App\Http\Middleware\ExampleMiddleware::class,
];
PSR-7 to PSR-15 Conversion:
Use CallableHandler to wrap legacy PSR-7 middleware for PSR-15 compatibility:
$handler = new CallableHandler();
$psr15Middleware = $handler->wrap($psr7Middleware);
Double-Pass Middleware:
Implement __invoke() with the DoublePassTrait for middleware requiring two passes (e.g., auth + CORS):
use Tuupola\CallableHandler\DoublePassTrait;
class AuthMiddleware
{
use DoublePassTrait;
public function __invoke($request, $handler)
{
// First pass logic
return $handler($request)->then(...);
}
}
Laravel Middleware Integration:
Extend Laravel’s middleware stack by wrapping PSR-7 middleware in Handle:
namespace App\Http\Middleware;
use Tuupola\CallableHandler\CallableHandler;
class ConvertMiddleware
{
public function __construct(CallableHandler $handler)
{
$this->handler = $handler;
}
public function handle($request, Closure $next)
{
$psr15Middleware = $this->handler->wrap($request->middleware());
return $psr15Middleware($request, $next);
}
}
Grouped Middleware Handling:
Process groups of middleware (e.g., middlewareGroups in Laravel) by wrapping each:
$groupedMiddleware = collect($middlewareGroups['web'])
->map(fn($middleware) => $handler->wrap($middleware))
->all();
PSR-15 Router Integration:
Use with frameworks like slim/slim or zend-expressive by registering wrapped middleware:
$app->pipe($handler->wrap($psr7Middleware));
Dependency Injection:
Bind CallableHandler in Laravel’s service container:
$app->singleton(CallableHandler::class, fn() => new CallableHandler());
Testing:
Mock CallableHandler in tests to verify middleware behavior:
$handler = $this->createMock(CallableHandler::class);
$handler->method('wrap')->willReturn($psr15Middleware);
Double-Pass Misuse:
Avoid using DoublePassTrait for single-pass middleware—it adds unnecessary complexity and may break PSR-15 compliance.
Fix: Use CallableHandler::wrap() for single-pass conversions.
PHP Version Conflicts:
Ensure PHP ≥7.1 (min) or ≥8.0 (for newer features). Older versions may fail silently.
Fix: Update composer.json constraints or use ^1.1.0 for PHP 8 support.
Middleware Ordering:
Wrapping middleware changes execution order. Test thoroughly after conversion.
Fix: Log middleware stack or use tap() to debug:
$middleware = $handler->wrap($middleware)->tap(fn($m) => logger()->debug($m));
PSR-7 vs. PSR-15 Signature:
PSR-7 middleware expects $request, $response, $next, while PSR-15 uses $request, $handler.
Fix: Use CallableHandler to auto-adapt signatures.
Enable Error Logging:
Configure CallableHandler to log wrapped calls:
$handler = new CallableHandler();
$handler->setLogger(new \Monolog\Logger('middleware'));
Check Return Types:
Ensure middleware returns ResponseInterface (PSR-7) or PromiseInterface (PSR-15). Use:
if (!$response instanceof \Psr\Http\Message\ResponseInterface) {
throw new \RuntimeException('Invalid response type');
}
Custom Wrappers:
Extend CallableHandler to support additional middleware types:
class CustomHandler extends CallableHandler
{
public function wrapCustom($middleware)
{
return fn($request, $handler) => $middleware($request, $handler, 'custom');
}
}
Middleware Metadata: Add metadata (e.g., tags) to wrapped middleware:
$wrapped = $handler->wrap($middleware);
$wrapped->setMetadata(['tag' => 'auth']);
Performance Optimization: Cache wrapped middleware instances for repeated use:
static $cache = [];
$wrapped = $cache[$middleware::class] ??= $handler->wrap($middleware);
Laravel Caching:
If using Laravel’s middleware caching (php artisan cache:clear), clear the CallableHandler cache manually:
$handler->clearCache();
PSR-15 Middleware Groups:
Laravel’s middlewareGroups may not auto-wrap PSR-7 middleware. Manually wrap in Kernel.php:
$middleware = collect($this->middlewareGroups['web'])
->map(fn($m) => $handler->wrap($m))
->all();
How can I help you explore Laravel packages today?