Skip to content

Commit

Permalink
support withTrashed on routes (#38348)
Browse files Browse the repository at this point in the history
  • Loading branch information
taylorotwell authored Aug 12, 2021
1 parent b0eca38 commit 747f166
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 4 deletions.
42 changes: 40 additions & 2 deletions src/Illuminate/Database/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -1797,6 +1797,18 @@ public function resolveRouteBinding($value, $field = null)
return $this->where($field ?? $this->getRouteKeyName(), $value)->first();
}

/**
* Retrieve the model for a bound value.
*
* @param mixed $value
* @param string|null $field
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveSoftDeletableRouteBinding($value, $field = null)
{
return $this->where($field ?? $this->getRouteKeyName(), $value)->withTrashed()->first();
}

/**
* Retrieve the child model for a bound value.
*
Expand All @@ -1806,16 +1818,42 @@ public function resolveRouteBinding($value, $field = null)
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveChildRouteBinding($childType, $value, $field)
{
return $this->resolveChildRouteBindingQuery($childType, $value, $field)->first();
}

/**
* Retrieve the child model for a bound value.
*
* @param string $childType
* @param mixed $value
* @param string|null $field
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveSoftDeletableChildRouteBinding($childType, $value, $field)
{
return $this->resolveChildRouteBindingQuery($childType, $value, $field)->withTrashed()->first();
}

/**
* Retrieve the child model query for a bound value.
*
* @param string $childType
* @param mixed $value
* @param string|null $field
* @return \Illuminate\Database\Eloquent\Model|null
*/
protected function resolveChildRouteBindingQuery($childType, $value, $field)
{
$relationship = $this->{Str::plural(Str::camel($childType))}();

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

if ($relationship instanceof HasManyThrough ||
$relationship instanceof BelongsToMany) {
return $relationship->where($relationship->getRelated()->getTable().'.'.$field, $value)->first();
return $relationship->where($relationship->getRelated()->getTable().'.'.$field, $value);
} else {
return $relationship->where($field, $value)->first();
return $relationship->where($field, $value);
}
}

Expand Down
12 changes: 10 additions & 2 deletions src/Illuminate/Routing/ImplicitRouteBinding.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,21 @@ public static function resolveForRoute($container, $route)

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

$routeBindingMethod = $route->allowsTrashedBindings()
? 'resolveSoftDeletableRouteBinding'
: 'resolveRouteBinding';

if ($parent instanceof UrlRoutable && in_array($parameterName, array_keys($route->bindingFields()))) {
if (! $model = $parent->resolveChildRouteBinding(
$childRouteBindingMethod = $route->allowsTrashedBindings()
? 'resolveSoftDeletableChildRouteBinding'
: 'resolveChildRouteBinding';

if (! $model = $parent->{$childRouteBindingMethod}(
$parameterName, $parameterValue, $route->bindingFieldFor($parameterName)
)) {
throw (new ModelNotFoundException)->setModel(get_class($instance), [$parameterValue]);
}
} elseif (! $model = $instance->resolveRouteBinding($parameterValue, $route->bindingFieldFor($parameterName))) {
} elseif (! $model = $instance->{$routeBindingMethod}($parameterValue, $route->bindingFieldFor($parameterName))) {
throw (new ModelNotFoundException)->setModel(get_class($instance), [$parameterValue]);
}

Expand Down
29 changes: 29 additions & 0 deletions src/Illuminate/Routing/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ class Route
*/
protected $originalParameters;

/**
* Indicates "trashed" models can be retrieved when resolving implicit model bindings for this route.
*
* @var bool
*/
protected $withTrashedBindings = false;

/**
* Indicates the maximum number of seconds the route should acquire a session lock for.
*
Expand Down Expand Up @@ -559,6 +566,28 @@ public function parentOfParameter($parameter)
return array_values($this->parameters)[$key - 1];
}

/**
* Allow "trashed" models to be retrieved when resolving implicit model bindings for this route.
*
* @return $this
*/
public function withTrashed()
{
$this->withTrashedBindings = true;

return $this;
}

/**
* Determines if the route allows "trashed" models to be retrieved when resolving implicit model bindings.
*
* @return bool
*/
public function allowsTrashedBindings()
{
return $this->withTrashedBindings;
}

/**
* Set a default value for the route.
*
Expand Down
60 changes: 60 additions & 0 deletions tests/Integration/Routing/ImplicitRouteBindingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
namespace Illuminate\Tests\Integration\Routing;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Schema;
use Orchestra\Testbench\Concerns\InteractsWithPublishedFiles;
use Orchestra\Testbench\TestCase;
Expand Down Expand Up @@ -45,6 +47,7 @@ protected function defineDatabaseMigrations(): void
$table->increments('id');
$table->string('name');
$table->timestamps();
$table->softDeletes();
});

$this->beforeApplicationDestroyed(function () {
Expand Down Expand Up @@ -73,10 +76,67 @@ public function testWithRouteCachingEnabled()
'name' => $user->name,
]);
}

public function testWithoutRouteCachingEnabled()
{
$user = ImplicitBindingModel::create(['name' => 'Dries']);

config(['app.key' => str_repeat('a', 32)]);

Route::post('/user/{user}', function (ImplicitBindingModel $user) {
return $user;
})->middleware(['web']);

$response = $this->postJson("/user/{$user->id}");

$response->assertJson([
'id' => $user->id,
'name' => $user->name,
]);
}

public function testSoftDeletedModelsAreNotRetrieved()
{
$user = ImplicitBindingModel::create(['name' => 'Dries']);

$user->delete();

config(['app.key' => str_repeat('a', 32)]);

Route::post('/user/{user}', function (ImplicitBindingModel $user) {
return $user;
})->middleware(['web']);

$response = $this->postJson("/user/{$user->id}");

$response->assertStatus(404);
}

public function testSoftDeletedModelsCanBeRetrievedUsingWithTrashedMethod()
{
$user = ImplicitBindingModel::create(['name' => 'Dries']);

$user->delete();

config(['app.key' => str_repeat('a', 32)]);

Route::post('/user/{user}', function (ImplicitBindingModel $user) {
return $user;
})->middleware(['web'])->withTrashed();

$response = $this->postJson("/user/{$user->id}");

$response->assertJson([
'id' => $user->id,
'name' => $user->name,
]);
}
}

class ImplicitBindingModel extends Model
{
use SoftDeletes;

public $table = 'users';

protected $fillable = ['name'];
Expand Down

0 comments on commit 747f166

Please sign in to comment.