Il s'agit d'un package Laravel 4-10 pour travailler avec des arbres dans des bases de données relationnelles.
Laravel 11.0 est pris en charge depuis la v6.0.4
Laravel 10.0 est pris en charge depuis la v6.0.2
Laravel 9.0 est pris en charge depuis la v6.0.1
Laravel 8.0 est pris en charge depuis la v6.0.0
Laravel 5.7, 5.8, 6.0, 7.0 est supporté depuis la v5
Laravel 5.5, 5.6 est supporté depuis la v4.3
Laravel 5.2, 5.3, 5.4 est supporté depuis la v4
Laravel 5.1 est pris en charge dans la v3
Laravel 4 est pris en charge dans la v2
Contenu:
Théorie
Documentation
Insertion de nœuds
Récupération de nœuds
Suppression de nœuds
Vérification et correction de la cohérence
Cadrage
Exigences
Installation
Les ensembles imbriqués ou Nested Set Model sont un moyen de stocker efficacement des données hiérarchiques dans une table relationnelle. De Wikipédia :
Le modèle d'ensemble imbriqué consiste à numéroter les nœuds selon un parcours d'arbre, qui visite chaque nœud deux fois, en attribuant des numéros dans l'ordre de visite, et lors des deux visites. Cela laisse deux nombres pour chaque nœud, qui sont stockés sous forme de deux attributs. L'interrogation devient peu coûteuse : l'appartenance à la hiérarchie peut être testée en comparant ces nombres. La mise à jour nécessite une renumérotation et est donc coûteuse.
NSM affiche de bonnes performances lorsque l’arborescence est rarement mise à jour. Il est conçu pour être rapide pour obtenir les nœuds associés. Il est idéal pour créer des menus ou des catégories multi-profondeurs pour les magasins.
Supposons que nous ayons un modèle Category
; une variable $node
est une instance de ce modèle et du nœud que nous manipulons. Il peut s'agir d'un nouveau modèle ou d'un modèle provenant d'une base de données.
Node a les relations suivantes qui sont entièrement fonctionnelles et peuvent être chargées avec impatience :
Le nœud appartient au parent
Le nœud a de nombreux children
Le nœud a de nombreux ancestors
Le nœud a de nombreux descendants
Le déplacement et l'insertion de nœuds impliquent plusieurs requêtes de base de données. Il est donc fortement recommandé d'utiliser des transactions.
IMPORTANT! Depuis la version 4.2.0, la transaction n'est pas automatiquement lancée
Une autre remarque importante est que les manipulations structurelles sont différées jusqu'à ce que vous appuyiez sur save
sur le modèle (certaines méthodes appellent implicitement save
et renvoient le résultat booléen de l'opération).
Si le modèle est enregistré avec succès, cela ne signifie pas que le nœud a été déplacé. Si votre application dépend du fait que le nœud ait réellement changé de position, utilisez la méthode hasMoved
:
if ($node->save()) {$moved = $node->hasMoved(); }
Lorsque vous créez simplement un nœud, il sera ajouté à la fin de l'arborescence :
Catégorie :: créer ($ attributs); // Enregistré en tant que root
$node = nouvelle catégorie($attributs);$node->save(); // Enregistré en tant que root
Dans ce cas, le nœud est considéré comme une racine , ce qui signifie qu'il n'a pas de parent.
// #1 Save$node->saveAsRoot();// #2 save$node->makeRoot()->save();
Le nœud sera ajouté à la fin de l’arborescence.
Si vous souhaitez faire du nœud un enfant d'un autre nœud, vous pouvez en faire le dernier ou le premier enfant.
Dans les exemples suivants, $parent
est un nœud existant.
Il existe plusieurs façons d'ajouter un nœud :
// #1 Utilisation de l'insertion différée$node->appendToNode($parent)->save();// #2 Utilisation du nœud parent$parent->appendNode($node);// #3 Utilisation de la relation parent-enfant$parent- >children()->create($attributes);// #5 Utilisation de la relation parent du nœud$node->parent()->associate($parent)->save();// #6 Utilisation du parent attribut$node->parent_id = $parent->id;$node->save();// #7 Utilisation de la méthode statiqueCategory::create($attributes, $parent);
Et seulement quelques façons de pré-ajouter :
// #1$node->prependToNode($parent)->save();// #2$parent->prependNode($node);
Vous pouvez faire en sorte que $node
soit un voisin du nœud $neighbor
en utilisant les méthodes suivantes :
$neighbor
doit exister, le nœud cible peut être récent. Si le nœud cible existe, il sera déplacé vers la nouvelle position et le parent sera modifié si nécessaire.
# Sauvegarde explicite$node->afterNode($neighbor)->save();$node->beforeNode($neighbor)->save();# Sauvegarde implicite$node->insertAfterNode($neighbor);$node-> insertBeforeNode($voisin);
Lors de l'utilisation de la méthode statique create
on node, elle vérifie si les attributs contiennent une clé children
. Si c'est le cas, il crée plus de nœuds de manière récursive.
$node = Category::create(['name' => 'Foo','children' => [ ['nom' => 'Bar','enfants' => [ [ 'nom' => 'Baz' ], ], ], ], ]);
$node->children
contient désormais une liste de nœuds enfants créés.
Vous pouvez facilement reconstruire un arbre. Ceci est utile pour modifier en masse la structure de l’arborescence.
Catégorie ::rebuildTree($data, $delete);
$data
est un tableau de nœuds :
$données = [ [ 'id' => 1, 'name' => 'foo', 'children' => [ ... ] ], [ 'nom' => 'bar' ], ];
Il y a un identifiant spécifié pour le nœud avec le nom foo
ce qui signifie que le nœud existant sera rempli et enregistré. Si le nœud n’existe pas, ModelNotFoundException
est levée. En outre, ce nœud a children
spécifiés qui sont également un tableau de nœuds ; ils seront traités de la même manière et enregistrés en tant qu'enfants du nœud foo
.
bar
de nœuds n'a pas de clé primaire spécifiée, elle sera donc créée.
$delete
indique s'il faut supprimer les nœuds qui existent déjà mais qui ne sont pas présents dans $data
. Par défaut, les nœuds ne sont pas supprimés.
Depuis la version 4.2.8, vous pouvez reconstruire un sous-arbre :
Catégorie ::rebuildSubtree($root, $data);
Cela contraint la reconstruction de l'arborescence aux descendants du nœud $root
.
Dans certains cas, nous utiliserons une variable $id
qui est un identifiant du nœud cible.
Les ancêtres forment une chaîne de parents jusqu'au nœud. Utile pour afficher le fil d'Ariane dans la catégorie actuelle.
Les descendants sont tous les nœuds d'un sous-arbre, c'est-à-dire les enfants du nœud, les enfants des enfants, etc.
Les ancêtres et les descendants peuvent être chargés avec impatience.
// Accès aux ancêtres$node->ancestors;// Accès aux descendants$node->descendants;
Il est possible de charger des ancêtres et des descendants à l'aide d'une requête personnalisée :
$result = Catégorie::ancestorsOf($id);$result = Catégorie::ancestorsAndSelf($id);$result = Catégorie::descendantsOf($id);$result = Catégorie::descendantsAndSelf($id);
Dans la plupart des cas, vos ancêtres doivent être classés par niveau :
$result = Category::defaultOrder()->ancestorsOf($id);
Une collection d’ancêtres peut être chargée avec impatience :
$categories = Category::with('ancestors')->paginate(30);// en vue du fil d'Ariane:@foreach($categories as $i => $category) <small>{{ $category->ancestors->count() ? implode(' > ', $category->ancestors->pluck('name')->toArray()) : 'Niveau supérieur' }}</small><br> {{ $category->name }} @endforeach
Les frères et sœurs sont des nœuds qui ont le même parent.
$result = $node->getSiblings();$result = $node->siblings()->get();
Pour obtenir uniquement les frères et sœurs suivants :
// Récupère un frère qui se trouve immédiatement après le nœud $result = $node->getNextSibling();// Récupère tous les frères et sœurs qui se trouvent après le nœud$result = $node->getNextSiblings();// Récupère tous les frères et sœurs en utilisant un query$result = $node->nextSiblings()->get();
Pour obtenir des frères et sœurs précédents :
// Récupère un frère qui est juste avant le nœud$result = $node->getPrevSibling();// Récupère tous les frères et sœurs qui sont avant le nœud$result = $node->getPrevSiblings();// Récupère tous les frères et sœurs en utilisant un query$result = $node->prevSiblings()->get();
Imaginez que chaque catégorie has many
biens. C'est-à-dire que la relation HasMany
est établie. Comment pouvez-vous obtenir tous les biens de $category
et chacun de ses descendants ? Facile!
// Récupère les identifiants des descendants$categories = $category->descendants()->pluck('id');// Inclut l'identifiant de la catégorie elle-même$categories[] = $category->getKey();// Récupère les marchandises $goods = Goods::whereIn('category_id', $categories)->get();
Si vous avez besoin de savoir à quel niveau se trouve le nœud :
$result = Category::withDepth()->find($id);$profondeur = $result->profondeur;
Le nœud racine sera au niveau 0. Les enfants des nœuds racines auront un niveau 1, etc.
Pour obtenir des nœuds du niveau spécifié, vous pouvez appliquer la contrainte having
:
$result = Category::withDepth()->having('profondeur', '=', 1)->get();
IMPORTANT! Cela ne fonctionnera pas en mode strict de base de données
Tous les nœuds sont strictement organisés en interne. Par défaut, aucun ordre n'est appliqué, les nœuds peuvent donc apparaître dans un ordre aléatoire et cela n'affecte pas l'affichage d'une arborescence. Vous pouvez trier les nœuds par alphabet ou autre index.
Mais dans certains cas, l'ordre hiérarchique est essentiel. Il est nécessaire pour récupérer les ancêtres et peut être utilisé pour commander des éléments de menu.
Pour appliquer l'ordre arborescent, la méthode defaultOrder
est utilisée :
$result = Catégorie ::defaultOrder()->get();
Vous pouvez obtenir les nœuds dans l'ordre inverse :
$result = Catégorie :: inversé () -> get ();
Pour déplacer le nœud vers le haut ou vers le bas à l'intérieur du parent afin d'affecter l'ordre par défaut :
$bool = $node->down();$bool = $node->up();// Décaler le nœud de 3 frères et sœurs$bool = $node->down(3);
Le résultat de l'opération est une valeur booléenne indiquant si le nœud a changé de position.
Diverses contraintes pouvant être appliquées au générateur de requêtes :
whereIsRoot() pour obtenir uniquement les nœuds racine ;
hasParent() pour obtenir les nœuds non root ;
whereIsLeaf() pour obtenir uniquement les feuilles ;
hasChildren() pour obtenir des nœuds qui ne quittent pas ;
whereIsAfter($id) pour obtenir tous les nœuds (pas seulement les frères et sœurs) qui se trouvent après un nœud avec l'identifiant spécifié ;
whereIsBefore($id) pour obtenir chaque nœud situé avant un nœud avec l'identifiant spécifié.
Contraintes des descendants :
$result = Catégorie::whereDescendantOf($node)->get();$result = Catégorie::whereNotDescendantOf($node)->get();$result = Catégorie::orWhereDescendantOf($node)->get() ;$result = Catégorie ::orWhereNotDescendantOf($node)->get();$result = Category::whereDescendantAndSelf($id)->get();// Inclure le nœud cible dans le jeu de résultats$result = Category::whereDescendantOrSelf($node)->get();
Contraintes des ancêtres :
$result = Catégorie ::whereAncestorOf($node)->get();$result = Catégorie ::whereAncestorOrSelf($id)->get();
$node
peut être soit une clé primaire du modèle, soit une instance de modèle.
Après avoir obtenu un ensemble de nœuds, vous pouvez le convertir en arbre. Par exemple:
$tree = Catégorie ::get()->toTree();
Cela remplira les relations parent
- children
sur chaque nœud de l'ensemble et vous pourrez restituer un arbre à l'aide d'un algorithme récursif :
$nodes = Category::get()->toTree();$traverse = fonction ($categories, $prefix = '-') utiliser ($traverse) {foreach ($categories as $category) {echo PHP_EOL.$ préfixe.' '.$category->name;$traverse($category->children, $prefix.'-'); } };$traverse($nœuds);
Cela produira quelque chose comme ceci :
- Root -- Child 1 --- Sub child 1 -- Child 2 - Another root
Vous pouvez également créer une arborescence plate : une liste de nœuds où les nœuds enfants se trouvent immédiatement après le nœud parent. Ceci est utile lorsque vous obtenez des nœuds avec un ordre personnalisé (c'est-à-dire par ordre alphabétique) et que vous ne souhaitez pas utiliser la récursivité pour parcourir vos nœuds.
$nodes = Category::get()->toFlatTree();
L'exemple précédent affichera :
Root Child 1 Sub child 1 Child 2 Another root
Parfois, vous n'avez pas besoin de charger l'arborescence entière et juste un sous-arbre d'un nœud spécifique. C'est le cas dans l'exemple suivant :
$root = Category::descendantsAndSelf($rootId)->toTree()->first();
En une seule requête, nous obtenons la racine d'un sous-arbre et tous ses descendants accessibles via la relation children
.
Si vous n'avez pas besoin du nœud $root
lui-même, procédez comme suit :
$tree = Catégorie ::descendantsOf($rootId)->toTree($rootId);
Pour supprimer un nœud :
$node->delete();
IMPORTANT! Tout descendant de ce nœud sera également supprimé !
IMPORTANT! Les nœuds doivent être supprimés en tant que modèles, n'essayez pas de les supprimer en utilisant une requête comme celle-ci :
Catégorie ::where('id', '=', $id)->delete();
Cela brisera l'arbre !
Le trait SoftDeletes
est pris en charge, également au niveau du modèle.
Pour vérifier si le nœud est un descendant d'un autre nœud :
$bool = $node->isDescendantOf($parent);
Pour vérifier si le nœud est une racine :
$bool = $node->isRoot();
Autres contrôles :
$node->isChildOf($other);
$node->isAncestorOf($other);
$node->isSiblingOf($other);
$node->isLeaf()
Vous pouvez vérifier si un arbre est cassé (c'est-à-dire s'il présente des erreurs structurelles) :
$bool = Catégorie ::isBroken();
Il est possible d'obtenir des statistiques d'erreurs :
$data = Catégorie ::countErrors();
Il renverra un tableau avec les clés suivantes :
oddness
-- le nombre de nœuds qui ont un mauvais ensemble de valeurs lft
et rgt
duplicates
-- le nombre de nœuds qui ont les mêmes valeurs lft
ou rgt
wrong_parent
-- le nombre de nœuds qui ont une valeur parent_id
invalide qui ne correspond pas aux valeurs lft
et rgt
missing_parent
-- le nombre de nœuds dont parent_id
pointe vers un nœud qui n'existe pas
Depuis la version 3.1, l'arborescence peut désormais être corrigée. En utilisant les informations d'héritage de la colonne parent_id
, les valeurs _lft
et _rgt
appropriées sont définies pour chaque nœud.
Node::fixTree();
Imaginez que vous ayez un modèle Menu
et MenuItems
. Il existe une relation un-à-plusieurs établie entre ces modèles. MenuItem
a l'attribut menu_id
pour joindre des modèles ensemble. MenuItem
incorpore des ensembles imbriqués. Il est évident que vous voudriez traiter chaque arbre séparément en fonction de l'attribut menu_id
. Pour ce faire, vous devez spécifier cet attribut comme attribut scope :
fonction protégée getScopeAttributes() {return [ 'menu_id' ]; }
Mais maintenant, afin d'exécuter une requête personnalisée, vous devez fournir des attributs utilisés pour la portée :
MenuItem::scoped([ 'menu_id' => 5 ])->withDepth()->get(); // OKMenuItem::descendantsOf($id)->get(); // FAUX : renvoie les nœuds d'autres scopeMenuItem::scoped([ 'menu_id' => 5 ])->fixTree(); // D'ACCORD
Lors de la demande de nœuds à l'aide d'une instance de modèle, les étendues s'appliquent automatiquement en fonction des attributs de ce modèle :
$node = MenuItem::findOrFail($id);$node->siblings()->withDepth()->get(); // D'ACCORD
Pour obtenir le générateur de requêtes étendues à l'aide d'une instance :
$node->newScopedQuery();
Utilisez toujours une requête étendue lors d'un chargement rapide :
MenuItem::scoped([ 'menu_id' => 5])->with('descendants')->findOrFail($id); // OKMenuItem::with('descendants')->findOrFail($id); // FAUX
PHP >= 5,4
Laravel >= 4.1
Il est fortement suggéré d'utiliser une base de données prenant en charge les transactions (comme InnoDb de MySql) pour sécuriser une arborescence contre une éventuelle corruption.
Pour installer le package, dans le terminal :
composer require kalnoy/nestedset
Pour les utilisateurs de Laravel 5.5 et supérieur :
Schema::create('table', function (Blueprint $table) {...$table->nestedSet(); });// Pour supprimer des colonnesSchema::table('table', function (Blueprint $table) {$table->dropNestedSet(); });
Pour les versions antérieures de Laravel :
...utilisez KalnoyNestedsetNestedSet ; Schema::create('table', function (Blueprint $table) {...NestedSet::columns($table); });
Pour supprimer des colonnes :
...utilisez KalnoyNestedsetNestedSet ; Schema::table('table', fonction (Blueprint $table) { NestedSet::dropColumns($table); });
Votre modèle doit utiliser le trait KalnoyNestedsetNodeTrait
pour activer les ensembles imbriqués :
utilisez KalnoyNestedsetNodeTrait; la classe Foo étend le modèle {utilisez NodeTrait; }
Si votre extension précédente utilisait un ensemble de colonnes différent, il vous suffit de remplacer les méthodes suivantes sur votre classe de modèle :
fonction publique getLftName() {retourner 'gauche'; }fonction publique getRgtName() {retourner 'à droite'; }fonction publique getParentIdName() {retourne 'parent'; }// Spécifiez la fonction mutatorpublic de l'attribut d'identifiant parent setParentAttribute($value) {$this->setParentIdAttribute($value); }
Si votre arborescence contient des informations parent_id
, vous devez ajouter deux colonnes à votre schéma :
$table->unsignedInteger('_lft');$table->unsignedInteger('_rgt');
Après avoir configuré votre modèle, il vous suffit de corriger l'arborescence pour remplir les colonnes _lft
et _rgt
:
MonModèle::fixTree();
Copyright (c) 2017 Alexandre Kalnoy
L'autorisation est accordée par la présente, gratuitement, à toute personne obtenant une copie de ce logiciel et des fichiers de documentation associés (le « Logiciel »), d'utiliser le Logiciel sans restriction, y compris, sans limitation, les droits d'utilisation, de copie, de modification, de fusion. , publier, distribuer, accorder des sous-licences et/ou vendre des copies du Logiciel, et permettre aux personnes à qui le Logiciel est fourni de le faire, sous réserve des conditions suivantes :
L'avis de droit d'auteur ci-dessus et cet avis d'autorisation doivent être inclus dans toutes les copies ou parties substantielles du logiciel.
LE LOGICIEL EST FOURNI « TEL QUEL », SANS GARANTIE D'AUCUNE SORTE, EXPRESSE OU IMPLICITE, Y COMPRIS MAIS SANS LIMITATION LES GARANTIES DE QUALITÉ MARCHANDE, D'ADAPTATION À UN USAGE PARTICULIER ET DE NON-VIOLATION. EN AUCUN CAS LES AUTEURS OU LES TITULAIRES DES DROITS D'AUTEUR NE SERONT RESPONSABLES DE TOUTE RÉCLAMATION, DOMMAGES OU AUTRE RESPONSABILITÉ, QUE CE SOIT DANS UNE ACTION CONTRACTUELLE, DÉLIT OU AUTRE, DÉCOULANT DE, DE OU EN RELATION AVEC LE LOGICIEL OU L'UTILISATION OU D'AUTRES TRANSACTIONS DANS LE LOGICIEL.