web-dev-qa-db-fra.com

Laravel relations récursives

Je travaille sur un projet dans Laravel. J'ai un modèle de compte qui peut avoir un parent ou des enfants. J'ai donc mon modèle configuré comme suit:

public function immediateChildAccounts()
{
    return $this->hasMany('Account', 'act_parent', 'act_id');
}

public function parentAccount()
{
    return $this->belongsTo('Account', 'act_parent', 'act_id');
}

Cela fonctionne bien. Ce que je veux faire, c'est placer tous les enfants sous un certain compte. Actuellement, je fais ceci:

public function allChildAccounts()
{
    $childAccounts = $this->immediateChildAccounts;
    if (empty($childAccounts))
        return $childAccounts;

    foreach ($childAccounts as $child)
    {
        $child->load('immediateChildAccounts');
        $childAccounts = $childAccounts->merge($child->allChildAccounts());
    }

    return $childAccounts;
}

Cela fonctionne aussi, mais je dois m'inquiéter si c'est lent. Ce projet est la réécriture d'un ancien projet que nous utilisons au travail. Nous aurons plusieurs milliers de comptes que nous migrerons vers ce nouveau projet. Pour les quelques comptes de test que j'ai, cette méthode ne pose aucun problème de performances.

Y a-t-il une meilleure solution? Devrais-je exécuter une requête brute? Est-ce que Laravel a quelque chose à faire? 

En résumé Ce que je veux faire, pour un compte donné, est d’obtenir chaque compte enfant et chaque enfant de ses enfants et ainsi de suite dans une seule liste/collection. Un diagramme:

A -> B -> D
|--> C -> E
     |--> F 
G -> H

Si je lance A-> immediateChildAccounts (), je devrais obtenir {B, C}
Si je lance A-> allChildAccounts (), je devrais avoir {B, D, C, E, F} (l'ordre n'a pas d'importance)

Encore une fois, ma méthode fonctionne, mais il me semble que je fais beaucoup trop de requêtes.

De plus, je ne suis pas sûr de pouvoir poser cette question ici, mais c'est lié. Comment puis-je obtenir une liste de tous les comptes qui ne pas inclure les comptes enfants? Donc, fondamentalement, l'inverse de cette méthode ci-dessus. Ainsi, un utilisateur n'essaye pas de donner à un compte un parent qui est déjà son enfant. En utilisant le diagramme ci-dessus, je veux (en pseudocode):

Account :: where (id_compte non dans (A-> allChildAccounts ())). Donc j'aurais {G, H}

Merci pour toute idée.

32
Troncoso

Voici comment utiliser les relations récursives:

public function childrenAccounts()
{
    return $this->hasMany('Account', 'act_parent', 'act_id');
}

public function allChildrenAccounts()
{
    return $this->childrenAccounts()->with('allChildrenAccounts');
}

Ensuite:

$account = Account::with('allChildrenAccounts')->first();

$account->allChildrenAccounts; // collection of recursively loaded children
// each of them having the same collection of children:
$account->allChildrenAccounts->first()->allChildrenAccounts; // .. and so on

De cette façon, vous enregistrez beaucoup de requêtes. Ceci exécutera 1 requête pour chaque niveau d'imbrication + 1 requête supplémentaire.

Je ne peux pas garantir que vos données seront efficaces, vous devez absolument les tester.


Ceci est pour les comptes sans enfant:

public function scopeChildless($q)
{
   $q->has('childrenAccounts', '=', 0);
}

puis:

$childlessAccounts = Account::childless()->get();
56
Jarek Tkaczyk

Je fais quelque chose de similaire. Je pense que la réponse est de mettre en cache la sortie et de vider le cache chaque fois que la base de données est mise à jour (à condition que vos comptes ne changent pas beaucoup?)

2
Kurucu

Nous faisons quelque chose de similaire, mais notre solution était la suivante:

class Item extends Model {
  protected $with = ['children'];

  public function children() {
    $this->hasMany(App\Items::class, 'parent_id', 'id');
 }
}
1
Meki
public function childrenAccounts()
{
    return $this->hasMany('Account', 'act_parent', 'act_id')->with('childrenAccounts);
}

ce code renvoie tous les comptes enfants (récurrents).

1
George

Je pense que j'ai aussi trouvé une solution décente.

class Organization extends Model
{
    public function customers()
    {
        return $this->hasMany(Customer::class, 'orgUid', 'orgUid');
    }

    public function childOrganizations()
    {
        return $this->hasMany(Organization::class, 'parentOrgUid', 'orgUid');
    }

    static function addIdToQuery($query, $org)
    {
        $query = $query->orWhere('id', $org->id);
        foreach ($org->childOrganizations as $org)
        {
            $query = Organization::addIdToQuery($query, $org);
        }
        return $query;
    }

    public function recursiveChildOrganizations()
    {
        $query = $this->childOrganizations();
        $query = Organization::addIdToQuery($query, $this);
        return $query;
    }

    public function recursiveCustomers()
    {
         $query = $this->customers();
         $childOrgUids = $this->recursiveChildOrganizations()->pluck('orgUid');
         return $query->orWhereIn('orgUid', $childOrgUids);
    }

}

Fondamentalement, je commence par une relation constructeur de requêtes et l'ajout de conditions orWhere. Dans le cas de la recherche de toutes les organisations enfants, j'utilise une fonction récursive pour explorer les relations.

Une fois que j'ai la relation recursiveChildOrganizations, j'ai exécuté la seule fonction récursive nécessaire. Toutes les autres relations (j'ai montré aux clients récursifs mais vous pourriez en avoir beaucoup) l'utilisent.

J'évite d'instancier les objets à chaque tour, car le constructeur de requêtes est beaucoup plus rapide que la création de modèles et l'utilisation de collections.

Cela est beaucoup plus rapide que de construire une collection et d'y insérer récursivement des membres (ce qui était ma première solution) et, chaque méthode renvoyant un générateur de requêtes et non une collection, elle s'empile à merveille avec des étendues ou toute autre condition que vous souhaitez utiliser.

0
Alex Fichter

Pour référence future:

public function parent()
{
    // recursively return all parents
    // the with() function call makes it recursive.
    // if you remove with() it only returns the direct parent
    return $this->belongsTo('App\Models\Category', 'parent_id')->with('parent');
}

public function child()
{
    // recursively return all children
    return $this->hasOne('App\Models\Category', 'parent_id')->with('child');
}

Ceci est pour un modèle Category qui a id, title, parent_id. Voici le code de migration de la base de données:

    Schema::create('categories', function (Blueprint $table) {
        $table->increments('id');
        $table->timestamps();
        $table->string('title');
        $table->integer('parent_id')->unsigned()->nullable();
        $table->foreign('parent_id')->references('id')->on('categories')->onUpdate('cascade')->onDelete('cascade');
    });
0
Vahid Amiri