81
votes

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.

9

9 Answers

204
votes

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;
37
votes

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.

17
votes

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();
9
votes

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()
    );
7
votes

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

5
votes

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.

3
votes

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

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();
1
votes

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,