【问题标题】:laravel - save vs saveorfail (real difference in nutshell)laravel - save vs saveorfail (简而言之,真正的区别)
【发布时间】:2019-12-21 00:06:11
【问题描述】:

我终于明白这个概念了,因为我仍然没有得到一些案例。

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

问题 2) 我没有使用任何事件模型。所以我认为save() 不会在任何时候返回false。那么它会返回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()

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

【问题讨论】:

  • 源代码包含 cmets 应该可以回答您的大部分问题:github.com/laravel/framework/blob/…
  • 我知道,但它没有说明它何时抛出异常。如果有经验的人能回答我的问题,那就太好了。谢谢。

标签: laravel laravel-5 eloquent laravel-4


【解决方案1】:

添加到https://stackoverflow.com/a/57495773 并回答 Q4:大多数用户不需要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());
                }
            }
        });
    }
}

使用 Postgres 运行 php artisan try:save-or-fail 永远不会保存 $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() 没有多大意义。

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • save() 也会抛出异常。 saveOrFail() 只是在事务中调用 save() 并且不添加任何额外的错误处理,如下代码所示:github.com/laravel/framework/blob/5.8/src/Illuminate/Database/…
    • 因此 save() 与 saveOrFail() 不同。失败的附加(包装)方法抛出异常,保存(不同的方法)返回一个值。
    【解决方案3】:

    问题 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() 在触发savingupdatingcreating 事件返回@ 时只会返回false 987654331@.

    由于您尚未定义任何事件侦听器,理论上是的,您应该只收到true 否则将引发异常。

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

    问题 4) 因为saveOrFail() 只是将save() 包装在事务中,所以它的用途是在save() 函数期间引发任何异常时保持数据库的一致性。 saveOrFail() 确保如果在 save() 期间引发任何异常,则不会保存模型。如果抛出异常,单独save() 无法保证模型没有被修改/保存。

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

    对于您的用例,我能想到的最好方法是在单个 if 表达式中对所有模型调用 save(),并且仅当表达式为 true 时才提交事务。这样,当save() 返回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();
    }
    

    【讨论】:

    • "2. 模型已经存在并且不是“脏”(即没有被修改)。”它仍然返回 true . if (count($dirty) &gt; 0) {..} return true;
    • 不,我喜欢你的解释。我自己查看了官方代码。你似乎是对的。我只是不明白第四个答案。 save 或 saveOrFail 之间的区别,因为如果出现问题,save 也不会工作,并且不会提交事务。每个单独的 save() 都是自动提交的事务,还记得在 mysql 中吗?
    • 我认为,例如,如果finishSave 方法可能引发异常,该方法在模型已提交到数据库之后运行(在performUpdateperformInsert 中)。然后您将处理一个异常,但不能确定模型是否已更新。
    【解决方案4】:

    Q1save() 将始终返回布尔值来源https://laravel.com/api/5.8/Illuminate/Database/Eloquent/Model.html#method_save

    Q2save()不会抛出异常

    Q3:是的,见 Q2

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

    【讨论】:

    • 非常感谢。一点点,我准备好了。 1)所以如果对于 save(),在 db 端发生了一些事情,laravel 将处理该数据库的异常,并且仍然会为我们返回布尔值,对吗? 2)如果 saveorfail 返回布尔值或抛出异常,我是否需要 try catch 块和 if 语句进行检查?
    • 1) 是 2) 是的,您可以使用 try catch 捕获异常,但是异常是针对异常行为的,不应成为您正常应用程序流程的一部分(作为最佳实践)
    • 2) 是的,但想象一下我有交易,其中我想保存 3 个模型。所以我对不同的模型使用了 saveOrFail() 3 次。由于事务,我得到了回滚的 catch 块。现在想象一下,第二个 saveOrFail() 没有抛出异常,只是返回布尔值,然后我的事务失去了意义,因为回滚不会发生。这是否意味着我还必须为 saveOrFail 编写 3 个 if else 语句并尝试 catch 块进行事务处理?
    • 是的,但这并不能回答我们的问题。我相信你也对我问的问题感到好奇。我想了解它。 Laravel 没有提及我刚才所说的那个场景。
    • 这是不正确的,save() 确实抛出异常。试试这个:(new User(['id' =&gt; 'hello']))-&gt;save();。你会得到一个Illuminate\Database\QueryException
    猜你喜欢
    • 1970-01-01
    • 2020-11-13
    • 1970-01-01
    • 1970-01-01
    • 2015-05-28
    • 2012-11-21
    • 2012-07-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多