Laravel Relations Récursives

je travaille sur un projet en Laravel. J'ai un Compte modèle qui peut avoir un parent ou peut avoir des enfants, donc j'ai mon modèle mis en place 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 très bien. Ce que je veux, c'est que tous les enfants aient un compte. Actuellement, je suis en train de faire 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 vieux projet que nous utilisons au travail. Nous aurons plusieurs milliers de comptes que nous migrerons au cours de ce nouveau projet. Pour les quelques comptes de test que j'ai, cette méthode ne pose aucun problème de performance.

y a-t-il une meilleure solution? Dois-je simplement exécuter une crue de la requête? Fait Laravel<!-- Vous avez quelque chose pour gérer ça?

En résumé Ce que je veux faire, pour n'importe quel compte donné, c'est obtenir chaque compte enfant et chaque enfant de ses enfants et ainsi de suite dans une liste/collection unique. Un schéma:

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

si je cours A->immediateChildAccounts(), je devrais obtenir {B, C}

Si j'exécute a->allChildAccounts (), je devrais obtenir {B, D, C, E, F} (order doesn't matter)

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

aussi, je ne suis pas sûr que ce soit correct de demander ça ici, mais c'est lié. Comment puis-je obtenir une liste de tous les comptes que ne pas inclure les comptes des enfants? Donc fondamentalement l'inverse de cette méthode ci-dessus. C'est pour qu'un utilisateur n'essaie pas donner un compte à un parent qui est déjà son enfant. En utilisant le diagramme ci-dessus, je veux (en pseudo-code):

Compte::où(id_compte pas dans (A->allChildAccounts())). Donc, je voudrais obtenir {G, H}

Merci pour toute la perspicacité.

20
demandé sur Troncoso 2014-10-30 15:08:07

4 réponses

Voici comment vous pouvez utiliser les relations récursives:

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

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

Puis:

$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

ainsi vous enregistrez beaucoup de requêtes. Ceci exécutera 1 requête par niveau de nidification + 1 requête supplémentaire.

je ne peux pas garantir qu'il sera efficace pour vos données, vous devez la tester absolument.


C'est pour les enfant de comptes:

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

puis:

$childlessAccounts = Account::childless()->get();
41
répondu Jarek Tkaczyk 2014-10-30 13:21:31

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 eux-mêmes ne changent pas beaucoup?)

2
répondu Kurucu 2016-09-21 20:03:12

nous faisons quelque chose de semblable, mais notre solution était celle-ci:

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

  public function children() {
    $this->hasMany(App\Items::class, 'parent_id', 'id');
 }
}
1
répondu Meki 2018-03-26 10:27:17

je pense que j'ai trouvé une bonne solution aussi bien.

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 avec une relation de constructeur de requête et en ajoutant des conditions orWhere à elle. Dans le cas de trouver toutes les organisations enfants, j'utilise une fonction récursive pour explorer les relations.

Une fois que j'ai la relation récursivechildorganisations, j'ai exécuté la seule fonction récursive nécessaire. Toutes les autres relations (j'ai montré récursivecustomers mais vous pourrait avoir beaucoup) utilisez ceci.

j'évite d'instancier les objets à chaque tour possible, car le constructeur de requêtes est tellement plus rapide que de créer des modèles et de travailler avec des collections.

Ceci est beaucoup plus rapide que la construction d'une collection et poussant récursivement des membres à elle (qui était ma première solution), et puisque chaque méthode retourne un constructeur de requête et non une collection, il empile merveilleusement avec des lunettes ou toute autre condition que vous voulez utiliser.

0
répondu Alex Fichter 2018-09-14 20:14:57