Revision: Wed, 27 Nov 2024 11:25:04 GMT
v2.14 – outdated
This version of the documentation is outdated. Consider upgrading your project to Spiral Framework 3.14
Edit this page

HTTP - Middleware

The framework allows you to set PSR-15 compatible HTTP middleware globally or to a specific route.

Note
Check https://github.com/middlewares/psr15-middlewares to find many publicly maintained middleware.

Create Middleware

Implement Psr\Http\Server\MiddlewareInterface to create your middleware:

php
class MyMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request, 
        RequestHandlerInterface $handler
    ): ResponseInterface
    {
        return $handler->handle($request)->withAddedHeader('My-Header', 'my-value');
    }
}

Global Middleware

To activate middleware for every user request, use Spiral\Bootloader\Http\HttpBootloader. You can only set this value in application bootloaders.

php
use Spiral\Bootloader\Http\HttpBootloader;

class MyMiddlewareBootloader extends Bootloader
{
    public function boot(HttpBootloader $http)
    {
        // automatically resolved by Container
        $http->addMiddleware(MyMiddleware::class);
    }
}

Middleware object will be instantiated on demand.

Note
Make sure to add this Bootloader before RoutesBootloader in your app.

Route Specific Middleware

To add middleware to the route object use withMiddleware method, make sure to use newly created route instance (example is given for Bootloader):

php
use App\Controller\HomeController;
use App\MyMiddleware;
use Spiral\Router\Route;
use Spiral\Router\RouterInterface;
use Spiral\Router\Target\Action;

// ...

public function boot(RouterInterface $router): void
{
    $route = new Route('/index', new Action(HomeController::class, 'index'));
    $route = $route->withMiddleware(MyMiddleware::class);

    $router->addRoute('index', $route);
}

Combine with IoC scopes

You can combine middleware with the IoC scope to create a request-specific application context.

php
class UserContext
{
    public int $id;
    public string $name;

    public function __construct(int $id, string $name)
    {
        $this->id = $id;
        $this->name = $name;
    }
}

Use Spiral\Core\ScopeInterface to set application scope in your middleware:

php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Spiral\Core\ScopeInterface;

class MyMiddleware implements MiddlewareInterface
{
    private ScopeInterface $scope;

    public function __construct(ScopeInterface $scope)
    {
        $this->scope = $scope;
    }

    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        return $this->scope->runScope([
            UserContext::class => new UserContext(123, 'test')
        ], function () use ($handler, $request) {
            return $handler->handle($request);
        });
    }
}

You can request this context from the container or via method injection of your controller:

php
public function index(UserContext $ctx): void
{
    dump($ctx);
}

Non-Direct Scope Configuration

You can use already exists requests scope to carry user values. Create bootloader providing access method for the context specific value:

php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class MyMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        return $handler->handle($request->withAttribute('userContext', new UserContext(123, 'test')));
    }
}

To gain access to this value from container:

php
use Psr\Http\Message\ServerRequestInterface;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Core\Exception\ScopeException;

class UserContextBootloader extends Bootloader 
{
    protected const BINDINGS = [
        UserContext::class => [self::class, 'userContext']
    ];
    
    private function userContext(ServerRequestInterface $request): UserContext
    {
        $userContext = $request->getAttribute('userContext', null);
        if ($userContext === null) {
            throw new ScopeException('Unable to resolve UserContext, invalid request scope');
        }
        
        return $userContext;
    }
}