Skip to content

Commit c29537f

Browse files
CopilotThavarshan
andcommitted
Add middleware/interceptor system for request/response transformation
Co-authored-by: Thavarshan <10804999+Thavarshan@users.noreply.github.com>
1 parent 5c61f33 commit c29537f

8 files changed

Lines changed: 901 additions & 2 deletions

File tree

src/Fetch/Concerns/PerformsHttpRequests.php

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use GuzzleHttp\Exception\RequestException as GuzzleRequestException;
1414
use GuzzleHttp\Psr7\Request as GuzzleRequest;
1515
use Matrix\Exceptions\AsyncException;
16+
use Psr\Http\Message\RequestInterface;
1617
use React\Promise\PromiseInterface;
1718

1819
use function Matrix\Support\async;
@@ -187,14 +188,98 @@ public function sendRequest(
187188
$handler->logRequest($methodStr, $fullUri, $guzzleOptions);
188189
}
189190

190-
// Send the request (async or sync)
191+
// Check if middleware is available and should be used
192+
if (method_exists($handler, 'hasMiddleware') && $handler->hasMiddleware()) {
193+
return $handler->executeWithMiddleware($methodStr, $fullUri, $guzzleOptions, $startTime);
194+
}
195+
196+
// Send the request (async or sync) without middleware
191197
if ($handler->isAsync) {
192198
return $handler->executeAsyncRequest($methodStr, $fullUri, $guzzleOptions);
193199
} else {
194200
return $handler->executeSyncRequest($methodStr, $fullUri, $guzzleOptions, $startTime);
195201
}
196202
}
197203

204+
/**
205+
* Execute the request through the middleware pipeline.
206+
*
207+
* @param string $method The HTTP method
208+
* @param string $uri The full URI
209+
* @param array<string, mixed> $options The Guzzle options
210+
* @param float $startTime The request start time
211+
* @return ResponseInterface|PromiseInterface The response or promise
212+
*/
213+
protected function executeWithMiddleware(
214+
string $method,
215+
string $uri,
216+
array $options,
217+
float $startTime,
218+
): ResponseInterface|PromiseInterface {
219+
// Create a PSR-7 request from the current state
220+
$body = null;
221+
if (isset($options['body'])) {
222+
$body = $options['body'];
223+
} elseif (isset($options['json'])) {
224+
$body = json_encode($options['json']);
225+
}
226+
227+
$psrRequest = new GuzzleRequest(
228+
$method,
229+
$uri,
230+
$options['headers'] ?? [],
231+
$body
232+
);
233+
234+
// Define the core handler that will be called after all middleware
235+
$coreHandler = function (RequestInterface $request) use ($options, $startTime): ResponseInterface|PromiseInterface {
236+
// Extract method and URI from the (potentially modified) request
237+
$method = $request->getMethod();
238+
$uri = (string) $request->getUri();
239+
240+
// Merge any headers from the modified request back into options
241+
$headers = [];
242+
foreach ($request->getHeaders() as $name => $values) {
243+
$headers[$name] = implode(', ', $values);
244+
}
245+
$options['headers'] = array_merge($options['headers'] ?? [], $headers);
246+
247+
// Handle body from modified request
248+
$body = $request->getBody();
249+
if ($body->getSize() > 0) {
250+
$body->rewind();
251+
$bodyContents = $body->getContents();
252+
$body->rewind();
253+
254+
// Check if it's JSON
255+
$contentType = $request->getHeaderLine('Content-Type');
256+
if (str_contains($contentType, 'application/json')) {
257+
$decoded = json_decode($bodyContents, true);
258+
if ($decoded !== null) {
259+
$options['json'] = $decoded;
260+
unset($options['body']);
261+
} else {
262+
$options['body'] = $bodyContents;
263+
unset($options['json']);
264+
}
265+
} else {
266+
$options['body'] = $bodyContents;
267+
unset($options['json']);
268+
}
269+
}
270+
271+
// Execute the actual request
272+
if ($this->isAsync) {
273+
return $this->executeAsyncRequest($method, $uri, $options);
274+
} else {
275+
return $this->executeSyncRequest($method, $uri, $options, $startTime);
276+
}
277+
};
278+
279+
// Execute through the middleware pipeline
280+
return $this->getMiddlewarePipeline()->handle($psrRequest, $coreHandler);
281+
}
282+
198283
/**
199284
* Sends an HTTP request with the specified parameters.
200285
*
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Fetch\Concerns;
6+
7+
use Fetch\Http\MiddlewarePipeline;
8+
use Fetch\Interfaces\ClientHandler;
9+
use Fetch\Interfaces\MiddlewareInterface;
10+
11+
/**
12+
* Trait for adding middleware support to HTTP clients.
13+
*
14+
* This trait provides methods for managing and executing middleware
15+
* that can transform requests and responses.
16+
*/
17+
trait SupportsMiddleware
18+
{
19+
/**
20+
* The middleware pipeline instance.
21+
*/
22+
protected ?MiddlewarePipeline $middlewarePipeline = null;
23+
24+
/**
25+
* Set multiple middleware at once.
26+
*
27+
* @param array<MiddlewareInterface|array{middleware: MiddlewareInterface, priority: int}> $middleware
28+
* @return $this
29+
*/
30+
public function middleware(array $middleware): ClientHandler
31+
{
32+
$this->middlewarePipeline = new MiddlewarePipeline($middleware);
33+
34+
return $this;
35+
}
36+
37+
/**
38+
* Add a single middleware to the pipeline.
39+
*
40+
* @param MiddlewareInterface $middleware The middleware to add
41+
* @param int $priority Higher priority middleware runs first (default: 0)
42+
* @return $this
43+
*/
44+
public function addMiddleware(MiddlewareInterface $middleware, int $priority = 0): ClientHandler
45+
{
46+
$this->getMiddlewarePipeline()->add($middleware, $priority);
47+
48+
return $this;
49+
}
50+
51+
/**
52+
* Prepend middleware to run first (with highest priority).
53+
*
54+
* @param MiddlewareInterface $middleware The middleware to prepend
55+
* @return $this
56+
*/
57+
public function prependMiddleware(MiddlewareInterface $middleware): ClientHandler
58+
{
59+
$this->getMiddlewarePipeline()->prepend($middleware);
60+
61+
return $this;
62+
}
63+
64+
/**
65+
* Get the middleware pipeline instance.
66+
*/
67+
public function getMiddlewarePipeline(): MiddlewarePipeline
68+
{
69+
if ($this->middlewarePipeline === null) {
70+
$this->middlewarePipeline = new MiddlewarePipeline;
71+
}
72+
73+
return $this->middlewarePipeline;
74+
}
75+
76+
/**
77+
* Check if any middleware is registered.
78+
*/
79+
public function hasMiddleware(): bool
80+
{
81+
return $this->middlewarePipeline !== null && ! $this->middlewarePipeline->isEmpty();
82+
}
83+
84+
/**
85+
* Clear all middleware from the pipeline.
86+
*
87+
* @return $this
88+
*/
89+
public function clearMiddleware(): ClientHandler
90+
{
91+
if ($this->middlewarePipeline !== null) {
92+
$this->middlewarePipeline->clear();
93+
}
94+
95+
return $this;
96+
}
97+
98+
/**
99+
* Conditionally add middleware.
100+
*
101+
* @param bool $condition The condition to check
102+
* @param callable $callback Callback that receives $this and should add middleware
103+
* @return $this
104+
*/
105+
public function when(bool $condition, callable $callback): ClientHandler
106+
{
107+
if ($condition) {
108+
$callback($this);
109+
}
110+
111+
return $this;
112+
}
113+
114+
/**
115+
* Conditionally add middleware (inverse of when).
116+
*
117+
* @param bool $condition The condition to check
118+
* @param callable $callback Callback that receives $this and should add middleware
119+
* @return $this
120+
*/
121+
public function unless(bool $condition, callable $callback): ClientHandler
122+
{
123+
return $this->when(! $condition, $callback);
124+
}
125+
}

src/Fetch/Http/ClientHandler.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Fetch\Concerns\ManagesPromises;
1212
use Fetch\Concerns\ManagesRetries;
1313
use Fetch\Concerns\PerformsHttpRequests;
14+
use Fetch\Concerns\SupportsMiddleware;
1415
use Fetch\Enum\ContentType;
1516
use Fetch\Enum\Method;
1617
use Fetch\Interfaces\ClientHandler as ClientHandlerInterface;
@@ -32,7 +33,8 @@ class ClientHandler implements ClientHandlerInterface
3233
ManagesDebugAndProfiling,
3334
ManagesPromises,
3435
ManagesRetries,
35-
PerformsHttpRequests;
36+
PerformsHttpRequests,
37+
SupportsMiddleware;
3638

3739
/**
3840
* Default options for the request.

0 commit comments

Comments
 (0)