web-dev-qa-db-fra.com

Vérifiez si la relation d’appartient existe - Laravel

Deux de mes tables (clients et produits) ont une relation ManyToMany utilisant blongToMany de Laravel et un tableau croisé dynamique. Maintenant, je veux vérifier si un certain client a un certain produit.

Je pouvais créer un modèle à vérifier dans le tableau croisé dynamique, mais depuis Laravel ne nécessite pas ce modèle pour la méthode ownToMany, je me demandais s’il existait un autre moyen de vérifier si une relation donnée existait sans avoir modèle pour le tableau croisé dynamique.

54
almo

Je pense que la façon officielle de faire est de faire:

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

C’est un peu inutile de faire la requête SELECT, d’obtenir tous les résultats dans un Collection, puis de faire un foreach sur le Collection pour trouver un modèle avec l'ID que vous transmettez. Cependant, il n'est pas nécessaire de modéliser le tableau croisé dynamique.

Si vous n'aimez pas ce gaspillage, vous pouvez le faire vous-même dans SQL/Query Builder, ce qui n'exigerait pas non plus la modélisation de la table (ni le modèle Client si vous ne l'aimiez pas. l'avez déjà à d'autres fins:

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

La question est assez ancienne mais cela peut aider les autres à la recherche d'une solution:

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

Pas de "gaspillage" comme dans la solution de @ alexrussell et la requête est également plus efficace.

16
Mouagip

La solution d'Alex fonctionne, mais elle va charger en mémoire un modèle Client et tous les modèles Product associés à partir de la base de données et ensuite uniquement vérifier si la relation existe.

Une meilleure façon de faire cela consiste à utiliser la méthode whereHas() .

1. Vous n'avez pas besoin de charger le modèle client, vous pouvez simplement utiliser son ID.

2. Vous n'avez pas non plus besoin de charger en mémoire tous les produits associés à ce client, comme le fait Alex.

3. Une requête SQL vers la base de données.

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

Mise à jour: Je n'ai pas pris en compte l'utilité de vérifier plusieurs relations. Si tel est le cas, @deczo a une meilleure façon de répondre à cette question. Exécuter une seule requête pour vérifier toutes les relations est la solution souhaitée.

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

Vous pouvez l'insérer dans votre modèle utilisateur/client ou le placer dans un référentiel ClientRepository et l'utiliser là où vous en avez besoin.

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

Toutefois, si vous avez déjà défini la relation de type apartToMany sur un modèle Client Eloquent, vous pouvez le faire, dans votre modèle Client, à la place:

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

Les méthodes de @ nielsiano fonctionneront, mais elles interrogeront DB pour chaque paire utilisateur/produit, ce qui est un gaspillage à mon avis.

Si vous ne voulez pas charger toutes les données des modèles associés, voici ce que je ferais pour un tilisateur unique:

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

Ensuite, vous pouvez simplement faire ceci:

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

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

Une seule requête est exécutée, puis vous travaillez avec le tableau.


Le moyen le plus simple serait d'utiliser contains comme l'a suggéré @alexrussell.

Je pense que c'est une question de préférence. Ainsi, à moins que votre application ne soit assez grosse et nécessite beaucoup d'optimisation, vous pouvez choisir ce que vous trouvez plus facile à utiliser.

6
Jarek Tkaczyk

Bonjour à tous) Ma solution à ce problème: j’ai créé une classe propre, étendue à partir d’Eloquent, et que j’étends tous mes modèles. Dans cette classe, j'ai écrit cette fonction simple:

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

Pour vérifier une relation existante, vous devez écrire quelque chose comme:

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

Cette méthode ne génère qu'une requête SELECT count (...) sans recevoir de données réelles des tables.

5
saggid

utiliser le 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...
}
1
ilvsx