1+
12<?php
23
34namespace Bermuda \Http \Middleware ;
1213 * Class Pipeline
1314 *
1415 * Stateless middleware pipeline that processes HTTP requests through a chain of PSR-15 middleware.
15- * This implementation uses a recursive approach without mutable state, making it thread-safe
16- * and safe for concurrent request processing.
16+ * Uses SplQueue for efficient sequential processing without position tracking.
1717 *
1818 * Key features:
1919 * - Thread-safe: No mutable position state
3131 *
3232 * $response = $pipeline->handle($request);
3333 * ```
34- *
35- * @example Pipeline composition
36- * ```php
37- * $apiPipeline = new Pipeline([
38- * new RateLimitMiddleware(),
39- * new JsonMiddleware(),
40- * ]);
41- *
42- * $mainPipeline = new Pipeline([
43- * new LoggingMiddleware(),
44- * $apiPipeline, // Pipeline as middleware
45- * ], $handler);
46- * ```
4734 */
4835final class Pipeline implements PipelineInterface
4936{
5037 /**
51- * An array of middleware objects that implement MiddlewareInterface.
38+ * Queue of middleware objects that implement MiddlewareInterface.
5239 *
53- * @var MiddlewareInterface[]
40+ * @var \SplQueue< MiddlewareInterface>
5441 */
55- private array $ middlewares = [] ;
42+ private \ SplQueue $ middlewares ;
5643
5744 /**
5845 * Constructor for the Pipeline class.
5946 *
60- * Initializes the Pipeline with an optional iterable of middleware and a fallback handler.
61- * Each middleware in the provided iterable is verified to implement MiddlewareInterface.
62- * If any middleware fails this verification, an InvalidArgumentException is thrown.
63- *
6447 * @param iterable<MiddlewareInterface> $middlewares An iterable collection of middleware objects.
6548 * @param RequestHandlerInterface $fallbackHandler The fallback handler to use when the middleware chain is exhausted.
66- * Defaults to an instance of EmptyPipelineHandler.
6749 *
6850 * @throws \InvalidArgumentException if any middleware does not implement MiddlewareInterface.
69- *
70- * @example
71- * ```php
72- * $pipeline = new Pipeline([
73- * new AuthMiddleware(),
74- * new ValidationMiddleware(),
75- * ], new AppHandler());
76- * ```
7751 */
7852 public function __construct (
7953 iterable $ middlewares = [],
8054 private(set) RequestHandlerInterface $ fallbackHandler = new EmptyPipelineHandler
8155 ) {
56+ $ this ->middlewares = new \SplQueue ();
57+
8258 foreach ($ middlewares as $ i => $ middleware ) {
8359 $ this ->validateMiddleware ($ middleware , $ i );
84- $ this ->middlewares [] = $ middleware ;
60+ $ this ->middlewares -> enqueue ( $ middleware) ;
8561 }
8662 }
8763
@@ -91,21 +67,6 @@ public function __construct(
9167 *
9268 * @param MiddlewareInterface|class-string<T> $middleware The middleware instance or class to search for.
9369 * @return bool Returns true if the middleware exists; false otherwise.
94- *
95- * @example Check by class name
96- * ```php
97- * if ($pipeline->has(AuthMiddleware::class)) {
98- * // Pipeline includes authentication
99- * }
100- * ```
101- *
102- * @example Check by instance
103- * ```php
104- * $auth = new AuthMiddleware();
105- * if ($pipeline->has($auth)) {
106- * // This specific instance is in the pipeline
107- * }
108- * ```
10970 */
11071 public function has (MiddlewareInterface |string $ middleware ): bool
11172 {
@@ -128,95 +89,56 @@ public function has(MiddlewareInterface|string $middleware): bool
12889 * Checks if the pipeline is empty.
12990 *
13091 * @return bool True if there are no middleware registered; false otherwise.
131- *
132- * @example
133- * ```php
134- * if ($pipeline->isEmpty()) {
135- * // Request will go directly to fallback handler
136- * }
137- * ```
13892 */
13993 public function isEmpty (): bool
14094 {
141- return empty ( $ this ->middlewares );
95+ return $ this ->middlewares -> isEmpty ( );
14296 }
14397
14498 /**
14599 * Returns the number of middleware objects in the pipeline.
146100 *
147- * Implements the Countable interface.
148- *
149101 * @return int The count of middleware components.
150- *
151- * @example
152- * ```php
153- * echo "Pipeline has " . count($pipeline) . " middleware";
154- * ```
155102 */
156103 public function count (): int
157104 {
158- return count ( $ this ->middlewares );
105+ return $ this ->middlewares -> count ( );
159106 }
160107
161108 /**
162109 * Retrieves an external iterator over the middleware collection.
163110 *
164- * Implements the IteratorAggregate interface, allowing foreach iteration over the middleware.
165- *
166111 * @return \Generator<MiddlewareInterface> A generator that yields each middleware in the pipeline.
167- *
168- * @example
169- * ```php
170- * foreach ($pipeline as $middleware) {
171- * echo get_class($middleware) . "\n";
172- * }
173- * ```
174112 */
175113 public function getIterator (): \Generator
176114 {
177- yield from $ this ->middlewares ;
115+ foreach ($ this ->middlewares as $ middleware ) {
116+ yield $ middleware ;
117+ }
178118 }
179119
180120 /**
181- * Performs a deep clone of the pipeline.
121+ * Performs a clone of the pipeline.
182122 *
183- * When cloning, each registered middleware and the fallback handler are also cloned
184- * to prevent shared state issues .
123+ * Clones the queue structure and the fallback handler.
124+ * Middleware themselves are not cloned as they should be stateless .
185125 */
186- public function __clone ()
126+ public function __clone (): void
187127 {
188- foreach ($ this ->middlewares as $ i => $ middleware ) {
189- $ this ->middlewares [$ i ] = clone $ middleware ;
190- }
128+ $ this ->middlewares = clone $ this ->middlewares ;
191129 $ this ->fallbackHandler = clone $ this ->fallbackHandler ;
192130 }
193131
194132 /**
195133 * Adds additional middleware to the pipeline.
196134 *
197- * This method appends (or prepends, based on the $prepend flag) the specified middleware to the pipeline.
198- * It returns a new Pipeline instance with the middleware integrated, leaving the original instance unchanged.
199- *
200135 * @param iterable|MiddlewareInterface $middlewares One or more middleware to add.
201136 * @param bool $prepend If true, adds the middleware at the beginning of the chain.
202137 *
203138 * @return PipelineInterface A new Pipeline instance with the added middleware.
204139 *
205140 * @throws \InvalidArgumentException If any provided middleware does not implement MiddlewareInterface.
206141 * @throws \RuntimeException If middleware refers to the pipeline itself.
207- *
208- * @example Append middleware
209- * ```php
210- * $pipeline = $pipeline->pipe([
211- * new CacheMiddleware(),
212- * new CompressionMiddleware(),
213- * ]);
214- * ```
215- *
216- * @example Prepend middleware (execute first)
217- * ```php
218- * $pipeline = $pipeline->pipe(new SecurityHeadersMiddleware(), prepend: true);
219- * ```
220142 */
221143 public function pipe (iterable |MiddlewareInterface $ middlewares , bool $ prepend = false ): PipelineInterface
222144 {
@@ -227,22 +149,18 @@ public function pipe(iterable|MiddlewareInterface $middlewares, bool $prepend =
227149 }
228150
229151 foreach ($ middlewares as $ i => $ middleware ) {
230- $ this ->validateMiddleware ($ middleware , $ i );
152+ $ this ->isMiddlewareInstance ($ middleware , $ i );
231153
232154 if ($ middleware === $ this ) {
233155 throw new \RuntimeException ('Cannot add pipeline to itself - this would create circular reference ' );
234156 }
235157
236- // Check for nested circular references
237158 if ($ middleware instanceof PipelineInterface && $ middleware ->has ($ this )) {
238159 throw new \RuntimeException ('Cannot add pipeline that contains reference to this pipeline ' );
239160 }
240161
241- if ($ prepend ) {
242- array_unshift ($ copy ->middlewares , $ middleware );
243- } else {
244- $ copy ->middlewares [] = $ middleware ;
245- }
162+ $ prepend ? $ copy ->middlewares ->unshift ($ middleware ) :
163+ $ copy ->middlewares ->enqueue ($ middleware );
246164 }
247165
248166 return $ copy ;
@@ -251,87 +169,32 @@ public function pipe(iterable|MiddlewareInterface $middlewares, bool $prepend =
251169 /**
252170 * Processes a request through the middleware chain.
253171 *
254- * This method implements MiddlewareInterface::process(), allowing the pipeline
255- * to act as middleware within another pipeline.
256- *
257172 * @param ServerRequestInterface $request The server request to process.
258173 * @param RequestHandlerInterface $handler The handler to use when middleware chain is exhausted.
259174 * @return ResponseInterface The HTTP response.
260175 *
261- * @example Using pipeline as middleware
262- * ```php
263- * $innerPipeline = new Pipeline([new ValidatorMiddleware()]);
264- *
265- * $outerPipeline = new Pipeline([
266- * new LoggingMiddleware(),
267- * $innerPipeline, // Acts as middleware
268- * ], $handler);
269- * ```
176+ * @throws \RuntimeException If handler is the pipeline itself (would cause double execution).
270177 */
271178 public function process (ServerRequestInterface $ request , RequestHandlerInterface $ handler ): ResponseInterface
272179 {
273- return $ this ->processMiddleware ($ request , 0 , $ handler );
180+ if ($ handler === $ this ) {
181+ throw new \RuntimeException ('Cannot use pipeline as its own handler - this would cause all middleware to execute twice ' );
182+ }
183+
184+ $ next = new Next ($ this ->middlewares , $ handler );
185+ return $ next ->handle ($ request );
274186 }
275187
276188 /**
277189 * Handles a request through the middleware chain using the fallback handler.
278190 *
279- * This method implements RequestHandlerInterface::handle(), allowing the pipeline
280- * to act as a request handler.
281- *
282191 * @param ServerRequestInterface $request The server request to handle.
283192 * @return ResponseInterface The HTTP response.
284- *
285- * @example
286- * ```php
287- * $pipeline = new Pipeline($middlewares, $appHandler);
288- * $response = $pipeline->handle($request);
289- * ```
290193 */
291194 public function handle (ServerRequestInterface $ request ): ResponseInterface
292195 {
293- return $ this ->processMiddleware ($ request , 0 , $ this ->fallbackHandler );
294- }
295-
296- /**
297- * Recursively processes middleware starting from the given position.
298- *
299- * This stateless approach eliminates the need for mutable position tracking,
300- * making the pipeline safe for concurrent use and easier to reason about.
301- *
302- * @param ServerRequestInterface $request The request to process.
303- * @param int $position Current position in the middleware chain.
304- * @param RequestHandlerInterface $finalHandler Handler to use when all middleware are exhausted.
305- * @return ResponseInterface The HTTP response.
306- */
307- private function processMiddleware (
308- ServerRequestInterface $ request ,
309- int $ position ,
310- RequestHandlerInterface $ finalHandler
311- ): ResponseInterface {
312- // If we've exhausted all middleware, use the final handler
313- if (!isset ($ this ->middlewares [$ position ])) {
314- return $ finalHandler ->handle ($ request );
315- }
316-
317- // Create a handler that will process the next middleware in the chain
318- $ nextPosition = $ position + 1 ;
319- $ handler = new class ($ nextPosition , $ finalHandler , function ($ req , $ pos , $ fh ) {
320- return $ this ->processMiddleware ($ req , $ pos , $ fh );
321- }) implements RequestHandlerInterface {
322- public function __construct (
323- private readonly int $ nextPosition ,
324- private readonly RequestHandlerInterface $ finalHandler ,
325- private readonly \Closure $ processor
326- ) {}
327-
328- public function handle (ServerRequestInterface $ request ): ResponseInterface
329- {
330- return ($ this ->processor )($ request , $ this ->nextPosition , $ this ->finalHandler );
331- }
332- };
333-
334- return $ this ->middlewares [$ position ]->process ($ request , $ handler );
196+ $ next = new Next ($ this ->middlewares , $ this ->fallbackHandler );
197+ return $ next ->handle ($ request );
335198 }
336199
337200 /**
@@ -343,7 +206,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface
343206 *
344207 * @throws \InvalidArgumentException If the value does not implement MiddlewareInterface.
345208 */
346- private function validateMiddleware (mixed $ middleware , int |string $ position ): void
209+ private function isMiddlewareInstance (mixed $ middleware , int |string $ position ): void
347210 {
348211 if (!$ middleware instanceof MiddlewareInterface) {
349212 $ type = get_debug_type ($ middleware );
@@ -356,22 +219,10 @@ private function validateMiddleware(mixed $middleware, int|string $position): vo
356219 /**
357220 * Creates a new Pipeline instance from a set of middleware provided as an iterable.
358221 *
359- * This static method allows the initialization of a Pipeline with a collection of middleware,
360- * and optionally, a custom fallback handler. If no fallback handler is provided, the default
361- * EmptyPipelineHandler is used.
362- *
363222 * @param iterable<MiddlewareInterface> $middlewares An iterable collection of middleware objects.
364223 * @param ?RequestHandlerInterface $fallbackHandler The fallback handler to use.
365224 *
366225 * @return self Returns a fully configured Pipeline instance.
367- *
368- * @example
369- * ```php
370- * $pipeline = Pipeline::createFromIterable([
371- * new AuthMiddleware(),
372- * new ValidationMiddleware(),
373- * ], new ApplicationHandler());
374- * ```
375226 */
376227 public static function createFromIterable (iterable $ middlewares , ?RequestHandlerInterface $ fallbackHandler = null ): PipelineInterface
377228 {
@@ -381,17 +232,9 @@ public static function createFromIterable(iterable $middlewares, ?RequestHandler
381232 /**
382233 * Returns a new Pipeline instance with the updated fallback handler.
383234 *
384- * This method creates a clone of the current pipeline and replaces its fallback handler with the provided handler.
385- * By cloning the existing pipeline, it ensures that the original pipeline remains unmodified, promoting immutability.
386- *
387235 * @param RequestHandlerInterface $handler The new fallback handler to be used when the middleware chain is exhausted.
388236 *
389237 * @return PipelineInterface Returns the cloned pipeline instance with the updated fallback handler.
390- *
391- * @example
392- * ```php
393- * $pipeline = $pipeline->withFallbackHandler(new CustomHandler());
394- * ```
395238 */
396239 public function withFallbackHandler (RequestHandlerInterface $ handler ): PipelineInterface
397240 {
0 commit comments