Просмотр исходного кода

add the ResourceTreeBuilder service and the TypeOfPracticeTree resource

Olivier Massot 2 месяцев назад
Родитель
Сommit
9d38c927b6

+ 1 - 0
config/opentalent/products.yaml

@@ -36,6 +36,7 @@ parameters:
           - SubdomainAvailability
           - UserSearchItem
           - DolibarrDocDownload
+          - TypeOfPracticeTree
         roles:
           - ROLE_IMPORT
           - ROLE_TAGG

+ 23 - 0
src/ApiResources/Tree/TypeOfPracticeTree.php

@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\ApiResources\Tree;
+
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use App\Service\Utils\Tree\BaseResourceTree;
+use App\Service\Utils\Tree\ResourceTreeInterface;
+use App\State\Provider\Tree\TypeOfPracticeTreeProvider;
+
+#[ApiResource(
+    operations: [
+        new Get(
+            uriTemplate: '/tree/type_of_practices',
+        ),
+    ],
+    provider: TypeOfPracticeTreeProvider::class,
+)]
+class TypeOfPracticeTree extends BaseResourceTree implements ResourceTreeInterface
+{
+}

+ 39 - 0
src/Service/ApiResourceBuilder/ResourceTree/TypeOfPracticeTreeBuilder.php

@@ -0,0 +1,39 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Service\ApiResourceBuilder\ResourceTree;
+
+use App\ApiResources\Tree\TypeOfPracticeTree;
+use App\Entity\Organization\TypeOfPractice;
+use App\Service\Utils\Tree\ResourceTreeBuilder;
+use Doctrine\ORM\EntityManagerInterface;
+
+
+/**
+ *
+ */
+class TypeOfPracticeTreeBuilder
+{
+    public function __construct(
+        private readonly EntityManagerInterface $entityManager,
+    ) {
+    }
+
+    public function make(): TypeOfPracticeTree
+    {
+        $typeOfPracticesRepository = $this->entityManager->getRepository(TypeOfPractice::class);
+
+        $typeOfPractices = $typeOfPracticesRepository->findAll();
+
+        $tree = ResourceTreeBuilder::makeTree(
+            $typeOfPractices,
+            'category',
+            'name'
+        );
+
+        $treeResource = new TypeOfPracticeTree();
+        $treeResource->setContent($tree);
+
+        return $treeResource;
+    }
+}

+ 31 - 0
src/Service/Utils/Tree/BaseResourceTree.php

@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Service\Utils\Tree;
+
+class BaseResourceTree implements ResourceTreeInterface
+{
+    /**
+     * @var array<string> | array<string, array<string, string>> | array<string, array<string, array<string, string>>>>>
+     */
+    private array $content = [];
+
+    /**
+     * @return array<string> | array<string, array<string, string>> | array<string, array<string, array<string, string>>>>>
+     */
+    public function getContent(): array
+    {
+        return $this->content;
+    }
+
+    /**
+     * @var array<string> | array<string, array<string, string>> | array<string, array<string, array<string, string>>>>>
+     */
+    public function setContent(array $content): self
+    {
+        $this->content = $content;
+
+        return $this;
+    }
+}

+ 117 - 0
src/Service/Utils/Tree/ResourceTreeBuilder.php

@@ -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;
+    }
+}

+ 20 - 0
src/Service/Utils/Tree/ResourceTreeInterface.php

@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Service\Utils\Tree;
+
+use App\ApiResources\ApiResourcesInterface;
+
+interface ResourceTreeInterface extends ApiResourcesInterface
+{
+    /**
+     * @return array<string> | array<string, array<string, string>> | array<string, array<string, array<string, string>>>>>
+     */
+    public function getContent(): array;
+
+    /**
+     * @var array<string> | array<string, array<string, string>> | array<string, array<string, array<string, string>>>>>
+     */
+    public function setContent(array $content): self;
+}

+ 39 - 0
src/State/Provider/Tree/TypeOfPracticeTreeProvider.php

@@ -0,0 +1,39 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\State\Provider\Tree;
+
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Operation;
+use ApiPlatform\State\ProviderInterface;
+use App\ApiResources\Tree\TypeOfPracticeTree;
+use App\Entity\Organization\TypeOfPractice;
+use App\Service\ApiResourceBuilder\ResourceTree\TypeOfPracticeTreeBuilder;
+use App\Service\Utils\Tree\ResourceTreeBuilder;
+use Doctrine\ORM\EntityManagerInterface;
+use Symfony\Component\HttpFoundation\Response;
+
+class TypeOfPracticeTreeProvider implements ProviderInterface
+{
+    public function __construct(
+        private readonly TypeOfPracticeTreeBuilder $typeOfPracticesTreeBuilder,
+    ) {
+    }
+
+    /**
+     * @param array<mixed> $uriVariables
+     * @param array<mixed> $context
+     *
+     * @throws \Doctrine\ORM\Exception\ORMException
+     * @throws \Doctrine\ORM\OptimisticLockException
+     */
+    public function provide(Operation $operation, array $uriVariables = [], array $context = []): TypeOfPracticeTree
+    {
+        if ($operation instanceof GetCollection) {
+            throw new \RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
+        }
+
+        return $this->typeOfPracticesTreeBuilder->make();
+    }
+}