Skip to content

Commit 747f166

Browse files
authored
support withTrashed on routes (#38348)
1 parent b0eca38 commit 747f166

File tree

4 files changed

+139
-4
lines changed

4 files changed

+139
-4
lines changed

src/Illuminate/Database/Eloquent/Model.php

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1797,6 +1797,18 @@ public function resolveRouteBinding($value, $field = null)
17971797
return $this->where($field ?? $this->getRouteKeyName(), $value)->first();
17981798
}
17991799

1800+
/**
1801+
* Retrieve the model for a bound value.
1802+
*
1803+
* @param mixed $value
1804+
* @param string|null $field
1805+
* @return \Illuminate\Database\Eloquent\Model|null
1806+
*/
1807+
public function resolveSoftDeletableRouteBinding($value, $field = null)
1808+
{
1809+
return $this->where($field ?? $this->getRouteKeyName(), $value)->withTrashed()->first();
1810+
}
1811+
18001812
/**
18011813
* Retrieve the child model for a bound value.
18021814
*
@@ -1806,16 +1818,42 @@ public function resolveRouteBinding($value, $field = null)
18061818
* @return \Illuminate\Database\Eloquent\Model|null
18071819
*/
18081820
public function resolveChildRouteBinding($childType, $value, $field)
1821+
{
1822+
return $this->resolveChildRouteBindingQuery($childType, $value, $field)->first();
1823+
}
1824+
1825+
/**
1826+
* Retrieve the child model for a bound value.
1827+
*
1828+
* @param string $childType
1829+
* @param mixed $value
1830+
* @param string|null $field
1831+
* @return \Illuminate\Database\Eloquent\Model|null
1832+
*/
1833+
public function resolveSoftDeletableChildRouteBinding($childType, $value, $field)
1834+
{
1835+
return $this->resolveChildRouteBindingQuery($childType, $value, $field)->withTrashed()->first();
1836+
}
1837+
1838+
/**
1839+
* Retrieve the child model query for a bound value.
1840+
*
1841+
* @param string $childType
1842+
* @param mixed $value
1843+
* @param string|null $field
1844+
* @return \Illuminate\Database\Eloquent\Model|null
1845+
*/
1846+
protected function resolveChildRouteBindingQuery($childType, $value, $field)
18091847
{
18101848
$relationship = $this->{Str::plural(Str::camel($childType))}();
18111849

18121850
$field = $field ?: $relationship->getRelated()->getRouteKeyName();
18131851

18141852
if ($relationship instanceof HasManyThrough ||
18151853
$relationship instanceof BelongsToMany) {
1816-
return $relationship->where($relationship->getRelated()->getTable().'.'.$field, $value)->first();
1854+
return $relationship->where($relationship->getRelated()->getTable().'.'.$field, $value);
18171855
} else {
1818-
return $relationship->where($field, $value)->first();
1856+
return $relationship->where($field, $value);
18191857
}
18201858
}
18211859

src/Illuminate/Routing/ImplicitRouteBinding.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,21 @@ public static function resolveForRoute($container, $route)
3737

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

40+
$routeBindingMethod = $route->allowsTrashedBindings()
41+
? 'resolveSoftDeletableRouteBinding'
42+
: 'resolveRouteBinding';
43+
4044
if ($parent instanceof UrlRoutable && in_array($parameterName, array_keys($route->bindingFields()))) {
41-
if (! $model = $parent->resolveChildRouteBinding(
45+
$childRouteBindingMethod = $route->allowsTrashedBindings()
46+
? 'resolveSoftDeletableChildRouteBinding'
47+
: 'resolveChildRouteBinding';
48+
49+
if (! $model = $parent->{$childRouteBindingMethod}(
4250
$parameterName, $parameterValue, $route->bindingFieldFor($parameterName)
4351
)) {
4452
throw (new ModelNotFoundException)->setModel(get_class($instance), [$parameterValue]);
4553
}
46-
} elseif (! $model = $instance->resolveRouteBinding($parameterValue, $route->bindingFieldFor($parameterName))) {
54+
} elseif (! $model = $instance->{$routeBindingMethod}($parameterValue, $route->bindingFieldFor($parameterName))) {
4755
throw (new ModelNotFoundException)->setModel(get_class($instance), [$parameterValue]);
4856
}
4957

src/Illuminate/Routing/Route.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ class Route
9393
*/
9494
protected $originalParameters;
9595

96+
/**
97+
* Indicates "trashed" models can be retrieved when resolving implicit model bindings for this route.
98+
*
99+
* @var bool
100+
*/
101+
protected $withTrashedBindings = false;
102+
96103
/**
97104
* Indicates the maximum number of seconds the route should acquire a session lock for.
98105
*
@@ -559,6 +566,28 @@ public function parentOfParameter($parameter)
559566
return array_values($this->parameters)[$key - 1];
560567
}
561568

569+
/**
570+
* Allow "trashed" models to be retrieved when resolving implicit model bindings for this route.
571+
*
572+
* @return $this
573+
*/
574+
public function withTrashed()
575+
{
576+
$this->withTrashedBindings = true;
577+
578+
return $this;
579+
}
580+
581+
/**
582+
* Determines if the route allows "trashed" models to be retrieved when resolving implicit model bindings.
583+
*
584+
* @return bool
585+
*/
586+
public function allowsTrashedBindings()
587+
{
588+
return $this->withTrashedBindings;
589+
}
590+
562591
/**
563592
* Set a default value for the route.
564593
*

tests/Integration/Routing/ImplicitRouteBindingTest.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
namespace Illuminate\Tests\Integration\Routing;
44

55
use Illuminate\Database\Eloquent\Model;
6+
use Illuminate\Database\Eloquent\SoftDeletes;
67
use Illuminate\Database\Schema\Blueprint;
8+
use Illuminate\Support\Facades\Route;
79
use Illuminate\Support\Facades\Schema;
810
use Orchestra\Testbench\Concerns\InteractsWithPublishedFiles;
911
use Orchestra\Testbench\TestCase;
@@ -45,6 +47,7 @@ protected function defineDatabaseMigrations(): void
4547
$table->increments('id');
4648
$table->string('name');
4749
$table->timestamps();
50+
$table->softDeletes();
4851
});
4952

5053
$this->beforeApplicationDestroyed(function () {
@@ -73,10 +76,67 @@ public function testWithRouteCachingEnabled()
7376
'name' => $user->name,
7477
]);
7578
}
79+
80+
public function testWithoutRouteCachingEnabled()
81+
{
82+
$user = ImplicitBindingModel::create(['name' => 'Dries']);
83+
84+
config(['app.key' => str_repeat('a', 32)]);
85+
86+
Route::post('/user/{user}', function (ImplicitBindingModel $user) {
87+
return $user;
88+
})->middleware(['web']);
89+
90+
$response = $this->postJson("/user/{$user->id}");
91+
92+
$response->assertJson([
93+
'id' => $user->id,
94+
'name' => $user->name,
95+
]);
96+
}
97+
98+
public function testSoftDeletedModelsAreNotRetrieved()
99+
{
100+
$user = ImplicitBindingModel::create(['name' => 'Dries']);
101+
102+
$user->delete();
103+
104+
config(['app.key' => str_repeat('a', 32)]);
105+
106+
Route::post('/user/{user}', function (ImplicitBindingModel $user) {
107+
return $user;
108+
})->middleware(['web']);
109+
110+
$response = $this->postJson("/user/{$user->id}");
111+
112+
$response->assertStatus(404);
113+
}
114+
115+
public function testSoftDeletedModelsCanBeRetrievedUsingWithTrashedMethod()
116+
{
117+
$user = ImplicitBindingModel::create(['name' => 'Dries']);
118+
119+
$user->delete();
120+
121+
config(['app.key' => str_repeat('a', 32)]);
122+
123+
Route::post('/user/{user}', function (ImplicitBindingModel $user) {
124+
return $user;
125+
})->middleware(['web'])->withTrashed();
126+
127+
$response = $this->postJson("/user/{$user->id}");
128+
129+
$response->assertJson([
130+
'id' => $user->id,
131+
'name' => $user->name,
132+
]);
133+
}
76134
}
77135

78136
class ImplicitBindingModel extends Model
79137
{
138+
use SoftDeletes;
139+
80140
public $table = 'users';
81141

82142
protected $fillable = ['name'];

0 commit comments

Comments
 (0)