Skip to content

Commit 2b5721f

Browse files
authored
Update Next.php
1 parent 509531d commit 2b5721f

File tree

1 file changed

+186
-9
lines changed

1 file changed

+186
-9
lines changed

src/Next.php

Lines changed: 186 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,213 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
namespace Bermuda\Http\Middleware;
46

5-
use \SplQueue;
7+
use SplQueue;
68
use Psr\Http\Message\ResponseInterface;
79
use Psr\Http\Server\MiddlewareInterface;
810
use Psr\Http\Message\ServerRequestInterface;
911
use Psr\Http\Server\RequestHandlerInterface;
1012

1113
/**
12-
* Handler class that processes the next middleware in the queue.
14+
* Class Next
15+
*
16+
* Internal handler that manages sequential middleware execution through a queue.
17+
*
18+
* This class is the core mechanism that powers the Pipeline's middleware processing.
19+
* It implements a queue-based approach where each middleware is dequeued and processed
20+
* in order. When the queue is exhausted, control is passed to the final handler.
21+
*
22+
* Key design decisions:
23+
* - **Queue cloning**: Prevents side effects by working on a copy of the original queue
24+
* - **Stateless processing**: Each Next instance is immutable after construction
25+
* - **Recursive delegation**: Each middleware receives a fresh Next instance as its handler
26+
* - **Thread-safe design**: No shared mutable state between invocations
27+
*
28+
* The queue-based approach eliminates the need for position tracking or cursor
29+
* management, making the implementation simpler and more reliable.
30+
*
31+
* @internal This class is for internal use by the Pipeline implementation only.
32+
* It should not be used directly by application code.
33+
*
34+
* @example How Next processes middleware (conceptual)
35+
* ```php
36+
* // Initial state: queue = [M1, M2, M3], finalHandler = H
37+
* $next = new Next($queue, $finalHandler);
38+
*
39+
* // First call: next->handle()
40+
* // - Dequeues M1
41+
* // - Calls M1->process($request, $next)
42+
* // - Inside M1: calls $handler->handle() which is $next->handle()
43+
*
44+
* // Second call: next->handle()
45+
* // - Dequeues M2
46+
* // - Calls M2->process($request, $next)
47+
* // - Inside M2: calls $handler->handle() which is $next->handle()
48+
*
49+
* // Third call: next->handle()
50+
* // - Dequeues M3
51+
* // - Calls M3->process($request, $next)
52+
* // - Inside M3: calls $handler->handle() which is $next->handle()
1353
*
14-
* This class manages the sequential processing of middleware using a queue,
15-
* eliminating the need for position tracking.
54+
* // Fourth call: next->handle()
55+
* // - Queue is empty
56+
* // - Calls $finalHandler->handle($request)
57+
* // - Returns response
58+
* ```
1659
*
17-
* @internal This class is for internal use only.
60+
* @example Middleware short-circuit behavior
61+
* ```php
62+
* class AuthMiddleware implements MiddlewareInterface
63+
* {
64+
* public function process(
65+
* ServerRequestInterface $request,
66+
* RequestHandlerInterface $handler
67+
* ): ResponseInterface {
68+
* if (!$this->isAuthenticated($request)) {
69+
* // Return immediately without calling $handler->handle()
70+
* // Remaining middleware in the queue are never executed
71+
* return new Response(401, [], 'Unauthorized');
72+
* }
73+
*
74+
* // Continue to next middleware
75+
* return $handler->handle($request);
76+
* }
77+
* }
78+
* ```
79+
*
80+
* @example Why queue cloning is critical
81+
* ```php
82+
* // Without cloning (problematic):
83+
* $queue = new SplQueue();
84+
* $queue->enqueue($m1);
85+
* $queue->enqueue($m2);
86+
*
87+
* $next1 = new Next($queue, $handler); // If not cloned
88+
* $next1->handle($request); // Modifies the original $queue
89+
*
90+
* $next2 = new Next($queue, $handler); // Queue is now empty!
91+
* $next2->handle($request); // Would skip all middleware
92+
*
93+
* // With cloning (correct):
94+
* $next1 = new Next($queue, $handler); // Clones queue internally
95+
* $next1->handle($request); // Works on copy
96+
*
97+
* $next2 = new Next($queue, $handler); // Gets fresh copy
98+
* $next2->handle($request); // Works correctly
99+
* ```
18100
*/
19101
final class Next implements RequestHandlerInterface
20102
{
103+
/**
104+
* Internal queue of middleware to be processed.
105+
*
106+
* This is a cloned copy of the original queue passed to the constructor,
107+
* ensuring that processing this Next instance does not affect the original
108+
* queue or other Next instances.
109+
*
110+
* @var SplQueue<MiddlewareInterface>
111+
*/
21112
private SplQueue $queue;
22-
113+
23114
/**
24-
* @param \SplQueue<MiddlewareInterface> $queue Queue of middleware
25-
* @param RequestHandlerInterface $finalHandler Handler to use when queue is empty
115+
* Constructs a new Next handler instance.
116+
*
117+
* The constructor clones the provided queue to ensure isolation. This prevents
118+
* the sequential dequeue operations during request processing from affecting
119+
* the original queue or other Next instances created from the same queue.
120+
*
121+
* This design allows the Pipeline to safely create multiple Next instances
122+
* from the same middleware queue without side effects.
123+
*
124+
* @param SplQueue<MiddlewareInterface> $queue The queue of middleware to process.
125+
* This queue is cloned internally to
126+
* prevent external modifications.
127+
* @param RequestHandlerInterface $finalHandler The handler to invoke when all
128+
* middleware have been processed.
129+
* This is typically the application
130+
* handler or Pipeline's fallback handler.
131+
*
132+
* @example Construction in Pipeline context
133+
* ```php
134+
* // Inside Pipeline::handle()
135+
* $next = new Next($this->middlewares, $this->fallbackHandler);
136+
* return $next->handle($request);
137+
* ```
26138
*/
27139
public function __construct(
28140
SplQueue $queue,
29141
private readonly RequestHandlerInterface $finalHandler
30142
) {
31-
$this->queue = clone $queue;
143+
$this->queue = clone $queue;
32144
}
33145

146+
/**
147+
* Handles a server request by processing the next middleware in the queue.
148+
*
149+
* This method implements the core middleware execution logic:
150+
*
151+
* 1. If the queue is empty, delegate to the final handler (base case)
152+
* 2. Otherwise, dequeue the next middleware
153+
* 3. Pass the request to the middleware along with $this as the next handler
154+
* 4. The middleware can then choose to:
155+
* - Call $handler->handle($request) to continue the chain
156+
* - Return a response immediately to short-circuit the chain
157+
*
158+
* This recursive delegation ensures that each middleware has full control
159+
* over whether to continue the chain or terminate early.
160+
*
161+
* @param ServerRequestInterface $request The server request to process through
162+
* the middleware chain.
163+
*
164+
* @return ResponseInterface The HTTP response, either from a middleware that
165+
* short-circuited the chain or from the final handler
166+
* after all middleware have been processed.
167+
*
168+
* @example Normal execution flow
169+
* ```php
170+
* $m1 = new LoggingMiddleware();
171+
* $m2 = new ValidationMiddleware();
172+
* $m3 = new RouteMiddleware();
173+
*
174+
* $queue = new SplQueue();
175+
* $queue->enqueue($m1);
176+
* $queue->enqueue($m2);
177+
* $queue->enqueue($m3);
178+
*
179+
* $next = new Next($queue, $finalHandler);
180+
*
181+
* // Call stack:
182+
* // 1. next->handle() -> dequeues m1
183+
* // 2. m1->process(req, next) -> logs, calls handler->handle()
184+
* // 3. next->handle() -> dequeues m2
185+
* // 4. m2->process(req, next) -> validates, calls handler->handle()
186+
* // 5. next->handle() -> dequeues m3
187+
* // 6. m3->process(req, next) -> routes, calls handler->handle()
188+
* // 7. next->handle() -> queue empty
189+
* // 8. finalHandler->handle() -> returns response
190+
* ```
191+
*
192+
* @example Short-circuit execution
193+
* ```php
194+
* class CacheMiddleware implements MiddlewareInterface
195+
* {
196+
* public function process($request, $handler): ResponseInterface
197+
* {
198+
* if ($cached = $this->cache->get($request)) {
199+
* return $cached; // Short-circuit - no further processing
200+
* }
201+
*
202+
* $response = $handler->handle($request);
203+
* $this->cache->set($request, $response);
204+
* return $response;
205+
* }
206+
* }
207+
*
208+
* // If cache hits, remaining middleware and final handler are skipped
209+
* ```
210+
*/
34211
public function handle(ServerRequestInterface $request): ResponseInterface
35212
{
36213
if ($this->queue->isEmpty()) {

0 commit comments

Comments
 (0)