almo
almo

Reputation: 6387

Check if belongsToMany relation exists - Laravel

Two of my tables (clients and products) have a ManyToMany relation using Laravel's blongToMany and a pivot table. Now I want to check if a certain client has a certain product.

I could create a model to check in the pivot table but since Laravel does not require this model for the belongsToMany method I was wondering if there is another way to check if a certain relationship exists without having a model for the pivot table.

Upvotes: 101

Views: 117679

Answers (9)

daniel tamayo
daniel tamayo

Reputation: 31

This has time but maybe I can help someone

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

It should be noted that you must have the product and customer model, I hope it helps,

Upvotes: 3

Maksim Ivanov
Maksim Ivanov

Reputation: 4331

To check the existence of a relationship between 2 models, all we need is a single query against the pivot table without any joins.

You can achieve it using the built-in newPivotStatementForId method:

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

Upvotes: 6

Alexey Mezenin
Alexey Mezenin

Reputation: 163978

Alex's solution is working one, but it will load a Client model and all related Product models from DB into memory and only after that, it will check if the relationship exists.

A better Eloquent way to do that is to use whereHas() method.

1. You don't need to load client model, you can just use his ID.

2. You also don't need to load all products related to that client into memory, like Alex does.

3. One SQL query to DB.

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

Upvotes: 20

Mouagip
Mouagip

Reputation: 5329

The question is quite old but this may help others looking for a solution:

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

No "wastefulness" as in @alexrussell's solution and the query is more efficient, too.

Upvotes: 55

ilvsx
ilvsx

Reputation: 41

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

Upvotes: 4

saggid
saggid

Reputation: 184

Hello all) My solution for this problem: i created a own class, extended from Eloquent, and extend all my models from it. In this class i written this simple function:

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

For make a check existing relation you must write something like:

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

This way generates only a SELECT count(...) query without receiving real data from tables.

Upvotes: 7

nielsstampe
nielsstampe

Reputation: 1364

Update: I did not take into account the usefulness of checking multiple relations, if that is the case then @deczo has a way better answer to this question. Running only one query to check for all relations is the desired solution.

    /**
     * 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()
        );
    }

You could put this in you User/Client model or you could have it in a ClientRepository and use that wherever you need it.

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

But if you already have defined the belongsToMany relationship on a Client Eloquent model, you could do this, inside your Client model, instead:

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

Upvotes: 10

Jarek Tkaczyk
Jarek Tkaczyk

Reputation: 81187

@nielsiano's methods will work, but they will query DB for every user/product pair, which is a waste in my opinion.

If you don't want to load all the related models' data, then this is what I would do for a single user:

// 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);
}

Then you can simply do this:

$user = User::first();
$user->hasProduct($someId); // true / false

// or
Auth::user()->hasProduct($someId);

Only 1 query is executed, then you work with the array.


The easiest way would be using contains like @alexrussell suggested.

I think this is a matter of preference, so unless your app is quite big and requires a lot of optimization, you can choose what you find easier to work with.

Upvotes: 8

alexrussell
alexrussell

Reputation: 14232

I think the official way to do this is to do:

$client = Client::find(1);
$exists = $client->products->contains($product_id);

It's somewhat wasteful in that it'll do the SELECT query, get all results into a Collection and then finally do a foreach over the Collection to find a model with the ID you pass in. However, it doesn't require modelling the pivot table.

If you don't like the wastefulness of that, you could do it yourself in SQL/Query Builder, which also wouldn't require modelling the table (nor would it require getting the Client model if you don't already have it for other purposes:

$exists = DB::table('client_product')
    ->whereClientId($client_id)
    ->whereProductId($product_id)
    ->count() > 0;

Upvotes: 234

Related Questions