Skip to content

Commit 3337678

Browse files
committed
feat: allow middleware registration per action on resource and relations
Closes #265
1 parent 79ddd03 commit 3337678

8 files changed

+344
-21
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. This projec
55

66
## Unreleased
77

8+
### Added
9+
10+
- [#265](https://github.com/laravel-json-api/laravel/issues/265) Allow registration of middleware per action on both
11+
resource routes and relationship routes.
12+
813
## [3.2.0] - 2023-11-08
914

1015
### Added

src/Routing/PendingRelationshipRegistration.php

+22-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
namespace LaravelJsonApi\Laravel\Routing;
2121

2222
use Illuminate\Routing\RouteCollection;
23+
use Illuminate\Support\Arr;
2324

2425
class PendingRelationshipRegistration
2526
{
@@ -155,12 +156,30 @@ public function name(string $method, string $name): self
155156
/**
156157
* Add middleware to the resource routes.
157158
*
158-
* @param string ...$middleware
159+
* @param mixed ...$middleware
159160
* @return $this
160161
*/
161-
public function middleware(string ...$middleware): self
162+
public function middleware(...$middleware): self
162163
{
163-
$this->options['middleware'] = $middleware;
164+
if (count($middleware) === 1) {
165+
$middleware = Arr::wrap($middleware[0]);
166+
}
167+
168+
if (array_is_list($middleware)) {
169+
$this->options['middleware'] = $middleware;
170+
return $this;
171+
}
172+
173+
$this->options['middleware'] = Arr::wrap($middleware['*'] ?? null);
174+
175+
foreach ($this->map as $alias => $action) {
176+
if (isset($middleware[$alias])) {
177+
$middleware[$action] = $middleware[$alias];
178+
unset($middleware[$alias]);
179+
}
180+
}
181+
182+
$this->options['action_middleware'] = $middleware;
164183

165184
return $this;
166185
}

src/Routing/PendingResourceRegistration.php

+15-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
use Closure;
2323
use Illuminate\Routing\RouteCollection;
24+
use Illuminate\Support\Arr;
2425
use InvalidArgumentException;
2526
use function is_string;
2627

@@ -180,12 +181,22 @@ public function parameter(string $parameter): self
180181
/**
181182
* Add middleware to the resource routes.
182183
*
183-
* @param string ...$middleware
184+
* @param mixed ...$middleware
184185
* @return $this
185186
*/
186-
public function middleware(string ...$middleware): self
187+
public function middleware(...$middleware): self
187188
{
188-
$this->options['middleware'] = $middleware;
189+
if (count($middleware) === 1) {
190+
$middleware = Arr::wrap($middleware[0]);
191+
}
192+
193+
if (array_is_list($middleware)) {
194+
$this->options['middleware'] = $middleware;
195+
return $this;
196+
}
197+
198+
$this->options['middleware'] = Arr::wrap($middleware['*'] ?? null);
199+
$this->options['action_middleware'] = $middleware;
189200

190201
return $this;
191202
}
@@ -196,7 +207,7 @@ public function middleware(string ...$middleware): self
196207
* @param string ...$middleware
197208
* @return $this
198209
*/
199-
public function withoutMiddleware(string ...$middleware)
210+
public function withoutMiddleware(string ...$middleware): self
200211
{
201212
$this->options['excluded_middleware'] = array_merge(
202213
(array) ($this->options['excluded_middleware'] ?? []),

src/Routing/RelationshipRegistrar.php

+20-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Illuminate\Contracts\Routing\Registrar as RegistrarContract;
2323
use Illuminate\Routing\Route as IlluminateRoute;
2424
use Illuminate\Routing\RouteCollection;
25+
use Illuminate\Support\Arr;
2526
use LaravelJsonApi\Contracts\Schema\Schema;
2627
use LaravelJsonApi\Core\Support\Str;
2728

@@ -272,9 +273,10 @@ private function getRelationshipAction(
272273
$name = $this->getRelationRouteName($method, $defaultName, $options);
273274

274275
$action = ['as' => $name, 'uses' => $this->controller.'@'.$method];
276+
$middleware = $this->getMiddleware($method, $options);
275277

276-
if (isset($options['middleware'])) {
277-
$action['middleware'] = $options['middleware'];
278+
if (!empty($middleware)) {
279+
$action['middleware'] = $middleware;
278280
}
279281

280282
if (isset($options['excluded_middleware'])) {
@@ -284,6 +286,22 @@ private function getRelationshipAction(
284286
return $action;
285287
}
286288

289+
/**
290+
* @param string $action
291+
* @param array $options
292+
* @return array
293+
*/
294+
private function getMiddleware(string $action, array $options): array
295+
{
296+
$all = $options['middleware'] ?? [];
297+
$actions = $options['action_middleware'] ?? [];
298+
299+
return [
300+
...$all,
301+
...Arr::wrap($actions[$action] ?? null),
302+
];
303+
}
304+
287305
/**
288306
* @param string $fieldName
289307
* @return string

src/Routing/ResourceRegistrar.php

+21-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Illuminate\Contracts\Routing\Registrar as RegistrarContract;
2424
use Illuminate\Routing\Route as IlluminateRoute;
2525
use Illuminate\Routing\RouteCollection;
26+
use Illuminate\Support\Arr;
2627
use LaravelJsonApi\Contracts\Server\Server;
2728
use LaravelJsonApi\Core\Support\Str;
2829

@@ -337,13 +338,14 @@ private function getResourceAction(
337338
string $method,
338339
?string $parameter,
339340
array $options
340-
) {
341+
): array {
341342
$name = $this->getResourceRouteName($resourceType, $method, $options);
342343

343344
$action = ['as' => $name, 'uses' => $controller.'@'.$method];
345+
$middleware = $this->getMiddleware($method, $options);
344346

345-
if (isset($options['middleware'])) {
346-
$action['middleware'] = $options['middleware'];
347+
if (!empty($middleware)) {
348+
$action['middleware'] = $middleware;
347349
}
348350

349351
if (isset($options['excluded_middleware'])) {
@@ -355,6 +357,22 @@ private function getResourceAction(
355357
return $action;
356358
}
357359

360+
/**
361+
* @param string $action
362+
* @param array $options
363+
* @return array
364+
*/
365+
private function getMiddleware(string $action, array $options): array
366+
{
367+
$all = $options['middleware'] ?? [];
368+
$actions = $options['action_middleware'] ?? [];
369+
370+
return [
371+
...$all,
372+
...Arr::wrap($actions[$action] ?? null),
373+
];
374+
}
375+
358376
/**
359377
* Get the action array for the relationships group.
360378
*

tests/lib/Integration/Routing/HasManyTest.php

+84-2
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,95 @@ public function testMiddleware(string $method, string $uri): void
144144
->middleware('foo')
145145
->resources(function ($server) {
146146
$server->resource('posts')->middleware('bar')->relationships(function ($relations) {
147-
$relations->hasMany('tags')->middleware('baz');
147+
$relations->hasMany('tags')->middleware('baz1', 'baz2');
148148
});
149149
});
150150
});
151151

152152
$route = $this->assertMatch($method, $uri);
153-
$this->assertSame(['api', 'jsonapi:v1', 'foo', 'bar', 'baz'], $route->action['middleware']);
153+
$this->assertSame(['api', 'jsonapi:v1', 'foo', 'bar', 'baz1', 'baz2'], $route->action['middleware']);
154+
}
155+
156+
/**
157+
* @param string $method
158+
* @param string $uri
159+
* @dataProvider genericProvider
160+
*/
161+
public function testMiddlewareAsArrayList(string $method, string $uri): void
162+
{
163+
$server = $this->createServer('v1');
164+
$schema = $this->createSchema($server, 'posts', '\d+');
165+
$this->createRelation($schema, 'tags');
166+
167+
$this->defaultApiRoutesWithNamespace(function () {
168+
JsonApiRoute::server('v1')
169+
->prefix('v1')
170+
->namespace('Api\\V1')
171+
->middleware('foo')
172+
->resources(function ($server) {
173+
$server->resource('posts')->middleware('bar')->relationships(function ($relations) {
174+
$relations->hasMany('tags')->middleware(['baz1', 'baz2']);
175+
});
176+
});
177+
});
178+
179+
$route = $this->assertMatch($method, $uri);
180+
$this->assertSame(['api', 'jsonapi:v1', 'foo', 'bar', 'baz1', 'baz2'], $route->action['middleware']);
181+
}
182+
183+
/**
184+
* @param string $method
185+
* @param string $uri
186+
* @param string $action
187+
* @dataProvider genericProvider
188+
*/
189+
public function testActionMiddleware(string $method, string $uri, string $action): void
190+
{
191+
$actions = [
192+
'*' => ['baz1', 'baz2'],
193+
'showRelated' => 'showRelated1',
194+
'showRelationship' => ['showRelationship1', 'showRelationship2'],
195+
'updateRelationship' => 'updateRelationship1',
196+
'attachRelationship' => ['attachRelationship1', 'attachRelationship2'],
197+
'detachRelationship' => 'detachRelationship1',
198+
];
199+
200+
$expected = [
201+
'api',
202+
'jsonapi:v1',
203+
'foo',
204+
'bar',
205+
...$actions['*'],
206+
...Arr::wrap($actions[$action]),
207+
];
208+
209+
$server = $this->createServer('v1');
210+
$schema = $this->createSchema($server, 'posts', '\d+');
211+
$this->createRelation($schema, 'tags');
212+
213+
$this->defaultApiRoutesWithNamespace(function () use ($actions) {
214+
JsonApiRoute::server('v1')
215+
->prefix('v1')
216+
->namespace('Api\\V1')
217+
->middleware('foo')
218+
->resources(function ($server) use ($actions) {
219+
$server->resource('posts')->middleware('bar')->relationships(
220+
function ($relations) use ($actions) {
221+
$relations->hasMany('tags')->middleware([
222+
'*' => $actions['*'],
223+
'related' => $actions['showRelated'],
224+
'show' => $actions['showRelationship'],
225+
'update' => $actions['updateRelationship'],
226+
'attach' => $actions['attachRelationship'],
227+
'detach' => $actions['detachRelationship'],
228+
]);
229+
},
230+
);
231+
});
232+
});
233+
234+
$route = $this->assertMatch($method, $uri);
235+
$this->assertSame($expected, $route->action['middleware']);
154236
}
155237

156238
/**

0 commit comments

Comments
 (0)