Skip to content

[12.x] isSoftDeletable(), isPrunable(), and isMassPrunable() to model class #56060

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 4 additions & 20 deletions src/Illuminate/Database/Console/PruneCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@

use Illuminate\Console\Command;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Eloquent\MassPrunable;
use Illuminate\Database\Eloquent\Prunable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Events\ModelPruningFinished;
use Illuminate\Database\Events\ModelPruningStarting;
use Illuminate\Database\Events\ModelsPruned;
Expand Down Expand Up @@ -101,7 +98,7 @@ protected function pruneModel(string $model)
? $instance->prunableChunkSize
: $this->option('chunk');

$total = $this->isPrunable($model)
$total = $model::isPrunable()
? $instance->pruneAll($chunkSize)
: 0;

Expand Down Expand Up @@ -141,7 +138,7 @@ protected function models()
})
->when(! empty($except), fn ($models) => $models->reject(fn ($model) => in_array($model, $except)))
->filter(fn ($model) => class_exists($model))
->filter(fn ($model) => $this->isPrunable($model))
->filter(fn ($model) => $model::isPrunable())
->values();
}

Expand All @@ -161,31 +158,18 @@ protected function getPath()
return app_path('Models');
}

/**
* Determine if the given model class is prunable.
*
* @param string $model
* @return bool
*/
protected function isPrunable($model)
{
$uses = class_uses_recursive($model);

return in_array(Prunable::class, $uses) || in_array(MassPrunable::class, $uses);
}

/**
* Display how many models will be pruned.
*
* @param string $model
* @param class-string $model
* @return void
*/
protected function pretendToPrune($model)
{
$instance = new $model;

$count = $instance->prunable()
->when(in_array(SoftDeletes::class, class_uses_recursive(get_class($instance))), function ($query) {
->when($model::isSoftDeletable(), function ($query) {
$query->withTrashed();
})->count();

Expand Down
3 changes: 1 addition & 2 deletions src/Illuminate/Database/Eloquent/Factories/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Enumerable;
Expand Down Expand Up @@ -971,7 +970,7 @@ public function __call($method, $parameters)
return $this->macroCall($method, $parameters);
}

if ($method === 'trashed' && in_array(SoftDeletes::class, class_uses_recursive($this->modelName()))) {
if ($method === 'trashed' && $this->modelName()::isSoftDeletable()) {
return $this->state([
$this->newModel()->getDeletedAtColumn() => $parameters[0] ?? Carbon::now()->subDay(),
]);
Expand Down
2 changes: 1 addition & 1 deletion src/Illuminate/Database/Eloquent/MassPrunable.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public function pruneAll(int $chunkSize = 1000)

$total = 0;

$softDeletable = in_array(SoftDeletes::class, class_uses_recursive(get_class($this)));
$softDeletable = static::isSoftDeletable();

do {
$total += $count = $softDeletable
Expand Down
24 changes: 24 additions & 0 deletions src/Illuminate/Database/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -2289,6 +2289,30 @@ public function setPerPage($perPage)
return $this;
}

/**
* Determine if the model is soft deletable.
*/
public static function isSoftDeletable(): bool
{
return in_array(SoftDeletes::class, class_uses_recursive(static::class));
}

/**
* Determine if the model is prunable.
*/
protected function isPrunable(): bool
{
return in_array(Prunable::class, class_uses_recursive(static::class)) || static::isMassPrunable();
}

/**
* Determine if the model is mass prunable.
*/
protected function isMassPrunable(): bool
{
return in_array(MassPrunable::class, class_uses_recursive(static::class));
}

/**
* Determine if lazy loading is disabled.
*
Expand Down
4 changes: 2 additions & 2 deletions src/Illuminate/Database/Eloquent/Prunable.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function pruneAll(int $chunkSize = 1000)
$total = 0;

$this->prunable()
->when(in_array(SoftDeletes::class, class_uses_recursive(static::class)), function ($query) {
->when(static::isSoftDeletable(), function ($query) {
$query->withTrashed();
})->chunkById($chunkSize, function ($models) use (&$total) {
$models->each(function ($model) use (&$total) {
Expand Down Expand Up @@ -64,7 +64,7 @@ public function prune()
{
$this->pruning();

return in_array(SoftDeletes::class, class_uses_recursive(static::class))
return static::isSoftDeletable()
? $this->forceDelete()
: $this->delete();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Query\Grammars\MySqlGrammar;
use Illuminate\Database\UniqueConstraintViolationException;

Expand Down Expand Up @@ -146,7 +145,7 @@ public function getQualifiedParentKeyName()
*/
public function throughParentSoftDeletes()
{
return in_array(SoftDeletes::class, class_uses_recursive($this->throughParent));
return $this->throughParent::isSoftDeletable();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
Expand Down Expand Up @@ -223,8 +222,7 @@ public function expectsDatabaseQueryCount($expected, $connection = null)
*/
protected function isSoftDeletableModel($model)
{
return $model instanceof Model
&& in_array(SoftDeletes::class, class_uses_recursive($model));
return $model instanceof Model && $model::isSoftDeletable();
}

/**
Expand Down
5 changes: 2 additions & 3 deletions src/Illuminate/Routing/ImplicitRouteBinding.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use Illuminate\Contracts\Routing\UrlRoutable;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException;
use Illuminate\Support\Reflector;
use Illuminate\Support\Str;
Expand Down Expand Up @@ -42,14 +41,14 @@ public static function resolveForRoute($container, $route)

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

$routeBindingMethod = $route->allowsTrashedBindings() && in_array(SoftDeletes::class, class_uses_recursive($instance))
$routeBindingMethod = $route->allowsTrashedBindings() && $instance::isSoftDeletable()
? 'resolveSoftDeletableRouteBinding'
: 'resolveRouteBinding';

if ($parent instanceof UrlRoutable &&
! $route->preventsScopedBindings() &&
($route->enforcesScopedBindings() || array_key_exists($parameterName, $route->bindingFields()))) {
$childRouteBindingMethod = $route->allowsTrashedBindings() && in_array(SoftDeletes::class, class_uses_recursive($instance))
$childRouteBindingMethod = $route->allowsTrashedBindings() && $instance::isSoftDeletable()
? 'resolveSoftDeletableChildRouteBinding'
: 'resolveChildRouteBinding';

Expand Down
3 changes: 1 addition & 2 deletions src/Illuminate/Routing/RouteBinding.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use Closure;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;

class RouteBinding
Expand Down Expand Up @@ -68,7 +67,7 @@ public static function forModel($container, $class, $callback = null)
// throw a not found exception otherwise we will return the instance.
$instance = $container->make($class);

$routeBindingMethod = $route?->allowsTrashedBindings() && in_array(SoftDeletes::class, class_uses_recursive($instance))
$routeBindingMethod = $route?->allowsTrashedBindings() && $instance::isSoftDeletable()
? 'resolveSoftDeletableRouteBinding'
: 'resolveRouteBinding';

Expand Down
4 changes: 2 additions & 2 deletions tests/Support/SupportArrTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,8 @@ public function testWhereNotNull(): void
$array = array_values(Arr::whereNotNull(['a', null, 'b', null, 'c']));
$this->assertEquals(['a', 'b', 'c'], $array);

$array = array_values(Arr::whereNotNull([null, 1, 'string', 0.0, false, [], new stdClass(), fn () => null]));
$this->assertEquals([1, 'string', 0.0, false, [], new stdClass(), fn () => null], $array);
$array = array_values(Arr::whereNotNull([null, 1, 'string', 0.0, false, [], $class = new stdClass(), $function = fn () => null]));
$this->assertEquals([1, 'string', 0.0, false, [], $class, $function], $array);
}

public function testFirst()
Expand Down
Loading