laravel - save vs saveorfail(简而言之真正的区别)

laravel - save vs saveorfail (real difference in nutshell)

我终于明白这个概念了,因为有些案例我还是没搞懂。

问题1)什么是save()return?它总是布尔值还是有时会抛出异常?

问题 2)我没有使用任何事件模型。所以我认为 save() 不会在任何时候 return 为假。那么它会 return true 还是抛出异常?我说得对吗?

问题3)如果我有这样的问题:

DB::beginTransaction();
try{
  $model1 = new Type();
  $model1->test = 'great';
  $model1->save();

  $model2 = new Type();
  $model2->test2 = 'awesome';
  $model2->save();
  DB::commit();
}catch(Exception $e){
  DB::rollBack();
}

有没有可能保存没有发生但没有抛出异常?我在这些模型中没有任何事件。

问题 4) 如果问题 3 的答案是“不,这是不可能的”,那么为什么我需要使用 saveOrFail()

我真的很感激,因为我真的找不到任何能深入解释我的问题的东西。

Q1: save() 总是 return 布尔值 source: https://laravel.com/api/5.8/Illuminate/Database/Eloquent/Model.html#method_save

Q2: save()不抛出异常

Q3:是的,见 Q2

Q4:因为saveOrFail()确实抛出异常,如果需要你可以处理

问题1)save()确实可以抛出异常。例如,如果您创建一个带有小数列的模型,例如'cost',并尝试在该列中保存一个字符串值,save()saveOrFail()都会抛出异常。演示:

>>> $item->cost = 'asdas';
>>> $item->save();
 Illuminate/Database/QueryException with message 'SQLSTATE[HY000]: General error: 1366 Incorrect decimal value: 'asdas' for column 'cost' at row 1 (SQL: update ...

问题2)通过source看,好像save()开火时只会return false savingupdatingcreating 事件 returns false.

由于您没有定义任何事件侦听器,理论上是的,您应该只接收 true 否则将抛出异常。

问题 3) 如果您没有监听事件,那么不,这是不可能的(至少不可能)。如果抛出异常,则不会保存。

问题 4)由于 saveOrFail() 只是将 save() 包装在一个事务中,它的用途是如果在事务期间出现任何异常,则保持数据库的一致性save() 函数。 saveOrFail() 确保如果在 save() 期间出现任何异常,则不会保存模型。如果抛出异常,仅 save() 无法保证模型不是 modified/saved。

由于您已经将代码包装在事务中,因此不需要使用 saveOrFail

对于您的用例,我能想到的最好的办法是在单个 if 表达式中对所有模型调用 save(),并且仅当表达式为 true 时才提交事务.这样你就可以同时满足 save() returns false 或引发异常的情况。像这样:

DB::beginTransaction();
try {
  $model1 = new Type();
  $model1->test = 'great';
  
  $model2 = new Type();
  $model2->test2 = 'awesome';
  
  if ($model1->save() && $model2->save()) {
    DB::commit();
  } else {
    DB::rollBack();
  }
} catch(Exception $e){
  DB::rollBack();
}

如果您想要正确的错误处理,请使用 saveOrFail(),它有异常错误消息。 save() 只是 returns 一个 false 布尔值。

补充 问题 4 的答案:大多数用户不需要 saveOrFail()save() 已经抛出异常,saveOrFail() 只是在新事务中调用 save()

我认为该方法的名称具有误导性。其他 Eloquent 方法如 find 不会抛出异常,但有一个 findOrFail 方法会抛出异常。 savesaveOrFail 的情况并非如此,在此处的代码中可见:https://github.com/laravel/framework/blob/5.8/src/Illuminate/Database/Eloquent/Model.php#L693

    public function saveOrFail(array $options = [])
    {
        return $this->getConnection()->transaction(function () use ($options) {
            return $this->save($options);
        });
    }

saveOrFail() 的主要用途是如果您想从失败的数据库端更新或插入中恢复。如果我们以下面的模式为例:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class SaveOrFailDemo extends Migration
{
    public function up()
    {
        Schema::create('save_or_fails', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->string('word')->unique();
        });
    }

    public function down()
    {
        Schema::dropIfExists('save_or_fails');
    }
}

使用这个基本模型:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class SaveOrFail extends Model
{
}

和这个测试命令:

<?php

namespace App\Console\Commands;

use App\SaveOrFail;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;

class TrySaveOrFail extends Command
{
    protected $signature = 'try:save-or-fail {--fail}';

    public function handle()
    {
        DB::transaction(function () {
            // cleanup old tries
            SaveOrFail::query()->delete();

            $model = new SaveOrFail();

            $saveFunction = $this->option('fail')
                ? fn () => $model->saveOrFail()
                : fn () => $model->save();

            $maxSaveAttempts = 3;
            $word = 'demo';

            // force a UNIQUE violation to occur
            $duplicate = new SaveOrFail();
            $duplicate->word = $word . '0';
            $duplicate->save();

            for ($currentSaveAttempt = 0; $currentSaveAttempt < $maxSaveAttempts; $currentSaveAttempt++) {
                $model->word = $word . $currentSaveAttempt;

                try {
                    $saveFunction();
                    $this->info("Saved! {$model->word}");
                    return $model;
                } catch (\PDOException $ex) {
                    $this->warn($ex->getMessage());
                }
            }
        });
    }
}

运行 php artisan try:save-or-fail 使用 Postgres 永远不会保存 $model。第一个 UNIQUE 违规导致事务进入中止状态:

application@f72fb45f3bfa:/app$ php artisan try:save-or-fail
SQLSTATE[23505]: Unique violation: 7 ERROR:  duplicate key value violates unique constraint "save_or_fails_word_unique"
DETAIL:  Key (word)=(demo0) already exists. (SQL: insert into "save_or_fails" ("word", "updated_at", "created_at") values (demo0, 2021-06-07 19:39:24, 2021-06-07 19:39:24) returning "id")
SQLSTATE[25P02]: In failed sql transaction: 7 ERROR:  current transaction is aborted, commands ignored until end of transaction block (SQL: insert into "save_or_fails" ("word", "updated_at", "created_at") values (demo1, 2021-06-07 19:39:24, 2021-06-07 19:39:24) returning "id")
SQLSTATE[25P02]: In failed sql transaction: 7 ERROR:  current transaction is aborted, commands ignored until end of transaction block (SQL: insert into "save_or_fails" ("word", "updated_at", "created_at") values (demo2, 2021-06-07 19:39:24, 2021-06-07 19:39:24) returning "id")

但是,由于 saveOrFail() 在其自己的事务中运行,我们能够从中恢复:

application@f72fb45f3bfa:/app$ php artisan try:save-or-fail --fail
SQLSTATE[23505]: Unique violation: 7 ERROR:  duplicate key value violates unique constraint "save_or_fails_word_unique"
DETAIL:  Key (word)=(demo0) already exists. (SQL: insert into "save_or_fails" ("word", "updated_at", "created_at") values (demo0, 2021-06-07 19:40:16, 2021-06-07 19:40:16) returning "id")
Saved! demo1

除非您预料到您的数据库操作可能会失败并且您会从中恢复,否则使用 saveOrFail() 没有多大意义。