Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.00% covered (success)
90.00%
9 / 10
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
DeferMiddleware
90.00% covered (success)
90.00%
9 / 10
75.00% covered (warning)
75.00%
3 / 4
5.03
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAttribute
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDefer
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 process
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5/**
6 * This file is part of fast-forward/defer.
7 *
8 * This source file is subject to the license bundled
9 * with this source code in the file LICENSE.
10 *
11 * @copyright Copyright (c) 2026 Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
12 * @license   https://opensource.org/licenses/MIT MIT License
13 *
14 * @see       https://github.com/php-fast-forward/defer
15 * @see       https://github.com/php-fast-forward
16 * @see       https://datatracker.ietf.org/doc/html/rfc2119
17 */
18
19namespace FastForward\Defer\Middleware;
20
21use LogicException;
22use FastForward\Defer\Defer;
23use FastForward\Defer\DeferInterface;
24use Psr\Http\Message\ResponseInterface;
25use Psr\Http\Message\ServerRequestInterface;
26use Psr\Http\Server\MiddlewareInterface;
27use Psr\Http\Server\RequestHandlerInterface;
28
29/**
30 * This middleware MUST be used to inject and manage a Defer instance in a PSR-15 ServerRequest.
31 * It SHALL ensure that all deferred callbacks are executed at the end of the request lifecycle.
32 * The attribute name MAY be customized via the constructor.
33 */
34final readonly class DeferMiddleware implements MiddlewareInterface
35{
36    /**
37     * Constructs a new DeferMiddleware instance.
38     *
39     * @param string $attribute the attribute name to use for storing the Defer instance (optional)
40     */
41    public function __construct(
42        private string $attribute = DeferInterface::class,
43    ) {}
44
45    /**
46     * Returns the attribute name used to store the Defer instance.
47     *
48     * @return string the attribute name
49     */
50    public function getAttribute(): string
51    {
52        return $this->attribute;
53    }
54
55    /**
56     * Retrieves the Defer instance from the request.
57     *
58     * This method MUST throw a LogicException if the Defer instance is not found.
59     *
60     * @param ServerRequestInterface $request the request to retrieve the Defer instance from
61     *
62     * @return DeferInterface the Defer instance
63     *
64     * @throws LogicException
65     */
66    public function getDefer(ServerRequestInterface $request): DeferInterface
67    {
68        $defer = $request->getAttribute($this->attribute);
69
70        if (! $defer instanceof DeferInterface) {
71            throw new LogicException(\sprintf('Defer instance not found in request attribute "%s".', $this->attribute));
72        }
73
74        return $defer;
75    }
76
77    /**
78     * Processes the request, injecting the Defer instance and ensuring execution at the end.
79     *
80     * @param ServerRequestInterface $request the incoming request
81     * @param RequestHandlerInterface $handler the request handler
82     *
83     * @return ResponseInterface the response from the handler
84     */
85    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
86    {
87        $defer = new Defer();
88
89        $request = $request->withAttribute($this->attribute, $defer);
90
91        try {
92            return $handler->handle($request);
93        } finally {
94            unset($defer);
95        }
96    }
97}