Skip to content

Commit f2c5e1d

Browse files
authored
[7.x] Scoped resource routes (#33752)
* [7.x] Scoped resource routes * cast to array * Fallback to default key if the given key is null * add check against keys * don't call method if no need
1 parent b2c3f66 commit f2c5e1d

File tree

5 files changed

+67
-3
lines changed

5 files changed

+67
-3
lines changed

src/Illuminate/Database/Eloquent/Model.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,6 +1581,8 @@ public function resolveChildRouteBinding($childType, $value, $field)
15811581
{
15821582
$relationship = $this->{Str::plural(Str::camel($childType))}();
15831583

1584+
$field = $field ?: $relationship->getRelated()->getRouteKeyName();
1585+
15841586
if ($relationship instanceof HasManyThrough ||
15851587
$relationship instanceof BelongsToMany) {
15861588
return $relationship->where($relationship->getRelated()->getTable().'.'.$field, $value)->first();

src/Illuminate/Routing/ImplicitRouteBinding.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static function resolveForRoute($container, $route)
3737

3838
$parent = $route->parentOfParameter($parameterName);
3939

40-
if ($parent instanceof UrlRoutable && $route->bindingFieldFor($parameterName)) {
40+
if ($parent instanceof UrlRoutable && in_array($parameterName, array_keys($route->bindingFields()))) {
4141
if (! $model = $parent->resolveChildRouteBinding(
4242
$parameterName, $parameterValue, $route->bindingFieldFor($parameterName)
4343
)) {

src/Illuminate/Routing/PendingResourceRegistration.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,19 @@ public function shallow($shallow = true)
195195
return $this;
196196
}
197197

198+
/**
199+
* Indicate that the resource routes should be scoped using the given binding fields.
200+
*
201+
* @param array $fields
202+
* @return \Illuminate\Routing\PendingResourceRegistration
203+
*/
204+
public function scoped(array $fields = [])
205+
{
206+
$this->options['bindingFields'] = $fields;
207+
208+
return $this;
209+
}
210+
198211
/**
199212
* Register the resource route.
200213
*

src/Illuminate/Routing/ResourceRegistrar.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,15 @@ public function register($name, $controller, array $options = [])
9595
$collection = new RouteCollection;
9696

9797
foreach ($this->getResourceMethods($defaults, $options) as $m) {
98-
$collection->add($this->{'addResource'.ucfirst($m)}(
98+
$route = $this->{'addResource'.ucfirst($m)}(
9999
$name, $base, $controller, $options
100-
));
100+
);
101+
102+
if (isset($options['bindingFields'])) {
103+
$this->setResourceBindingFields($route, $options['bindingFields']);
104+
}
105+
106+
$collection->add($route);
101107
}
102108

103109
return $collection;
@@ -313,6 +319,24 @@ protected function getShallowName($name, $options)
313319
: $name;
314320
}
315321

322+
/**
323+
* Set the route's binding fields if the resource is scoped.
324+
*
325+
* @param \Illuminate\Routing\Route $route
326+
* @param array $bindingFields
327+
* @return void
328+
*/
329+
protected function setResourceBindingFields($route, $bindingFields)
330+
{
331+
preg_match_all('/(?<={).*?(?=})/', $route->uri, $matches);
332+
333+
$fields = array_fill_keys($matches[0], null);
334+
335+
$route->setBindingFields(array_replace(
336+
$fields, array_intersect_key($bindingFields, $fields)
337+
));
338+
}
339+
316340
/**
317341
* Get the base resource URI for a given resource.
318342
*

tests/Routing/RouteRegistrarTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,31 @@ public function testCanSetShallowOptionOnRegisteredResource()
357357
$this->assertFalse($this->router->getRoutes()->hasNamedRoute('users.tasks.show'));
358358
}
359359

360+
public function testCanSetScopedOptionOnRegisteredResource()
361+
{
362+
$this->router->resource('users.tasks', RouteRegistrarControllerStub::class)->scoped();
363+
$this->assertSame(
364+
['user' => null],
365+
$this->router->getRoutes()->getByName('users.tasks.index')->bindingFields()
366+
);
367+
$this->assertSame(
368+
['user' => null, 'task' => null],
369+
$this->router->getRoutes()->getByName('users.tasks.show')->bindingFields()
370+
);
371+
372+
$this->router->resource('users.tasks', RouteRegistrarControllerStub::class)->scoped([
373+
'task' => 'slug',
374+
]);
375+
$this->assertSame(
376+
['user' => null],
377+
$this->router->getRoutes()->getByName('users.tasks.index')->bindingFields()
378+
);
379+
$this->assertSame(
380+
['user' => null, 'task' => 'slug'],
381+
$this->router->getRoutes()->getByName('users.tasks.show')->bindingFields()
382+
);
383+
}
384+
360385
public function testCanExcludeMethodsOnRegisteredApiResource()
361386
{
362387
$this->router->apiResource('users', RouteRegistrarControllerStub::class)

0 commit comments

Comments
 (0)