我的两个表(客户和产品)使用Laravel的blongToMany和一个中间表建立了多对多关系。
现在我想检查一个特定的客户是否拥有一个特定的产品。
我可以创建一个模型来在中间表中进行检查,但由于Laravel不需要此模型来进行belongsToMany方法,因此我想知道是否有其他方法可以检查某个关系是否存在而无需为中间表创建模型。
我认为官方的做法是这样的:
$client = Client::find(1);
$exists = $client->products->contains($product_id);
这种方法有些浪费,因为它会执行SELECT
查询,将所有结果放入Collection
中,最后通过foreach
遍历Collection
以找到您传入的ID对应的模型。然而,它不需要对枢轴表进行建模。
如果您不喜欢这种浪费,您可以在SQL/Query Builder中自己完成,这也不需要对表进行建模(如果您没有其他目的获取Client
模型,则无需获取它):
$exists = DB::table('client_product')
->whereClientId($client_id)
->whereProductId($product_id)
->count() > 0;
这个问题非常老,但是这可能会对其他正在寻找解决方案的人有所帮助:
$client = Client::find(1);
$exists = $client->products()->where('products.id', $productId)->exists();
没有像 @alexrussell 的解决方案中那样的“浪费”,查询也更有效率。
SQLSTATE[42000]: Syntax error or access violation: 1066 Not unique table/alias:
。 - user4748790where('products.id', $productId)
。 - MouagipAlex的解决方案是可行的,但它会将Client
模型和所有相关的Product
模型从DB加载到内存中,并在这之后才检查关系是否存在。
更好的Eloquent方法是使用whereHas()
方法。
1. 您不需要加载客户端模型,只需使用其ID即可。
2. 您也不需要像Alex一样将与该客户相关的所有产品加载到内存中。
3. 一个SQL查询到DB。
$doesClientHaveProduct = Product::where('id', $productId)
->whereHas('clients', function($q) use($clientId) {
$q->where('id', $clientId);
})
->count();
更新:我没有考虑到检查多个关系的有用性,如果是这种情况,那么@deczo对这个问题有一个更好的答案。只运行一个查询来检查所有关系是期望的解决方案。
/**
* Determine if a Client has a specific Product
* @param $clientId
* @param $productId
* @return bool
*/
public function clientHasProduct($clientId, $productId)
{
return ! is_null(
DB::table('client_product')
->where('client_id', $clientId)
->where('product_id', $productId)
->first()
);
}
您可以将此放入您的User/Client模型中,或者将其放入ClientRepository中,并在需要它的任何地方使用它。if ($this->clientRepository->clientHasProduct($clientId, $productId)
{
return 'Awesome';
}
但是如果您已经在客户端Eloquent模型上定义了belongsToMany关系,则可以在Client模型中执行以下操作:
return ! is_null(
$this->products()
->where('product_id', $productId)
->first()
);
@nielsiano的方法可以实现,但在我看来,这会为每个用户/产品对查询数据库,这是一种浪费。
如果您不想加载所有相关模型的数据,那么针对单个用户,我会这样做:
// User model
protected $productIds = null;
public function getProductsIdsAttribute()
{
if (is_null($this->productsIds) $this->loadProductsIds();
return $this->productsIds;
}
public function loadProductsIds()
{
$this->productsIds = DB::table($this->products()->getTable())
->where($this->products()->getForeignKey(), $this->getKey())
->lists($this->products()->getOtherKey());
return $this;
}
public function hasProduct($id)
{
return in_array($id, $this->productsIds);
}
$user = User::first();
$user->hasProduct($someId); // true / false
// or
Auth::user()->hasProduct($someId);
只执行1个查询,然后您就可以使用数组。
最简单的方法是像@alexrussell建议的那样使用contains
。
我认为这是一个偏好问题,所以除非您的应用程序很大并且需要大量优化,否则您可以选择您觉得更容易使用的方式。
大家好) 我对这个问题的解决方案是: 我创建了一个自己的类,继承自 Eloquent,并将所有我的模型都从它继承。在这个类中,我编写了这个简单的函数:
function have($relation_name, $id) {
return (bool) $this->$relation_name()->where('id','=',$id)->count();
}
if ($user->have('subscribes', 15)) {
// do some things
}
要检查两个模型之间关系的存在性,我们只需要针对中间表进行单个查询,而无需进行任何联接操作。
您可以使用内置的newPivotStatementForId
方法来实现:
$exists = $client->products()->newPivotStatementForId($product->id)->exists();
$product->id
属性?在我的情况下,我想获取所有至少有一个产品的客户,因为我先前不知道$product->id
。 - Pathros使用trait:
trait hasPivotTrait
{
public function hasPivot($relation, $model)
{
return (bool) $this->{$relation}()->wherePivot($model->getForeignKey(), $model->{$model->getKeyName()})->count();
}
}
.
if ($user->hasPivot('tags', $tag)){
// do some things...
}
if($client->products()->find($product->id)){
exists!!
}
->find()
方法?在我的情况下,我想获取所有至少拥有一个产品的客户,因为我之前不知道$product->id
。 - Pathros
$exists = $client->products()->contains($product_id);
),那么它确实会说“BelongsToMany”上不存在contains
。 但是,如果您将其作为属性调用,则“BelongsToMany”将被静默转换为集合,其中确实具有“contains()”。 - alexrussell