Skip to content

Commit c081e21

Browse files
committed
feat(dispatcher): add headers, cookies, and middleware control
- Add $headers parameter to dispatch and all HTTP verb shortcuts - Add $cookies parameter to dispatch and all HTTP verb shortcuts - Add fluent withoutMiddleware() for skipping middleware per request - Update facade and helper to support new parameters - Add tests for all three features (29 new tests) - Document new features in README
1 parent 6ad0bd3 commit c081e21

9 files changed

Lines changed: 552 additions & 16 deletions

README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,72 @@ $dispatcher->delete('/url', $data);
6969
$dispatcher->options('/url', $data);
7070
```
7171

72+
### Custom Headers
73+
74+
All methods accept an optional `$headers` array as the last parameter, allowing you to set custom headers on the sub request:
75+
76+
```php
77+
// Set Authorization and Accept headers
78+
$dispatcher->get('/api/users', [], [
79+
'Authorization' => 'Bearer my-token',
80+
'Accept' => 'application/json',
81+
]);
82+
83+
// Works with the facade and helper too
84+
SubRequest::dispatch('GET', '/api/users', [], ['Authorization' => 'Bearer my-token']);
85+
subrequest('GET', '/api/users', [], ['Authorization' => 'Bearer my-token']);
86+
```
87+
88+
Headers are applied to the sub request and automatically restored to their original values after dispatch.
89+
90+
### Cookies
91+
92+
All methods accept an optional `$cookies` array as the last parameter, allowing you to forward or set cookies on the sub request:
93+
94+
```php
95+
// Set cookies on the sub request
96+
$dispatcher->get('/api/profile', [], [], [
97+
'session_id' => 'abc123',
98+
'token' => 'my-auth-token',
99+
]);
100+
101+
// Combine headers and cookies
102+
$dispatcher->post('/api/data', ['key' => 'value'], [
103+
'Accept' => 'application/json',
104+
], [
105+
'session_id' => 'abc123',
106+
]);
107+
108+
// Works with the facade and helper too
109+
SubRequest::dispatch('GET', '/api/profile', [], [], ['session_id' => 'abc123']);
110+
subrequest('GET', '/api/profile', [], [], ['session_id' => 'abc123']);
111+
```
112+
113+
Cookies are applied to the sub request and automatically restored to their original values after dispatch.
114+
115+
### Middleware Control
116+
117+
Use `withoutMiddleware()` to skip specific middleware on a sub request:
118+
119+
```php
120+
use App\Http\Middleware\Authenticate;
121+
use App\Http\Middleware\RateLimiter;
122+
123+
// Skip a single middleware
124+
$dispatcher->withoutMiddleware(Authenticate::class)->get('/api/internal');
125+
126+
// Skip multiple middleware by chaining
127+
$dispatcher
128+
->withoutMiddleware(Authenticate::class)
129+
->withoutMiddleware(RateLimiter::class)
130+
->post('/api/internal', $data);
131+
132+
// Or pass an array
133+
$dispatcher->withoutMiddleware([Authenticate::class, RateLimiter::class])->get('/api/internal');
134+
```
135+
136+
The middleware exclusion is automatically reset after each dispatch, so subsequent calls will run all middleware as normal.
137+
72138
## License
73139

74140
MIT

src/Dispatcher.php

Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
class Dispatcher
1212
{
13+
/** @var array<int, class-string> */
14+
private array $excludedMiddleware = [];
15+
1316
public function __construct(private readonly Router $router, private readonly Request $request)
1417
{
1518
}
@@ -18,89 +21,169 @@ public function __construct(private readonly Router $router, private readonly Re
1821
* Shortcut for sending a DELETE sub request.
1922
*
2023
* @param array<string, mixed> $input
24+
* @param array<string, string> $headers
25+
* @param array<string, string> $cookies
2126
*/
22-
public function delete(string $url, array $input = []): Response
27+
public function delete(string $url, array $input = [], array $headers = [], array $cookies = []): Response
2328
{
24-
return $this->dispatch(HttpVerb::DELETE, $url, $input);
29+
return $this->dispatch(HttpVerb::DELETE, $url, $input, $headers, $cookies);
2530
}
2631

2732
/**
2833
* Dispatch a sub request to the Laravel application.
2934
*
3035
* @param array<string, mixed> $input
36+
* @param array<string, string> $headers
37+
* @param array<string, string> $cookies
3138
*/
32-
public function dispatch(string $method, string $url, array $input = []): Response
39+
public function dispatch(string $method, string $url, array $input = [], array $headers = [], array $cookies = []): Response
3340
{
3441
$method = strtoupper($method);
3542

43+
$originalHeaders = [];
44+
foreach ($headers as $name => $value) {
45+
$originalHeaders[$name] = $this->request->headers->get($name);
46+
}
47+
48+
$originalCookies = [];
49+
foreach ($cookies as $name => $value) {
50+
$originalCookies[$name] = $this->request->cookies->get($name);
51+
}
52+
3653
$original = [
3754
'method' => $this->request->getMethod(),
3855
'input' => $this->request->input(),
3956
'route' => $this->router->getCurrentRoute(),
57+
'headers' => $originalHeaders,
58+
'cookies' => $originalCookies,
4059
];
4160

4261
$request = $this->request->create($url, $method, $input);
4362

63+
foreach ($headers as $name => $value) {
64+
$request->headers->set($name, $value);
65+
$this->request->headers->set($name, $value);
66+
}
67+
68+
foreach ($cookies as $name => $value) {
69+
$request->cookies->set($name, $value);
70+
$this->request->cookies->set($name, $value);
71+
}
72+
4473
$this->request->setMethod($method);
4574

4675
$this->request->replace($request->input());
4776

77+
if ($this->excludedMiddleware !== []) {
78+
$route = $this->router->getRoutes()->match($request);
79+
$originalExcluded = $route->excludedMiddleware();
80+
$route->withoutMiddleware($this->excludedMiddleware);
81+
$this->excludedMiddleware = [];
82+
}
83+
4884
$dispatch = $this->router->dispatch($request);
4985

86+
if (isset($route, $originalExcluded)) {
87+
$action = $route->getAction();
88+
$action['excluded_middleware'] = $originalExcluded;
89+
$route->setAction($action);
90+
}
91+
5092
$this->request->setMethod($original['method']);
5193

5294
$this->request->replace($original['input']);
5395

96+
foreach ($original['headers'] as $name => $value) {
97+
if ($value === null) {
98+
$this->request->headers->remove($name);
99+
} else {
100+
$this->request->headers->set($name, $value);
101+
}
102+
}
103+
104+
foreach ($original['cookies'] as $name => $value) {
105+
if ($value === null) {
106+
$this->request->cookies->remove($name);
107+
} else {
108+
$this->request->cookies->set($name, $value);
109+
}
110+
}
111+
54112
return $dispatch;
55113
}
56114

57115
/**
58116
* Shortcut for sending a GET sub request.
59117
*
60118
* @param array<string, mixed> $input
119+
* @param array<string, string> $headers
120+
* @param array<string, string> $cookies
61121
*/
62-
public function get(string $url, array $input = []): Response
122+
public function get(string $url, array $input = [], array $headers = [], array $cookies = []): Response
63123
{
64-
return $this->dispatch(HttpVerb::GET, $url, $input);
124+
return $this->dispatch(HttpVerb::GET, $url, $input, $headers, $cookies);
65125
}
66126

67127
/**
68128
* Shortcut for sending an OPTIONS sub request.
69129
*
70130
* @param array<string, mixed> $input
131+
* @param array<string, string> $headers
132+
* @param array<string, string> $cookies
71133
*/
72-
public function options(string $url, array $input = []): Response
134+
public function options(string $url, array $input = [], array $headers = [], array $cookies = []): Response
73135
{
74-
return $this->dispatch(HttpVerb::OPTIONS, $url, $input);
136+
return $this->dispatch(HttpVerb::OPTIONS, $url, $input, $headers, $cookies);
75137
}
76138

77139
/**
78140
* Shortcut for sending a PATCH sub request.
79141
*
80142
* @param array<string, mixed> $input
143+
* @param array<string, string> $headers
144+
* @param array<string, string> $cookies
81145
*/
82-
public function patch(string $url, array $input = []): Response
146+
public function patch(string $url, array $input = [], array $headers = [], array $cookies = []): Response
83147
{
84-
return $this->dispatch(HttpVerb::PATCH, $url, $input);
148+
return $this->dispatch(HttpVerb::PATCH, $url, $input, $headers, $cookies);
85149
}
86150

87151
/**
88152
* Shortcut for sending a POST sub request.
89153
*
90154
* @param array<string, mixed> $input
155+
* @param array<string, string> $headers
156+
* @param array<string, string> $cookies
91157
*/
92-
public function post(string $url, array $input = []): Response
158+
public function post(string $url, array $input = [], array $headers = [], array $cookies = []): Response
93159
{
94-
return $this->dispatch(HttpVerb::POST, $url, $input);
160+
return $this->dispatch(HttpVerb::POST, $url, $input, $headers, $cookies);
95161
}
96162

97163
/**
98164
* Shortcut for sending a PUT sub request.
99165
*
100166
* @param array<string, mixed> $input
167+
* @param array<string, string> $headers
168+
* @param array<string, string> $cookies
101169
*/
102-
public function put(string $url, array $input = []): Response
170+
public function put(string $url, array $input = [], array $headers = [], array $cookies = []): Response
103171
{
104-
return $this->dispatch(HttpVerb::PUT, $url, $input);
172+
return $this->dispatch(HttpVerb::PUT, $url, $input, $headers, $cookies);
173+
}
174+
175+
/**
176+
* Exclude middleware from the next dispatched sub request.
177+
*
178+
* @param array<int, class-string>|class-string $middleware
179+
*/
180+
public function withoutMiddleware(array|string $middleware): static
181+
{
182+
$this->excludedMiddleware = array_merge(
183+
$this->excludedMiddleware,
184+
(array) $middleware,
185+
);
186+
187+
return $this;
105188
}
106189
}

src/SubRequest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use Symfony\Component\HttpFoundation\Response;
99

1010
/**
11-
* @method static Response dispatch(string $method, string $url, array<string, mixed> $input = [])
11+
* @method static Response dispatch(string $method, string $url, array<string, mixed> $input = [], array<string, string> $headers = [], array<string, string> $cookies = [])
1212
*
1313
* @see Dispatcher
1414
*/

src/helpers.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,17 @@
1111

1212
/**
1313
* @param array<mixed>|Collection<string, mixed> $input
14+
* @param array<string, string> $headers
15+
* @param array<string, string> $cookies
1416
*/
15-
function subrequest(string $method, string $route, array|Collection $input = []): Response
17+
function subrequest(string $method, string $route, array|Collection $input = [], array $headers = [], array $cookies = []): Response
1618
{
1719
if (! in_array($method, HttpVerb::METHODS, true)) {
1820
throw new InvalidArgumentException('$method must be valid http verb (' . implode(', ', HttpVerb::METHODS) . ')');
1921
}
2022

2123
$data = $input instanceof Collection ? $input->toArray() : $input;
2224

23-
return SubRequest::dispatch($method, $route, $data);
25+
return SubRequest::dispatch($method, $route, $data, $headers, $cookies);
2426
}
2527
}

0 commit comments

Comments
 (0)