检查是否存在AboutToMany关系-Laravel


74

我的两个表(客户和产品)使用Laravel的blongToMany和数据透视表建立了ManyToMany关系。现在,我要检查某个客户是否具有某个产品。

我可以创建一个模型来检查数据透视表,但是由于Laravel不需要将此模型用于belongsToMany方法,所以我想知道是否有另一种方法可以在没有数据透视表模型的情况下检查是否存在某种关系。

Answers:


187

我认为这样做的官方方法是:

$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;

1
+1。这是官方的方式,应标记为最佳答案。
2014年

1
@DavidMIRV您是否从上层代码示例中得到该错误?如果是这样,您确定要将此关系称为属性吗?如果您将其作为方法调用(即$exists = $client->products()->contains($product_id);)执行,则确实会说contains上不存在BelongsToMany。但是,如果你把它作为一个属性,BelongsToMany被悄悄转化为集合,它确实contains()
alexrussell 2015年

是的,我已解决它,我不记得确切的问题是什么,但我想我不知道
:::

1
@GustavoStraube此方法有效,但是它将只是检查关系是否存在而将大量数据加载到内存中。
Alexey Mezenin

1
@AlexeyMezenin是的,我同意你的观点。就我而言,无论如何,我已经在加载相关模型。因此,不会加载额外的数据。无论如何,感谢您的见解。它可能会帮助其他人获得此答案。
Gustavo Straube

31

这个问题已经很老了,但这可能会帮助其他人寻找解决方案:

$client = Client::find(1);
$exists = $client->products()->where('products.id', $productId)->exists();

没有@alexrussell解决方案中的“浪费”,查询也更加有效。


1
SQLSTATE[42000]: Syntax error or access violation: 1066 Not unique table/alias:当我在模型中尝试时出现错误:/

@George然后尝试where('products.id', $productId)
Mouagip

不要以为您需要表别名,id足够。这取决于产品关系。最清洁的解决方案沙发恕我直言。
约翰(Johan)

@Johan如果例如client也具有ID,则需要别名。这很普遍。
Mouagip

此解决方案工作更稳定。我在使用$ client-> products-> contains($ product_id);迭代得太快时遇到问题;
ALeX inSide

15

Alex的解决方案正在工作,但它将把Client模型和所有相关Product模型从DB加载到内存中,然后,它将检查该关系是否存在。

更好的口才是使用whereHas()method。

1.您无需加载客户端模型,只需使用其ID。

2.您也不需要像Alex一样将与该客户端相关的所有产品加载到内存中。

3.对数据库的一个SQL查询。

$doesClientHaveProduct = Product::where('id', $productId)
    ->whereHas('clients', function($q) use($clientId) {
        $q->where('id', $clientId);
    })
    ->count();

9

更新:我没有考虑检查多重关系的有用性,如果是这种情况,那么@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()
        );
    }

您可以将其放在用户/客户端模型中,也可以将其放在ClientRepository中,并在需要时使用它。

if ($this->clientRepository->clientHasProduct($clientId, $productId)
{
    return 'Awesome';
}

但是,如果您已经在Client Eloquent模型上定义了EmiratesToMany关系,则可以在Client模型内部执行此操作:

    return ! is_null(
        $this->products()
             ->where('product_id', $productId)
             ->first()
    );

7

@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个查询,然后使用数组。


最简单的方法是使用contains@alexrussell建议的方法。

我认为这是优先考虑的问题,因此,除非您的应用程序很大且需要大量优化,否则您可以选择更易于使用的内容。


@deczo很好的回答,并使用Laravel内置帮助程序使键/表的查询动态化-我倾向于为此而努力,但这将它带入了一个新的高度!:D
alexrussell 2014年

2
谢谢,仍然需要在Eloquent上发挥很多作用,以便了解其中哪些方法仅返回键,col_id以及哪些合格/前缀table.col_id。不幸的是,口才经常缺乏一致性。
Jarek Tkaczyk 2014年

1
是的-我必须说,在尽可能多地尊重Taylor的情况下,您对Laravel的研究越深入,就会发现烦人的不一致之处和不太清晰的代码(并且没有可用的注释/文档)。但是,嘿,这不是我能做得更好!
alexrussell 2014年

5

大家好)我针对这个问题的解决方案:我创建了一个自Eloquent扩展的类,并从中扩展了我的所有模型。在这一节课中,我编写了这个简单的函数:

function have($relation_name, $id) {
    return (bool) $this->$relation_name()->where('id','=',$id)->count();
}

要检查现有关系,您必须编写类似以下内容的内容:

if ($user->have('subscribes', 15)) {
    // do some things
}

这种方式只生成一个SELECT count(...)查询,而不从表中接收实际数据。


3

使用特征:

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...
}

不能解决我的问题,但可以帮助我走上正确的道路,谢谢。
phaberest

1

要检查2个模型之间是否存在关系,我们需要的是对数据透视表的单个查询,没有任何联接。

您可以使用内置newPivotStatementForId方法来实现:

$exists = $client->products()->newPivotStatementForId($product->id)->exists();

1

这有时间,但也许我可以帮助某人

if($client->products()->find($product->id)){
 exists!!
}

请注意,您必须拥有产品和客户模型,希望对您有所帮助,


是的,对我有帮助。它返回一个相关模型,或者如果标识符未找到该模型,则返回null。
АнтонБаранов
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.