diff --git a/src/Illuminate/Database/Eloquent/Prunable.php b/src/Illuminate/Database/Eloquent/Prunable.php index f36e1dc5c8f..b1314af362e 100644 --- a/src/Illuminate/Database/Eloquent/Prunable.php +++ b/src/Illuminate/Database/Eloquent/Prunable.php @@ -2,8 +2,10 @@ namespace Illuminate\Database\Eloquent; +use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Database\Events\ModelsPruned; use LogicException; +use Throwable; trait Prunable { @@ -21,9 +23,21 @@ public function pruneAll(int $chunkSize = 1000) ->when(in_array(SoftDeletes::class, class_uses_recursive(static::class)), function ($query) { $query->withTrashed(); })->chunkById($chunkSize, function ($models) use (&$total) { - $models->each->prune(); + $models->each(function ($model) use (&$total) { + try { + $model->prune(); - $total += $models->count(); + $total++; + } catch (Throwable $e) { + $handler = app(ExceptionHandler::class); + + if ($handler) { + $handler->report($e); + } else { + throw $e; + } + } + }); event(new ModelsPruned(static::class, $total)); }); diff --git a/tests/Integration/Database/EloquentPrunableTest.php b/tests/Integration/Database/EloquentPrunableTest.php index 22f48e9464f..495b3f53dfd 100644 --- a/tests/Integration/Database/EloquentPrunableTest.php +++ b/tests/Integration/Database/EloquentPrunableTest.php @@ -2,12 +2,14 @@ namespace Illuminate\Tests\Integration\Database; +use Exception; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Prunable; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Events\ModelsPruned; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Event; +use Illuminate\Support\Facades\Exceptions; use Illuminate\Support\Facades\Schema; use LogicException; @@ -20,6 +22,7 @@ protected function afterRefreshingDatabase() 'prunable_soft_delete_test_models', 'prunable_test_model_missing_prunable_methods', 'prunable_with_custom_prune_method_test_models', + 'prunable_with_exceptions', ])->each(function ($table) { Schema::create($table, function (Blueprint $table) { $table->increments('id'); @@ -97,6 +100,27 @@ public function testPruneWithCustomPruneMethod() Event::assertDispatched(ModelsPruned::class, 1); } + + public function testPruneWithExceptionAtOneOfModels() + { + Event::fake(); + Exceptions::fake(); + + collect(range(1, 5000))->map(function ($id) { + return ['name' => 'foo']; + })->chunk(200)->each(function ($chunk) { + PrunableWithException::insert($chunk->all()); + }); + + $count = (new PrunableWithException)->pruneAll(); + + $this->assertEquals(999, $count); + + Event::assertDispatched(ModelsPruned::class, 1); + Event::assertDispatched(fn (ModelsPruned $event) => $event->count === 999); + Exceptions::assertReportedCount(1); + Exceptions::assertReported(fn (Exception $exception) => $exception->getMessage() === 'foo bar'); + } } class PrunableTestModel extends Model @@ -136,6 +160,23 @@ public function prune() } } +class PrunableWithException extends Model +{ + use Prunable; + + public function prunable() + { + return $this->where('id', '<=', 1000); + } + + public function prune() + { + if ($this->id === 500) { + throw new Exception('foo bar'); + } + } +} + class PrunableTestModelMissingPrunableMethod extends Model { use Prunable;