|
|
@@ -0,0 +1,117 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+declare(strict_types=1);
|
|
|
+
|
|
|
+namespace App\Service\Utils\Tree;
|
|
|
+
|
|
|
+class ResourceTreeBuilder
|
|
|
+{
|
|
|
+ /**
|
|
|
+ * Construit un arbre à partir d'un tableau à plat.
|
|
|
+ * La méthode admet entre un et trois niveaux.
|
|
|
+ *
|
|
|
+ * Les données d'entrée doivent être des objets avec des getters.
|
|
|
+ *
|
|
|
+ * Exemple :
|
|
|
+ *
|
|
|
+ * $data = [
|
|
|
+ * MyResource({category: 'category1', subCategory: 'sub-category1', value: 'item1'}),
|
|
|
+ * MyResource({category: 'category1', subCategory: 'sub-category1', value: 'item2'}),
|
|
|
+ * MyResource({category: 'category1', subCategory: 'sub-category2', value: 'item3'}),
|
|
|
+ * MyResource({category: 'category2', subCategory: 'sub-category3', value: 'item4'}),
|
|
|
+ * ]
|
|
|
+ *
|
|
|
+ * La fonction :
|
|
|
+ *
|
|
|
+ * >>> TreeResourceBuilder::makeTree($data, 'category', 'subCategory', 'value')
|
|
|
+ *
|
|
|
+ * Donnera :
|
|
|
+ *
|
|
|
+ * $tree = [
|
|
|
+ * [
|
|
|
+ * 'category1' => [
|
|
|
+ * 'sub-category1' => ['item1', 'item2'],
|
|
|
+ * 'sub-category2' => ['item3'],
|
|
|
+ * ],
|
|
|
+ * 'category2' => [
|
|
|
+ * 'sub-category3' => ['item4'],
|
|
|
+ * ],
|
|
|
+ * ]
|
|
|
+ *
|
|
|
+ * @param array<mixed> $data
|
|
|
+ *
|
|
|
+ * @return array<string> | array<string, array<string, string>> | array<string, array<string, array<string, string>>>>>
|
|
|
+ */
|
|
|
+ public static function makeTree(
|
|
|
+ array $data,
|
|
|
+ string $level1PropName = 'name',
|
|
|
+ ?string $level2PropName = null,
|
|
|
+ ?string $level3PropName = null,
|
|
|
+ ): array {
|
|
|
+ $content = [];
|
|
|
+
|
|
|
+ foreach ($data as $item) {
|
|
|
+ $level1Value = self::callGetter($item, $level1PropName);
|
|
|
+
|
|
|
+ // On ajoute l'item de niveau 1 s'il n'existe pas encore
|
|
|
+ if (!array_key_exists($level1Value, $content)) {
|
|
|
+ if ($level2PropName !== null) {
|
|
|
+ // Il y a un niveau 2, on prépare le contenu
|
|
|
+ $content[$level1Value] = [];
|
|
|
+ } else {
|
|
|
+ // Pas de niveau 2, on ajoute directement l'item et on passe à l'item suivant
|
|
|
+ $content[] = $level1Value;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($level2PropName === null) {
|
|
|
+ // Si pas de niveau 2, on passe à l'item suivant
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $level2Value = self::callGetter($item, $level2PropName);
|
|
|
+
|
|
|
+ if (!array_key_exists($level2Value, $content[$level1Value])) {
|
|
|
+ if ($level3PropName !== null) {
|
|
|
+ // Il y a un niveau 3, on prépare le contenu
|
|
|
+ $content[$level1Value][$level2Value] = [];
|
|
|
+ } else {
|
|
|
+ // Pas de niveau 3, on ajoute directement l'item et on passe à l'item suivant
|
|
|
+ $content[$level1Value][] = $level2Value;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($level3PropName === null) {
|
|
|
+ // Si pas de niveau 3, on passe à l'item suivant
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $level3Value = self::callGetter($item, $level3PropName);
|
|
|
+
|
|
|
+ if (!array_key_exists($level3PropName, $content[$level1Value][$level2Value])) {
|
|
|
+ $content[$level1Value][$level2Value][] = $level1Value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return $content;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected static function callGetter($object, $propName): string
|
|
|
+ {
|
|
|
+ $getterName = 'get'.ucfirst($propName);
|
|
|
+
|
|
|
+ if (!method_exists($object, $getterName)) {
|
|
|
+ throw new \InvalidArgumentException('Invalid property name');
|
|
|
+ }
|
|
|
+
|
|
|
+ $value = $object->$getterName();
|
|
|
+
|
|
|
+ if ((new \ReflectionClass($value))->isEnum()) {
|
|
|
+ return $value->value;
|
|
|
+ }
|
|
|
+
|
|
|
+ return (string) $value;
|
|
|
+ }
|
|
|
+}
|