【问题标题】:Eloquent ORM - Why does Laravel not think these two objects are the same?Eloquent ORM - 为什么 Laravel 认为这两个对象不一样?
【发布时间】:2019-01-12 03:59:27
【问题描述】:

我对 Laravel 还是很陌生。在我在两个模型之间able to get multiple Eloquent relationships defined 之后,我注意到可以将实例传递给它不属于的父模型实例中的方法。所以我认为在方法本身中强制执行这种关系可能会很好。

在Organization.php中:

/**
 * @param \App\Contact The Contact to delete
 */
public function deleteContact(Contact $contact)
{
    if($contact->organization == $this)
    {
        $this->contacts->find($contact->id)->delete();
    }
    else
    {
        dd('Error: Contact ' . $contact->fname . ' ' . $contact->lname . ' #' . $contact->id . ' does not belong to Organization ' . $this->name . ' #' . $this->id);
    }
}

但是,在传递实际属于的联系人时,我不断收到自定义错误消息,或者至少我认为他们确实如此!当我在tinker 中测试关系时,它为 Bob 和 Bob 的组织返回了不同的实例 ids。

>>> $org = Organization::find(17)
=> App\Organization {#2929
     id: 17,
     name: "Test Org",
     primary_contact_id: 33,
   }
>>> $org->contacts
=> Illuminate\Database\Eloquent\Collection {#2925
     all: [
       App\Contact {#2926
         id: 33,
         organization_id: 17,
         fname: "Alice",
         lname: "Abbot",
       },
       App\Contact {#2943
         id: 34,
         organization_id: 17,
         fname: "Bob",
         lname: "Baker",
       },
     ],
   }
>>> $bob = Contact::find(34)
=> App\Contact {#2940
     id: 34,
     organization_id: 17,
     fname: "Bob",
     lname: "Baker",
   }
>>> $bob->organization == $org
=> false
>>> $bob->organization
=> App\Organization {#2932
     id: 17,
     name: "Test Org",
     primary_contact_id: 33,
   }

显然它是在比较两个不同的实例。为了解决这个问题,我不得不将条件更改为$contact->organization->id == $this->id,这很有效。但是,这样做的 Eloquent 要少得多。

  1. 为什么会生成两个不同的实例?这些是后续调用,应该(理论上)产生相同的实例(或者我认为)。

  2. 这种额外的保护甚至是必要的吗?由于内部代码,我们至少受到了部分保护,但我不能用try/catch 包围并优雅地处理违规行为——我需要先测试,然后尝试删除。 (如果传递的Contact 不是Organization 的有效相关实例,find() 返回null,导致 PHP 错误而不是异常。)有没有更好的方法来做到这一点,或者我我想多了?

【问题讨论】:

  • 为什么不使用$bob->organization_id == $this->id 之类的组织ID 进行比较?无需从数据库中加载组织只是为了比较它的 ID。
  • 你可以试试这个吗? $bob->organization->is($org);
  • Eloquent 不会在您每次查询时提供相同的模型实例。只要确保你明白这一点。就像调用find 两次不会返回相同的内存一样,调用find 不会返回与通过关系检索相同资源时相同的内存。
  • @Devon 它可能是一个不同的实例,但是如果两个模型具有相同的值并且属于同一类,则 == 比较运算符将返回 true。见php.net/manual/en/language.oop5.object-comparison.php。您正在考虑 === 标识运算符,它需要相同的实例。
  • @NMahurin 是的,我知道,但由于模型中的数据不同,它不可靠。除非它是 stdClass 对象,否则我会谨慎使用 ==。但我上面的评论是关于他关于生成不同实例的问题。

标签: php laravel


【解决方案1】:

从 Laravel 的角度来看,我们不能仅仅依靠相等运算符来比较对象,这在 Eloquent 模型的情况下可能非常不可靠且难以比较。

正如@Devon 和@N Mahurin 所说,如果您要比较更可预测的stdClass 对象,则使用相等运算符比较对象会很有效。

这在过去是一场斗争。幸运的是,这个PR 在 5.3 中成为了 Laravel 的核心。

要知道两个模型是否本质上相同,Laravel 会检查是否

  1. 主键相同
  2. 表是一样的
  3. 数据库连接相同。

基于此,我们可以假设我们正在比较两个相等的行。因此,在您的情况下,以下应该有效:

$bob->organization->is($org);

型号比较请参考documentation

【讨论】:

    【解决方案2】:

    必须对第一个问题进行一些测试。似乎当您在模型内部调用关系时,该模型实例的关系数组充满了该关系。你的$this->contacts->find($contact->id)->delete(); 导致了这个。即使您在比较之后调用了关系。我对这种行为感到有些惊讶,但似乎确实如此。由于两个模型实例现在包含不同的值(一个具有填充的关系数组),因此比较失败。不过有一个新的 is() 方法,请参阅https://www.neontsunami.com/posts/comparing-model-equality-in-laravel。这可能对您有所帮助。

    有几种不同的方法来处理第二个问题。我个人只是在已经使用的if/else 中汇总测试,如下所示:

    if(($contact->organization == $this) && ($this->contacts->find($contact->id)?? false))
    

    使用 ??如果您的 find() 返回 null,则运算符将返回 false。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多