瀏覽代碼

refactor SchemaValidationService

Olivier Massot 1 年之前
父節點
當前提交
b66b624647

+ 1 - 4
src/Service/Cron/Job/SchemaValidation.php

@@ -3,10 +3,7 @@
 namespace App\Service\Cron\Job;
 
 use App\Service\Cron\BaseCronJob;
-use App\Service\Doctrine\SchemaValidationService;
-use App\Service\Utils\DatesUtils;
-use Doctrine\DBAL\Connection;
-use Doctrine\DBAL\DBALException;
+use App\Service\Doctrine\SchemaValidation\SchemaValidationService;
 use JetBrains\PhpStorm\Pure;
 
 /**

+ 13 - 0
src/Service/Doctrine/SchemaValidation/DiffTypeEnum.php

@@ -0,0 +1,13 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Service\Doctrine\SchemaValidation;
+
+enum DiffTypeEnum: string
+{
+    case MISSING_ENTITY = 'MISSING_ENTITY';
+    case MISSING_PROPERTY = 'MISSING_PROPERTY';
+    case DIFFERENT_TYPE = 'DIFFERENT_TYPE';
+    case DIFFERENT_RELATION_TYPE = 'DIFFERENT_RELATION_TYPE';
+    case DIFFERENT_RELATION_CONFIGURATION = 'DIFFERENT_RELATION_CONFIGURATION';
+}

+ 47 - 0
src/Service/Doctrine/SchemaValidation/Difference.php

@@ -0,0 +1,47 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Service\Doctrine\SchemaValidation;
+
+class Difference
+{
+    protected DiffTypeEnum $type;
+    protected ?string $message;
+
+
+    protected string $entity;
+    protected string $property;
+    protected string $expectedType;
+    protected string $actualType;
+    protected string $expectedRelationType;
+    protected string $actualRelationType;
+    protected string $expectedRelationConfiguration;
+    protected string $actualRelationConfiguration;
+
+    public function __construct(DiffTypeEnum $type, ?string $message) {
+        $this->type = $type;
+        $this->message = $message;
+    }
+
+    public function getType(): DiffTypeEnum
+    {
+        return $this->type;
+    }
+
+    public function setType(DiffTypeEnum $type): self
+    {
+        $this->type = $type;
+        return $this;
+    }
+
+    public function getMessage(): ?string
+    {
+        return $this->message;
+    }
+
+    public function setMessage(?string $message): self
+    {
+        $this->message = $message;
+        return $this;
+    }
+}

+ 243 - 0
src/Service/Doctrine/SchemaValidation/SchemaValidationService.php

@@ -0,0 +1,243 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Service\Doctrine\SchemaValidation;
+
+use App\Service\ApiLegacy\ApiLegacyRequestService;
+use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\ORM\Mapping\ClassMetadataInfo;
+use Doctrine\ORM\Mapping\MappingException;
+use RuntimeException;
+use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
+use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
+use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
+use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
+
+/**
+ * Validation du schéma Doctrine par comparaison aux entités en production sur la V1
+ *
+ * — À supprimer lorsque la migration sera achevée —
+ *
+ */
+class SchemaValidationService
+{
+    public function __construct(
+        private readonly EntityManagerInterface  $entityManager,
+        private readonly ApiLegacyRequestService $apiLegacyRequestService,
+    )
+    {}
+
+    /**
+     * Compare the V2 doctrine schema to the one in V1, and return a list of differences,
+     * of the form:
+     *
+     *     [<entity> → Difference | [<field> → Difference]]
+     *
+     * @return array
+     * @throws ClientExceptionInterface
+     * @throws MappingException
+     * @throws RedirectionExceptionInterface
+     * @throws ServerExceptionInterface
+     * @throws TransportExceptionInterface
+     */
+    public function validateSchema(): array
+    {
+        $schemaV1 = $this->getV1Schema();
+        $schemaV2 = $this->getV2Schema();
+
+        $diff = $this->getDiff($schemaV1, $schemaV2);
+
+        foreach ($diff['Organization'] as $field => $diffEntry) {
+            var_dump($field . " : " . $diffEntry->value);
+        }
+
+        return $diff;
+    }
+
+    /**
+     * Retrieve the V2 schema
+     *
+     * @return array<string, array<string | array<string|int>>
+     * @throws MappingException
+     */
+    protected function getV2Schema(): array
+    {
+        $metadata = $this->entityManager->getMetadataFactory()->getAllMetadata();
+        $schema = [];
+
+        foreach ($metadata as $entityMetadata) {
+            $schema[$entityMetadata->getTableName()] = [];
+
+            foreach ($entityMetadata->getFieldNames() as $field) {
+                $schema[$entityMetadata->getTableName()][$field] = $entityMetadata->getTypeOfField($field);
+            }
+
+            foreach ($entityMetadata->getAssociationNames() as $association) {
+                $schema[$entityMetadata->getTableName()][$association] = $entityMetadata->getAssociationMapping($association);
+            }
+        }
+
+        return $schema;
+    }
+
+    /**
+     * Retrieve the V1 schema
+     *
+     * @return array<string, array<string | array<string|int>>
+     * @throws ClientExceptionInterface
+     * @throws RedirectionExceptionInterface
+     * @throws ServerExceptionInterface
+     * @throws TransportExceptionInterface
+     */
+    protected function getV1Schema(): array {
+        $response = $this->apiLegacyRequestService->get('/_internal/doctrine/schema');
+
+        return json_decode($response->getContent(), true);
+    }
+
+    /**
+     * Get a list of differences between V1 and V2 doctrine schemas
+     *
+     * @param array<string, array<string | array<string|int>> $schemaV1
+     * @param array<string, array<string| array<string|int>>> $schemaV2
+     * @return array<string, Difference | array<Difference>>
+     */
+    protected function getDiff(array $schemaV1, array $schemaV2): array {
+        $diff = [
+        ];
+
+        foreach ($schemaV1 as $entity => $fields) {
+
+            if (!$this->isEntityInSchema($schemaV2, $entity)) {
+                // L'entité n'existe pas en V2
+                $diff[$entity] = new Difference(DiffTypeEnum::MISSING_ENTITY, "Entity $entity is missing in V2");
+                continue;
+            }
+
+            $diff[$entity] = [];
+
+            foreach ($fields as $field => $fieldTypeV1) {
+
+                if (!$this->isPropertyInSchema($schemaV2, $entity, $field)) {
+                    // Le champ n'existe pas en V2
+                    $diff[$entity][$field] = new Difference(DiffTypeEnum::MISSING_PROPERTY, "Property $field is missing in V2");
+                    continue;
+                }
+
+                $fieldTypeV2 = $schemaV2[$entity][$field];
+
+                // Si ce champ n'est pas une relation
+                if (!$this->isRelationField($schemaV1, $entity, $field)) {
+                    // Le champ n'est pas une relation en V1
+                    if ($fieldTypeV2 !== $fieldTypeV1) {
+                        // Le champ a un type différent en V2
+                        $diff[$entity][$field] = new Difference(DiffTypeEnum::DIFFERENT_TYPE, "Property $field has a different type (V1: $fieldTypeV1, V2: $fieldTypeV2)");
+                    }
+                } elseif (!$this->isRelationField($schemaV2, $entity, $field)) {
+                    // Le champ est une relation en V1 mais pas en V2
+                    $diff[$entity][$field] = new Difference(DiffTypeEnum::DIFFERENT_TYPE, "Property $field is a relation in V1 but not in V2");
+                } else {
+                    // Le champ est une relation dans les deux schémas, on compare leurs configurations
+                    $difference = $this->getRelationDiff($fieldTypeV1, $fieldTypeV2);
+                    if ($difference) {
+                        $diff[$entity][$field] = $difference;
+                    }
+                }
+            }
+        }
+
+        return $diff;
+    }
+
+    /**
+     * Returns true if the given entity name exists in the doctrine schema
+     *
+     * @param array<string, array<string | array<string|int>> $schema
+     * @param string $entity
+     * @return bool
+     */
+    protected function isEntityInSchema(array $schema, string $entity): bool {
+        return isset($schema[$entity]);
+    }
+
+    /**
+     * Returns true if the given property name exists in the doctrine schema under this entity.
+     *
+     * @param array<string, array<string | array<string|int>> $schema
+     * @param string $entity
+     * @param string $property
+     * @return bool
+     */
+    protected function isPropertyInSchema(array $schema, string $entity, string $property): bool {
+        return isset($schema[$entity][$property]);
+    }
+
+    /**
+     * Is the given field is a relation field.
+     *
+     * @param array<string, array<string | array<string|int>> $schema
+     * @param string $entity
+     * @param string $relation
+     * @return bool
+     */
+    protected function isRelationField(array $schema, string $entity, string $relation): bool {
+        return isset($schema[$entity][$relation]) && is_array($schema[$entity][$relation]);
+    }
+
+    /**
+     * Get the name of a relation from a ClassMetadataInfo integer constant.
+     *
+     * @param array<string, string|int> $relation
+     * @return string
+     */
+    protected function getRelationTypeLabel(array $relation): string {
+        if ($relation['type'] === ClassMetadataInfo::ONE_TO_ONE) {
+            return 'OneToOne';
+        } elseif ($relation['type'] === ClassMetadataInfo::MANY_TO_ONE) {
+            return 'ManyToOne';
+        } elseif ($relation['type'] === ClassMetadataInfo::ONE_TO_MANY) {
+            return 'OneToMany';
+        } elseif ($relation['type'] === ClassMetadataInfo::MANY_TO_MANY) {
+            return 'ManyToMany';
+        }
+        throw new RuntimeException('Unknown relation type');
+    }
+
+    /**
+     * Look up for differences in $relationCompared compared to $relationReference, and return
+     * a Difference if any, or null else.
+     *
+     * @param array<string, string> $relationReference
+     * @param array<string, string> $relationCompared
+     * @return Difference|null
+     */
+    protected function getRelationDiff(array $relationReference, array $relationCompared): Difference | null
+    {
+        if ($relationReference['type'] !== $relationCompared['type']) {
+            return new Difference(
+                DiffTypeEnum::DIFFERENT_RELATION_TYPE,
+                "Relation type is different : {$this->getRelationTypeLabel($relationReference)} !== {$this->getRelationTypeLabel($relationCompared)}"
+            );
+        }
+
+        if (
+            $relationReference['targetEntity'] !== $relationCompared['targetEntity']
+        ) {
+            return new Difference(
+                DiffTypeEnum::DIFFERENT_RELATION_CONFIGURATION,
+                "Relation configuration is different (targetEntity)"
+            );
+        }
+
+        if (
+            $relationReference['mappedBy'] !== $relationCompared['mappedBy']
+        ) {
+            return new Difference(
+                DiffTypeEnum::DIFFERENT_RELATION_CONFIGURATION,
+                "Relation configuration is different (mappedBy)"
+            );
+        }
+
+        return null;
+    }
+}

+ 0 - 101
src/Service/Doctrine/SchemaValidationService.php

@@ -1,101 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Service\Doctrine;
-
-use App\Service\ApiLegacy\ApiLegacyRequestService;
-use Doctrine\ORM\EntityManagerInterface;
-use Doctrine\ORM\Mapping\MappingException;
-
-/**
- * Validation du schéma Doctrine par comparaison aux entités en production sur la V1
- *
- * -- A supprimer lorsque la migration sera achevée --
- *
- */
-class SchemaValidationService
-{
-    public function __construct(
-        private EntityManagerInterface $entityManager,
-        private readonly ApiLegacyRequestService $apiLegacyRequestService,
-    )
-    {}
-
-    public function validateSchema() {
-        $schemaV1 = $this->getV1Schema();
-        $schemaV2 = $this->getV2Schema();
-
-        $diff = $this->getDiff($schemaV1, $schemaV2);
-
-        dd($diff);
-    }
-
-    /**
-     * Retrieve the V2 schema
-     *
-     * @return array<string, array>
-     * @throws MappingException
-     */
-    protected function getV2Schema(): array
-    {
-        $metadata = $this->entityManager->getMetadataFactory()->getAllMetadata();
-        $schema = [];
-
-        foreach ($metadata as $entityMetadata) {
-            $schema[$entityMetadata->getTableName()] = [];
-
-            foreach ($entityMetadata->getFieldNames() as $field) {
-                $schema[$entityMetadata->getTableName()][$field] = $entityMetadata->getTypeOfField($field);
-            }
-
-            foreach ($entityMetadata->getAssociationNames() as $association) {
-                $schema[$entityMetadata->getTableName()][$association] = $entityMetadata->getAssociationMapping($association);
-            }
-        }
-
-        return $schema;
-    }
-
-    /**
-     * Retrieve the V1 schema
-     */
-    protected function getV1Schema(): array {
-        $response = $this->apiLegacyRequestService->get('/_internal/doctrine/schema');
-
-        return json_decode($response->getContent(), true);
-    }
-
-    /**
-     * Get a list of differences between V1 and V2 doctrine schemas
-     * @return void
-     */
-    protected function getDiff(array $schemaV1, array $schemaV2): array {
-        $diff = [
-            'missing-entity' => [],
-            'entities' => []
-        ];
-
-        foreach ($schemaV1 as $entity => $fields) {
-            if (!isset($schemaV2[$entity])) {
-                $diff['missing-entity'][] = $entity;
-                continue;
-            }
-
-            $diff['entities'][$entity] = [];
-
-            foreach ($fields as $field => $type) {
-                if (!isset($schemaV2[$entity][$field])) {
-                    $diff['entities'][$entity][$field] = 'missing';
-                    continue;
-                }
-
-                if ($schemaV2[$entity][$field] !== $type) {
-                    $diff['entities'][$entity][$field] = 'type-diff';
-                    continue;
-                }
-            }
-        }
-
-        return $diff;
-    }
-}