Search code examples
mysqllaraveltransactionslaravel-artisan

Cannot Persist Further Operations After Rolling Back a Dry Run Transaction


I have an artisan command, in which I am cleaning up some data which has gone bad. Before I actually delete the data, I want to do a dry run and show some of the implications that deleting that data may present.

The essesnce of my command is:

    public function handle()
    {
        ...

        $this->dryRun($modelsToDelete); // Prints info to user

        if ($this->confirm('Are you sure you want to delete?') {
            $modelsToDelete->each->forceDelete();
        }
        ...
    }

    public function dryRun($modelsToDelete)
    {
        ...

        DB::connection($connection)->beginTransaction();
        $before = $this->findAllOrphans($models);

        $modelsToDelete->each(function ($record) use ($bar) {
            $record->forceDelete();
        });

        $after = $this->findAllOrphans($models);
        DB::connection($connection)->rollBack();

        // Print info about diff
        ...
    }

The problem is that when I do the dry run, and confirm to delete, the actual operation is not persisting in the database. If I comment out the dry run and do the command, the operation does persist. I have checked DB::transactionLevel() before and after the dry run and real operation, and everything seems correct.

I have also tried using DB::connection($connection)->pretend(...), but still the same issue. I also tried doing DB::purge($connection) and DB::reconnect($connection) after rolling back.

Does anyone have any thoughts as to what is going on?

(Using Laravel v6.20.14)


Solution

  • after digging the source code, I found out that laravel set property "exists" to false after you call delete on model instance and it will not perform delete query again. you can reference:

    https://github.com/laravel/framework/blob/9edd46fc6dcd550e4fd5d081bea37b0a43162165/src/Illuminate/Database/Eloquent/Model.php#L1173 https://github.com/laravel/framework/blob/9edd46fc6dcd550e4fd5d081bea37b0a43162165/src/Illuminate/Database/Eloquent/Model.php#L1129

    and to make model instance can be deleted after dryRun, you should pass a deep copy to dryRun, for example:

    $this->dryRun(unserialize(serialize($modelsToDelete)));
    

    note: don't use php clone because it's create a shallow copy