Überprüfen Sie, ob eine Beziehung zu ToMany besteht - Laravel

74

Zwei meiner Tabellen (Kunden und Produkte) haben eine ManyToMany-Beziehung mit Laravels blongToMany und einer Pivot-Tabelle. Jetzt möchte ich überprüfen, ob ein bestimmter Kunde ein bestimmtes Produkt hat.

Ich könnte ein Modell zum Einchecken in die Pivot-Tabelle erstellen, aber da Laravel dieses Modell für die Methode "Gehört zu ToMany" nicht benötigt, habe ich mich gefragt, ob es eine andere Möglichkeit gibt, zu überprüfen, ob eine bestimmte Beziehung besteht, ohne ein Modell für die Pivot-Tabelle zu haben.

Almo
quelle

Antworten:

183

Ich denke, der offizielle Weg, dies zu tun, ist:

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

Es ist insofern etwas verschwenderisch, als es die SELECTAbfrage ausführt, alle Ergebnisse in a umwandelt Collectionund schließlich ein foreachover the durchführt Collection, um ein Modell mit der von Ihnen übergebenen ID zu finden. Es ist jedoch nicht erforderlich, die Pivot-Tabelle zu modellieren.

Wenn Ihnen die Verschwendung davon nicht gefällt, können Sie dies selbst in SQL / Query Builder tun, wozu auch keine Modellierung der Tabelle erforderlich ist (und das ClientModell auch nicht abgerufen werden muss, wenn Sie es nicht bereits für andere Zwecke haben ::

$exists = DB::table('client_product')
    ->whereClientId($client_id)
    ->whereProductId($product_id)
    ->count() > 0;
alexrussell
quelle
1
+1. Dies ist der offizielle Weg und sollte als beste Antwort markiert werden.
Arda
1
@DavidMIRV erhalten Sie diesen Fehler aus dem oberen Codebeispiel? Wenn ja, sind Sie sicher, dass Sie die Beziehung als Attribut aufrufen? Wenn Sie dies als Methodenaufruf (dh $exists = $client->products()->contains($product_id);) tun, wird tatsächlich angegeben, dass containsdies am nicht vorhanden ist BelongsToMany. Wenn Sie es jedoch als Attribut aufrufen, wird BelongsToMany stillschweigend in eine Sammlung konvertiert, die dies hat contains().
Alexrussell
Ja, ich habe es behoben. Ich kann mich nicht genau erinnern, was das Problem war, aber ich glaube, ich habe es vermisst :: mit
DavidMIRV
1
@GustavoStraube Diese Methode funktioniert, lädt jedoch viele Daten in den Speicher, um die Existenz einer Beziehung zu überprüfen.
Alexey Mezenin
1
@AlexeyMezenin ja, ich stimme dir zu. In meinem Fall lade ich die zugehörigen Modelle ohnehin schon. Es werden also keine zusätzlichen Daten geladen. Trotzdem danke für den Einblick. Es wird wahrscheinlich anderen Menschen helfen, diese Antwort zu erreichen.
Gustavo Straube
29

Die Frage ist ziemlich alt, aber dies kann anderen helfen, nach einer Lösung zu suchen:

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

Keine "Verschwendung" wie in der Lösung von @ alexrussell und die Abfrage ist auch effizienter.

Mouagip
quelle
1
Ich erhalte eine Fehlermeldung, SQLSTATE[42000]: Syntax error or access violation: 1066 Not unique table/alias:wenn ich das in meinem Modell versuche: /
@ George Dann versuchen Sie es where('products.id', $productId).
Mouagip
Ich glaube nicht, dass Sie dort einen Tabellenalias benötigen. Es ist genug. Dies wird in der Produktbeziehung festgelegt. Sauberste Lösung sofar imho.
Johan
@Johan Du brauchst den Alias ​​wenn zB clientauch eine ID hat. Das ist ziemlich häufig.
Mouagip
Diese Lösung arbeitet stabiler. Ich hatte ein Problem, als ich mit $ client-> products-> includes ($ product_id) zu schnell iterierte.
ALeX inSide
13

Alex 'Lösung funktioniert, aber sie lädt ein ClientModell und alle damit verbundenenProduct Modelle aus der Datenbank in den Speicher und prüft erst danach, ob die Beziehung besteht.

Ein besserer eloquenter Weg, dies zu tun, ist die Verwendung einer whereHas()Methode.

1. Sie müssen das Client-Modell nicht laden, sondern können nur seine ID verwenden.

2. Sie müssen auch nicht alle mit diesem Client verbundenen Produkte in den Speicher laden, wie dies bei Alex der Fall ist.

3. Eine SQL-Abfrage an DB.

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

Update: Ich habe die Nützlichkeit der Überprüfung mehrerer Beziehungen nicht berücksichtigt. Wenn dies der Fall ist, hat @deczo eine bessere Antwort auf diese Frage. Es ist die gewünschte Lösung, nur eine Abfrage auszuführen, um alle Beziehungen zu überprüfen.

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

Sie können dies in Ihr Benutzer- / Client-Modell einfügen oder in einem Client-Repository speichern und dort verwenden, wo Sie es benötigen.

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

Wenn Sie die Zugehörigkeit zu ToMany bereits in einem Client Eloquent-Modell definiert haben, können Sie dies stattdessen in Ihrem Client-Modell tun:

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

Die Methoden von @ nielsiano werden funktionieren, aber sie werden die Datenbank für jedes Benutzer / Produkt-Paar abfragen, was meiner Meinung nach eine Verschwendung ist.

Wenn Sie nicht alle Daten der zugehörigen Modelle laden möchten, würde ich Folgendes für einen einzelnen Benutzer tun :

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

Dann können Sie einfach Folgendes tun:

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

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

Es wird nur 1 Abfrage ausgeführt, dann arbeiten Sie mit dem Array.


Der einfachste Weg wäre die Verwendung von contains@alexrussell.

Ich denke, dies ist eine Frage der Präferenz. Wenn Ihre App nicht sehr groß ist und viel Optimierung erfordert, können Sie auswählen, mit was Sie leichter arbeiten können.

Jarek Tkaczyk
quelle
Tolle Antwort @deczo und erstaunliche Verwendung der in Laravel integrierten Helfer, um die Abfrage in Bezug auf Schlüssel / Tabellen dynamisch zu gestalten - ich bemühe mich selbst darum, aber dies bringt es auf eine neue Ebene! : D
alexrussell
2
Vielen Dank, man muss immer noch viel mit Eloquent spielen, um zu lernen, welche dieser Methoden nur einen Schlüssel zurückgeben col_idund welche qualifiziert / vorangestellt sind table.col_id. Leider fehlt Eloquent ziemlich oft die Konsistenz.
Jarek Tkaczyk
1
Ja - ich muss sagen, mit so viel Respekt vor Taylor wie möglich, je mehr Sie sich mit Laravel beschäftigen, desto mehr finden Sie irritierende Inkonsistenzen und Code, der nicht allzu klar ist (und ohne brauchbare Kommentare / Dokumentation). Aber hey, ich könnte es nicht besser machen!
Alexrussell
5

Hallo an alle) Meine Lösung für dieses Problem: Ich habe eine eigene Klasse erstellt, die von Eloquent erweitert wurde, und alle meine Modelle daraus erweitert. In dieser Klasse habe ich diese einfache Funktion geschrieben:

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

Um eine bestehende Beziehung zu überprüfen, müssen Sie Folgendes schreiben:

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

Auf diese Weise wird nur eine SELECT-Zählabfrage (...) generiert, ohne dass echte Daten aus Tabellen empfangen werden.

saggid
quelle
3

Verwenden Sie das Merkmal:

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...
}
ilvsx
quelle
Ich habe mein Problem nicht gelöst, aber mir geholfen, den richtigen Weg zu finden, danke.
Phaberest
1

Um das Vorhandensein einer Beziehung zwischen zwei Modellen zu überprüfen, benötigen wir lediglich eine einzelne Abfrage für die Pivot-Tabelle ohne Verknüpfungen.

Sie können dies mit der integrierten newPivotStatementForIdMethode erreichen:

$exists = $client->products()->newPivotStatementForId($product->id)->exists();
Maksim Ivanov
quelle
1

Das hat Zeit, aber vielleicht kann ich jemandem helfen

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

Es sollte beachtet werden, dass Sie das Produkt- und Kundenmodell haben müssen, ich hoffe es hilft,

Daniel Tamayo
quelle
Ja, das hilft mir. Es gibt ein verwandtes Modell oder null zurück, wenn das Modell nicht vom Bezeichner gefunden wird.
Баранов