web-dev-qa-db-fra.com

A plusieurs par plusieurs à plusieurs

J'ai la disposition de table suivante:

deals:
- id
- price

products:
- id
- name

deal_product:
- id
- deal_id
- product_id

metrics:
- id
- name

metric_product:
- id
- metric_id
- product_id
- value

products et metrics ont une relation plusieurs-à-plusieurs avec une colonne pivot de valeur.

deals et products ont également une relation plusieurs-à-plusieurs.

Je peux obtenir des statistiques pour un produit avec $product->metrics, mais je veux pouvoir obtenir toutes les statistiques pour tous les produits liés à un accord, donc je pourrais faire quelque chose comme ceci: $deal->metrics.

J'ai actuellement les éléments suivants dans mon modèle Deal:

public function metrics()
{
    $products = $this->products()->pluck('products.id')->all();

    return Metric::whereHas('products', function (Builder $query) use ($products) {
        $query->whereIn('products.id', $products);
    });
}

Mais cela ne renvoie pas une relation, donc je ne peux pas le charger ou obtenir des modèles connexes.

Il doit être au format relationnel, car ils doivent être chargés pour mon cas d'utilisation.

Merci de votre aide!

18
ntzm

Si vous souhaitez avoir une relation personnalisée, vous pouvez créer vos propres étendues à la classe abstraite Relation. Par exemple: BelongsToManyThought.

Mais si vous ne voulez pas implémenter une Relation, je pense qu'elle peut répondre à vos besoins:

Dans App\Deal.php, vous pouvez combiner la solution de @ thomas-van-der-veen

public function metrics()
{
    return Metric

    ::join('metric_product', 'metric.id', '=', 'metric_product.metric_id')

    ->join('products', 'metric_product.product_id', '=', 'products.id')

    ->join('deal_product', 'products.id', '=', 'deal_product.product_id')

    ->join('deals', 'deal_product.deal_id', '=', 'deal.id')

    ->where('deal.id', $this->id);

}


// you can access to $deal->metrics and use eager loading
public function getMetricsAttribute()
{
    if (!$this->relationLoaded('products') || !$this->products->first()->relationLoaded('metrics')) {
        $this->load('products.metrics');
    }

    return collect($this->products->lists('metrics'))->collapse()->unique();
}

Vous pouvez vous référer à ceci post pour voir comment vous pouvez simplement utiliser des relations imbriquées.

Cette solution peut faire l'affaire pour interroger la relation avec la méthode et l'accès à l'attribut de métriques.

13
mpur

Il existe un Laravel 5.5 composer package qui peut effectuer des relations à plusieurs niveaux (approfondi)

Paquet: https://github.com/staudenmeir/eloquent-has-many-deep

Exemple:

User → appartient à plusieurs → Role → appartient à plusieurs → Permission

class User extends Model
{
    use \Staudenmeir\EloquentHasManyDeep\HasRelationships;

    public function permissions()
    {
        return $this->hasManyDeep(
            'App\Permission',
            ['role_user', 'App\Role', 'permission_role'], // Pivot tables/models starting from the Parent, which is the User
        );
    }
}

Exemple si des clés étrangères doivent être définies:

https://github.com/staudenmeir/eloquent-has-many-deep/issues/7#issuecomment-43147794

4
NicksonYap

Cela devrait faire l'affaire:

Le fichier Models/Product.php:

class Product extends Model
{
    public function deals()
    {
        return $this->belongsToMany(Deal::class);
    }

    public function metrics()
    {
        return $this->belongsToMany(Metric::class);
    }
}

le fichier Models/Deal.php:

class Deal extends Model
{
    public function products()
    {
        return $this->belongsToMany(Product::class);
    }
}

le fichier Models/Metric.php:

class Metric extends Model
{
    public function products()
    {
        return $this->belongsToMany(Product::class);
    }
}

Depuis votre contrôleur:

class ExampleController extends Controller
{
    public function index()
    {
        $deal = Deal::with('products.metrics')->get();
    }
}

Vous disposez désormais d'une collection d'offres sur les produits et de leurs statistiques associées. Voir docs pour un chargement rapide imbriqué.

3
Rafael Berro

Je n'ai pas assez de représentants pour poster un commentaire, je vais donc poster une réponse à la place.

Il y a quelques jours, j'ai posté un question similaire et j'ai pu y répondre moi-même. Je pense que la solution fonctionne également pour cette question.

En joignant les tables manuellement et en ajoutant une clause where, vous pouvez récupérer les métriques souhaitées.

Solution possible:

// Deal.php
public function metrics()
{
    return Metric

        ::join('metric_product', 'metric.id', '=', 'metric_product.metric_id')

        ->join('products', 'metric_product.product_id', '=', 'products.id')

        ->join('deal_product', 'products.id', '=', 'deal_product.product_id')

        ->join('deals', 'deal_product.deal_id', '=', 'deal.id')

        ->where('deal.id', $this->id);

}

// How to retrieve metrics
$deal->metrics()->get();

// You can still use other functions like (what I needed) pagination
$deal->metrics()->paginate(24);

// Or add more where clauses
$deal->metrics()->where(blablabla)->get();

J'espère que cela t'aides :)

Modifier après le commentaire @ntzm

Si vous n'avez besoin que de certaines propriétés de la table de mesures et d'aucune des fonctionnalités du modèle, vous pouvez ajouter une sélection ('*') avant les jointures.

Comme ça:

return Metric

    ::select('*')

    ->join(...........

Il renverra un modèle métrique avec tous les attributs des tables jointes.

Cet exemple retournera un objet comme:

Metric (
    ....
        attributes: [
            id => 0, // From metrics table
            name => 'somename', // Metrics table
            metric_id => 0, // metric_product table
            product_id => 0, // metric_product table
            ....
        ]
    ....
)

Puisqu'il y a beaucoup de colonnes avec le même nom, vous ne pourrez pas accéder à ces valeurs. Cependant, vous pouvez effectuer plusieurs sélections et renommer les colonnes dans vos résultats.

Comme ça:

retrun Metric

    ::select([
        *,
        DB::raw('products.name as product_name'),
        ....
    ])

    ->join(....

Cela se traduira par quelque chose comme:

Metric (
    ....
        attributes: [
            id => 0, // From metrics table
            name => 'somename', // Metrics table
            metric_id => 0, // metric_product table
            product_id => 0, // metric_product table
            product_name => 'somename' // products table
            ....
        ]
    ....
)

J'espère que cela résoudra quelque chose :) Bien sûr, il existe des moyens beaucoup plus propres de résoudre votre problème.

3

Il n'y a pas de méthode "plusieurs à plusieurs".

Pour obtenir toutes les métriques disponibles dans tous les produits pour une certaine offre, créez un getter pour Eager Load all Products and Metrics for the Deal, et tirez parti des méthodes de collecte de Laravel.

Dans votre modèle Deal:

public function products()
{
    return $this->belongsToMany('App\Product');
}

public function getMetrics()
{
    return $this->products->lists('metrics')->collapse()->values();
}

Vous pouvez ensuite obtenir les métriques d'un accord avec $deal->getMetrics()

Cela renverra une collection d'objets de métrique, qui devrait être exactement ce que vous recherchez.

1
crabbly

Si quelqu'un en a encore besoin:

Vous devez d'abord charger les produits et les métriques

$deals->with('products.metrics');

dans le modèle d'accord

public function getMetricsAttribute(){
   return $this->products->pluck('metrics')->collapse();
}

vous pouvez désormais obtenir toutes les métriques des produits d'un accord en:

$deal->metrics
0
Zeyad Mounir

Si quelqu'un fait encore des recherches à ce sujet, consultez ce package que j'ai récemment développé. Il vous fournit une relation belongsToManyThrough() et est super facile à installer et à utiliser :)

https://github.com/shomisha/unusual-relationships

0
Misa Kovic