web-dev-qa-db-fra.com

Laravel, Comment utiliser les conditions where pour la colonne de relation?

J'utilise Laravel et j'ai un petit problème avec Eloquent ORM .. Je peux faire fonctionner cela simplement avec une requête SQL en utilisant un JOIN mais je n'arrive pas à le faire fonctionner avec Eloquent!

C'est ce que je veux, j'ai deux tableaux. l'un est "Restaurants" et l'autre est "Restaurant_Facilities".

Les tableaux sont simples .. et les relations un-à-un. comme il y a une table restaurant avec id, name, slug, etc. et une autre table appelée restaurant_facilities avec id, restaurant_id, wifi, parking, etc.

Maintenant, ce que je veux faire, c'est .. charger tous les restaurants qui ont wifi = 1 ou wifi = 0 .. Comment puis-je le faire avec Eloquent? J'ai essayé le chargement, les tableaux croisés dynamiques, avec (), collections () et rien ne semble fonctionner!

Le même problème que j'ai pour une relation plusieurs-à-plusieurs pour cuisines! J'ai la même table restaurant et une table cuisine et une table restaurant_cuisine_connection ..

mais comment puis-je charger tous les restaurants dans une cuisine spécifique en utilisant son ID?

Cela marche.

Cuisine::find(6)->restaurants()->get();

mais je veux charger ceci depuis Restaurant :: modèle pas depuis les cuisines .. parce que j'ai beaucoup de conditions enchaînées ensemble .. c'est pour une page de recherche et de filtrage/navigation.

Des idées ou des moyens? Je me bats avec ça depuis 3 jours et toujours pas de réponse.

Exemples de modèles:

class Restaurant extends Eloquent {

    protected $table = 'restaurants';

    public function facilities() {
        return $this->hasOne('Facilities'); 
    }
}

class Facilities extends Eloquent {

    protected $table = 'restaurants_facilities';

    public function restaurant() {
        return $this->belongsTo('Restaurant');
    }

}

PS: Cela semble fonctionner .. mais ce n'est pas un moyen éloquent non?

Restaurant::leftJoin(
                'cuisine_restaurant', 
                'cuisine_restaurant.restaurant_id', 
                '=', 'restaurants.id'
             )
             ->where('cuisine_id', 16)
               ->get();

Quelle est également la meilleure méthode pour trouver un nombre de restaurants qui ont une valeur de colonne spécifique sans autre requête? comme .. je dois trouver le total des restaurants qui ont un parking = 1 et wifi = 1?

Aidez-moi à ce sujet.

Merci.

33
Tharshan Venkdesan

Je ne vois rien de mal à faire le joint gauche ici, si vous devez charger à partir du modèle Restaurant. Je pourrais le résumer à une méthode sur mon modèle de restaurant, comme ceci:

class Restaurant extends Eloquent {
    protected $table = 'restaurants'; // will be default in latest L4 beta

    public function facility()
    {
      return $this->hasOne('Facility');
    }

    // Or, better, make public, and inject instance to controller.
    public static function withWifi()
    {
      return static::leftJoin(
        'restaurant_facilities',
        'restaurants.id', '=', 'restaurant_facilities.restaurant_id'
      )->where('wifi', '=', 1);
    }
}

Et puis, depuis vos itinéraires:

Route::get('/', function()
{
  return Restaurant::withWifi()->get();
});

Sur le pouce - je n'ai pas testé ce code, mais je pense que cela devrait fonctionner. Vous pouvez utiliser à la place un chargement dynamique avec une contrainte, mais cela ne précisera que si l’objet facilité est nul ou non. Il retournerait toujours tous les restaurants, sauf si vous spécifiez une clause where.

(P.S. Je m'en tiendrai à la forme singulière de Facility. Remarquez comment hasOne('Facilities') ne lit pas correctement?)

32
JeffreyWay

Je suis tombé sur ce post en essayant d'améliorer ma REST API lors de la construction d'un nouveau paradigme de partage. Vous voulez utiliser Eager Loading Constraints . Disons que vous avez une API route où vous chargez un élément partagé et sa collection de sous-éléments tels que celui-ci:

/api/shared/{share_id}/subitem/{subitem_id}

Lorsque vous atteignez cette route avec une demande GET, vous souhaitez charger ce sous-élément spécifique. Certes, vous pouvez simplement charger ce modèle par cet identifiant, mais que se passe-t-il si nous devons valider si l'utilisateur a accès à cet élément partagé en premier lieu? Une réponse a recommandé de charger la relation inversée, mais cela pourrait conduire à un contrôleur confus et confus très rapidement. L'utilisation de contraintes sur la charge désirée est une approche plus "éloquente". Nous le chargeons donc comme ceci:

$shared = Shared::where('id', $share_id)
  ->with([ 'subitems' => function($query) use ($subitem_id) {
    $query->where('subitem_id', $subitem_id)
  }]);

Alors, où ne voulez que le sous-élément qui a cet identifiant. Maintenant, nous pouvons vérifier si elle a été trouvée ou non en faisant quelque chose comme ceci:

if ($shared->subitems->isEmpty())

Puisque les sous-éléments sont une collection (tableau de sous-éléments), nous renvoyons le sous-élément [0] avec ceci:

return $shared->subitems[0];
20
Throttlehead

Utilisez whereHas pour filtrer par n'importe quelle relation. Il ne rejoindra pas la relation mais filtrera le modèle actuel par une propriété associée. Consultez également les étendues locales pour vous aider dans des situations comme celle-ci https://laravel.com/docs/5.3/eloquent#local-scopes

Votre exemple serait:

Restaurant::whereHas('facilities', function($query) {
    return $query->where('wifi', true);
})->get();


Restaurant::whereHas('cuisines', function($query) use ($cuisineId) {
    return $query->where('id', $cuisineId);
})->get();

Pour réaliser la même chose avec des étendues locales:

class Restaurant extends Eloquent
{
    // Relations here

    public function scopeHasWifi($query)
    {
        return $query->whereHas('facilities', function($query) {
            return $query->where('wifi', true);
        });
    }

    public function scopeHasCuisine($query, $cuisineId)
    {
        return $query->whereHas('cuisines', function($query) use ($cuisineId) {
            return $query->where('id', $cuisineId);
        });
    }
}

Pour les étendues locales, vous NE voulez PAS les définir comme méthodes statiques sur votre modèle car cela crée une nouvelle instance du générateur de requêtes et vous empêcherait de chaîner les méthodes. L'utilisation d'une portée locale injecte et renvoie l'instance actuelle du générateur de requêtes afin que vous puissiez chaîner autant de portées que vous le souhaitez, comme:

Restaurant::hasWifi()->hasCuisine(6)->get();

Les portées locales sont définies avec le préfixe scope dans le nom de la méthode et appelées sans scope dans le nom de la méthode comme dans l'exemple ci-dessus.

14
Eric Tucker

Une autre solution mettant en vedette la fonction whereHas():

$with_wifi = function ($query) {
   $query->where('wifi', 1);
};

Facilities::whereHas('restaurant', $with_wifi)

Joli et rangé.

10
Yauheni Prakopchyk

Faut-il absolument le charger à partir du modèle Restaurant? Afin de résoudre le problème, je l'aborde généralement inversement.

Facilities::with('restaurant')->where('wifi' ,'=', 0)->get();

Vous obtiendrez ainsi toutes les installations du restaurant qui correspondent à vos conditions, et vous serez impatient de charger le restaurant.

Vous pouvez enchaîner plus de conditions et compter le total comme ceci.

Facilities::with('restaurant')
  ->where('wifi' ,'=', 1)
  ->where('parking','=', 1)
  ->count();

Cela fonctionnera aussi avec la cuisine

Cuisine::with('restaurant')->where('id','=',1)->get();

Cela saisit l'objet cuisine avec l'id de 1 impatient chargé de tous les restaurants qui ont cette cuisine

1
Alex Naspo