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

refactor SchemaValidationService and SchemaSnippetsMaker

Olivier Massot 1 год назад
Родитель
Сommit
4897064926

+ 190 - 137
src/Service/Doctrine/SchemaValidation/SchemaSnippetsMaker.php

@@ -24,7 +24,6 @@ use Nette\PhpGenerator\PhpFile;
 use Nette\PhpGenerator\PhpNamespace;
 use Nette\PhpGenerator\Property;
 use Nette\PhpGenerator\PsrPrinter;
-use RuntimeException;
 
 /**
  * Service produisant les snippets des entités, propriétés et méthodes
@@ -50,14 +49,8 @@ class SchemaSnippetsMaker
      */
     public function makeSnippets(array $diff): void
     {
-        $snippetsDir = Path::join(Path::getProjectDir(), 'schema_validation_snippets');
-
-        if (is_dir($snippetsDir)) {
-            FileUtils::rrmDir($snippetsDir);
-        }
-        mkdir($snippetsDir, 0777, true);
-
-        $printer = new PsrPrinter;
+        $snippetsDir = $this->getSnippetsDir();
+        $this->prepareSnippetsDir($snippetsDir);
 
         foreach ($diff as $entity => $differences) {
             if (empty($differences)) {
@@ -70,8 +63,8 @@ class SchemaSnippetsMaker
 
             if (is_array($differences)) {
                 foreach ($differences as $field => $difference) {
-                    if (!is_array($difference->getExpectedType())
-                    ) {
+                    if (!is_array($difference->getExpectedType()))
+                    {
                         $prop = $this->makeSnippetEntitySimpleProp($field, $difference->getExpectedType());
                         $methods[] = $this->makeSnippetGetterForProp($prop);
                         $methods[] = $this->makeSnippetSetterForProp($prop);
@@ -93,24 +86,17 @@ class SchemaSnippetsMaker
                     $class->addMember($prop);
                 }
             } else {
-                $class->addProperty('id', 'int');
+                $prop = $this->makeIdPropertySnippet();
+                $class->addMember($prop);
             }
 
             foreach ($methods as $method) {
                 $class->addMember($method);
             }
 
-            $file = new PhpFile;
-            $file->addComment('This file is auto-generated.');
-            $file->setStrictTypes();
+            $file = $this->makeFileSnippet();
 
-            $namespaceValue = $this->entityUtils->getNamespaceFromName($entity) ?? ('App\\Entity\\' . $entity);
-
-            $namespace = new PhpNamespace($namespaceValue);
-            $namespace->addUse(ApiResource::class);
-            $namespace->addUse('Doctrine\Common\Collections\ArrayCollection');
-            $namespace->addUse('Doctrine\Common\Collections\Collection');
-            $namespace->addUse('Doctrine\ORM\Mapping', 'ORM');
+            $namespace = $this->makeNamespaceSnippet($entity);
 
             if (is_array($differences)) {
                 foreach ($this->getEntityToImportFromRelations($differences) as $use) {
@@ -122,30 +108,9 @@ class SchemaSnippetsMaker
 
             $file->addNamespace($namespace);
 
-            try {
-                $fullname = $this->entityUtils->getFullNameFromEntityName($entity);
-            } catch (\LogicException) {
-                $fullname = $entity;
-            }
-
-            if ($fullname === $entity) {
-                var_dump('Entity namespace not found: ' . $entity);
-            }
-
-            $relativePath = str_replace('\\', '/', $fullname) . '.php';
-
-            $fileName = Path::join($snippetsDir, $relativePath);
-
-            if (!is_dir(dirname($fileName))) {
-                mkdir(dirname($fileName), 0777, true);
-            }
+            $fileName = $this->getSnippetPath($snippetsDir, $entity);
 
-            $f = fopen($fileName, 'w+');
-            try {
-                fwrite($f, $printer->printFile($file));
-            } finally {
-                fclose($f);
-            }
+            $this->writeSnippet($file, $fileName);
         }
     }
 
@@ -168,6 +133,69 @@ class SchemaSnippetsMaker
         return $class;
     }
 
+    /**
+     * Retourne le chemin absolu vers le répertoire dans lesquels sont créés les snippets
+     * @return string
+     */
+    protected function getSnippetsDir(): string
+    {
+        return Path::join(Path::getProjectDir(), 'schema_validation_snippets');
+    }
+
+    /**
+     * Vide et ajuste les droits du répertoire des snippets
+     *
+     * @param string $snippetsDir
+     * @return void
+     */
+    protected function prepareSnippetsDir(string $snippetsDir): void {
+        if (is_dir($snippetsDir)) {
+            FileUtils::rrmDir($snippetsDir);
+        }
+        mkdir($snippetsDir, 0777, true);
+    }
+
+    /**
+     * Retourne le chemin absolu du snippet donné
+     *
+     * @param string $snippetsDir
+     * @param string $entity
+     * @return string
+     */
+    protected function getSnippetPath(string $snippetsDir, string $entity): string
+    {
+        try {
+            $fullName = $this->entityUtils->getFullNameFromEntityName($entity);
+        } catch (\LogicException) {
+            $fullName = '_NameSpaceNotFound/' . $entity;
+        }
+
+        $relativePath = str_replace('\\', '/', $fullName) . '.php';
+
+        return Path::join($snippetsDir, $relativePath);
+    }
+
+    /**
+     * Créé le fichier du snippet sur le disque
+     *
+     * @param PhpFile $phpFile
+     * @param string $fileName
+     * @return void
+     */
+    protected function writeSnippet(PhpFile $phpFile, string $fileName): void {
+        if (!is_dir(dirname($fileName))) {
+            mkdir(dirname($fileName), 0777, true);
+        }
+
+        $printer = new PsrPrinter;
+        $f = fopen($fileName, 'w+');
+        try {
+            fwrite($f, $printer->printFile($phpFile));
+        } finally {
+            fclose($f);
+        }
+    }
+
     /**
      * Parcourt les propriétés de type relations OneToOne ou ManyToOne pour lister
      * les classes d'entités à importer (pour le typage de ces propriétés).
@@ -205,6 +233,81 @@ class SchemaSnippetsMaker
         return $imports;
     }
 
+    /**
+     * Obtient le namespace pour l'entité donnée
+     *
+     * @param string $entity
+     * @return string
+     */
+    protected function getNamespaceValue(string $entity): string
+    {
+        return $this->entityUtils->getNamespaceFromName($entity) ?? ('App\\Entity\\' . $entity);
+    }
+
+    /**
+     * Construit l'objet PhpFile
+     * @see https://api.nette.org/php-generator/master/Nette/PhpGenerator/PhpFile.html
+     *
+     * @return PhpFile
+     */
+    protected function makeFileSnippet(): PhpFile
+    {
+        $file = new PhpFile;
+        $file->addComment('This file is auto-generated.');
+        $file->setStrictTypes();
+
+        return $file;
+    }
+
+    /**
+     * Construit l'objet PhpNamespace
+     * @see https://api.nette.org/php-generator/master/Nette/PhpGenerator/PhpNamespace.html
+     *
+     * @param string $entity
+     * @return PhpNamespace
+     */
+    protected function makeNamespaceSnippet(string $entity): PhpNamespace
+    {
+        $namespaceValue = $this->getNamespaceValue($entity);
+
+        $namespace = new PhpNamespace($namespaceValue);
+        $namespace->addUse(ApiResource::class);
+        $namespace->addUse('Doctrine\Common\Collections\ArrayCollection');
+        $namespace->addUse('Doctrine\Common\Collections\Collection');
+        $namespace->addUse('Doctrine\ORM\Mapping', 'ORM');
+
+        return $namespace;
+    }
+
+    /**
+     * Construit l'objet Property pour le champs 'id' d'une entité
+     * @see https://api.nette.org/php-generator/master/Nette/PhpGenerator/Property.html
+     *
+     * @return Property
+     */
+    protected function makeIdPropertySnippet(): Property
+    {
+        $prop = new Property('id');
+        $prop->setPrivate();
+        $prop->setType('int');
+        $prop->addAttribute('ORM\Id');
+        $prop->addAttribute('ORM\Column');
+        $prop->addAttribute('ORM\GeneratedValue');
+        return $prop;
+    }
+
+    protected function getPhpTypeFromDoctrineType(string $doctrineType): string
+    {
+        return [
+            'text' => 'string',
+            'boolean' => 'bool',
+            'integer' => 'int',
+            'datetime' => '?\DateTimeInterface',
+            'json_array' => 'array',
+        ][$doctrineType] ?? 'mixed';
+
+    }
+
     /**
      * Make a Property object for a simple field (not a relation)
      * @see https://api.nette.org/php-generator/master/Nette/PhpGenerator/Property.html
@@ -215,12 +318,7 @@ class SchemaSnippetsMaker
      */
     protected function makeSnippetEntitySimpleProp(string $name, string $type): Property
     {
-        $php_type = $type;
-        $php_type = str_replace('text', 'string', $php_type);
-        $php_type = str_replace('boolean', 'bool', $php_type);
-        $php_type = str_replace('integer', 'int', $php_type);
-        $php_type = str_replace('datetime', '?\DateTimeInterface', $php_type);
-        $php_type = str_replace('json_array', 'array', $php_type);
+        $php_type = $this->getPhpTypeFromDoctrineType($type);
 
         $prop = new Property($name);
         $prop->setPrivate();
@@ -248,7 +346,7 @@ class SchemaSnippetsMaker
      * @see https://api.nette.org/php-generator/master/Nette/PhpGenerator/Property.html
      *
      * @param string $name
-     * @param array $type
+     * @param array<mixed> $type
      * @return Property
      */
     protected function makeSnippetEntityCollectionProp(string $name, array $type): Property
@@ -256,101 +354,56 @@ class SchemaSnippetsMaker
         $prop = new Property($name);
         $prop->setType('Collection');
 
-        if ($type['type'] === ClassMetadataInfo::ONE_TO_MANY) {
-            $prop->setType(Collection::class);
-
-            $options = [];
-            if (isset($type['mappedBy'])) {
-                $options['mappedBy'] = $type['mappedBy'];
-            }
-            if (isset($type['targetEntity'])) {
-                $options['targetEntity'] = $this->entityUtils->getEntityNameFromFullName($type['targetEntity']) . '::class';
-            }
-            if (isset($type['inversedBy'])) {
-                $options['inversedBy'] = $type['inversedBy'];
-            }
-            if (isset($type['cascade'])) {
-                $options['cascade'] = $type['cascade'];
-            } else {
-                $options['cascade'] = ['persist'];
-            }
-            if (isset($type['orphanRemoval'])) {
-                $options['orphanRemoval'] = $type['orphanRemoval'];
+        if (
+            isset($type['type']) &&
+            $type['type'] === ClassMetadataInfo::ONE_TO_ONE || $type['type'] === ClassMetadataInfo::MANY_TO_ONE
+        ) {
+            $targetEntityName = $this->entityUtils->getEntityNameFromFullName($type['targetEntity']);
+            try {
+                $newType = $this->entityUtils->getFullNameFromEntityName($targetEntityName);
+            } catch (\LogicException) {
+                $newType = 'mixed';
             }
+            $prop->setType($newType);
 
-            $prop->addAttribute(OneToMany::class, $options);
-        } else if ($type['type'] === ClassMetadataInfo::MANY_TO_MANY) {
+        } else {
             $prop->setType(Collection::class);
+        }
 
-            $options = [];
-            if (isset($type['mappedBy'])) {
-                $options['mappedBy'] = $type['mappedBy'];
-            }
-            if (isset($type['targetEntity'])) {
-                $options['targetEntity'] = $this->entityUtils->getEntityNameFromFullName($type['targetEntity']) . '::class';
-            }
-            if (isset($type['inversedBy'])) {
-                $options['inversedBy'] = $type['inversedBy'];
-            }
-            if (isset($type['cascade'])) {
-                $options['cascade'] = $type['cascade'];
-            } else {
-                $options['cascade'] = ['persist'];
-            }
-            if (isset($type['orphanRemoval'])) {
-                $options['orphanRemoval'] = $type['orphanRemoval'];
-            }
+        $options = [];
+        if (isset($type['mappedBy'])) {
+            $options['mappedBy'] = $type['mappedBy'];
+        }
 
-            $prop->addAttribute(ManyToMany::class, $options);
-        } else if ($type['type'] === ClassMetadataInfo::ONE_TO_ONE) {
-            $newType = 'mixed';
-            if (isset($type['targetEntity'])) {
-                $targetEntityName = $this->entityUtils->getEntityNameFromFullName($type['targetEntity']);
-                try {
-                    $newType = $this->entityUtils->getFullNameFromEntityName($targetEntityName);
-                } catch (\LogicException) {}
-            }
+        if (isset($type['targetEntity'])) {
+            $options['targetEntity'] = $this->entityUtils->getEntityNameFromFullName($type['targetEntity']) . '::class';
+        }
 
-            $prop->setType($newType);
+        if (isset($type['inversedBy'])) {
+            $options['inversedBy'] = $type['inversedBy'];
+        }
 
-            $options = [];
-            if (isset($type['inversedBy'])) {
-                $options['inversedBy'] = $type['inversedBy'];
-            }
-            if (isset($type['targetEntity'])) {
-                $options['targetEntity'] = $this->entityUtils->getEntityNameFromFullName($type['targetEntity']) . '::class';
-            }
-            if (isset($type['cascade'])) {
-                $options['cascade'] = $type['cascade'];
-            } else {
-                $options['cascade'] = ['persist'];
-            }
-            $prop->addAttribute(OneToOne::class, $options);
-            $prop->addAttribute(JoinColumn::class, []);
-
-        } else if ($type['type'] === ClassMetadataInfo::MANY_TO_ONE) {
-            $newType = 'mixed';
-            if (isset($type['targetEntity'])) {
-                $targetEntityName = $this->entityUtils->getEntityNameFromFullName($type['targetEntity']);
-                try {
-                    $newType = $this->entityUtils->getFullNameFromEntityName($targetEntityName);
-                } catch (\LogicException) {}
-            }
-            $prop->setType($newType);
+        if (isset($type['cascade'])) {
+            $options['cascade'] = $type['cascade'];
+        } else {
+            $options['cascade'] = ['persist'];
+        }
 
-            $options = [];
-            if (isset($type['cascade'])) {
-                $options['cascade'] = $type['cascade'];
-            }
-            if (isset($type['inversedBy'])) {
-                $options['inversedBy'] = $type['inversedBy'];
-            }
+        if (isset($type['orphanRemoval'])) {
+            $options['orphanRemoval'] = $type['orphanRemoval'];
+        }
 
-            $prop->addAttribute(ManyToOne::class, $options);
-            $prop->addAttribute(JoinColumn::class, ['referencedColumnName' => 'id', 'nullable' => false, 'onDelete' => 'SET NULL']);
+        $relationClassNames = [
+            ClassMetadataInfo::ONE_TO_MANY => OneToMany::class,
+            ClassMetadataInfo::MANY_TO_MANY => ManyToMany::class,
+            ClassMetadataInfo::MANY_TO_ONE => ManyToOne::class,
+            ClassMetadataInfo::ONE_TO_ONE => OneToOne::class,
+        ];
 
-        } else {
-            throw new RuntimeException('Unknown relation type');
+        $prop->addAttribute($relationClassNames[$type['type']], $options);
+
+        if ($type['type'] === ClassMetadataInfo::MANY_TO_ONE) {
+            $prop->addAttribute(JoinColumn::class, ['referencedColumnName' => 'id', 'nullable' => false, 'onDelete' => 'SET NULL']);
         }
 
         return $prop;

+ 10 - 19
src/Service/Doctrine/SchemaValidation/SchemaValidationService.php

@@ -125,8 +125,8 @@ class SchemaValidationService
                     $this->isRelationField($schemaV1, $entity, $field) &&
                     $this->isPropertyInSchema($schemaV2, $entity, $field .'s')
                 ) {
-                    // Le champ existe en V2, mais il a été passé au pluriel, par exemple : $contactPoint devenu $contactPoints
-                    // Pour éviter les faux positifs, on renomme le champ dans le schéma v1
+                    // Le champ existe en V2, mais il a été mis au pluriel, par exemple : $contactPoint devenu $contactPoints
+                    // Pour éviter les faux positifs, on renomme le champ dans le schéma V1
                     $schemaV1[$entity][$field .'s'] = $fieldTypeV1;
                     unset($schemaV1[$entity][$field]);
                     $fields = $schemaV1[$entity];
@@ -140,11 +140,6 @@ class SchemaValidationService
                 if (!$this->isPropertyInSchema($schemaV2, $entity, $field)) {
                     // Le champ n'existe pas en V2
                     if ($this->isRelationField($schemaV1, $entity, $field)) {
-                        if ($this->isPropertyInSchema($schemaV2, $entity, $field .'s')) {
-
-                            continue;
-                        }
-
                         $diff[$entity][$field] = new Difference(
                             DiffTypeEnum::MISSING_RELATION,
                             "Relation " . $this->getRelationTypeLabel($fieldTypeV1) . " `$field` is missing in V2",
@@ -225,7 +220,7 @@ class SchemaValidationService
      */
     protected function isPropertyInSchema(array $schema, string $entity, string $property): bool
     {
-        return isset($schema[$entity][$property]);
+        return $this->isEntityInSchema($schema, $entity) && isset($schema[$entity][$property]);
     }
 
     /**
@@ -238,7 +233,7 @@ class SchemaValidationService
      */
     protected function isRelationField(array $schema, string $entity, string $relation): bool
     {
-        return isset($schema[$entity][$relation]) && is_array($schema[$entity][$relation]);
+        return $this->isPropertyInSchema($schema, $entity, $relation) && is_array($schema[$entity][$relation]);
     }
 
     /**
@@ -290,15 +285,11 @@ class SchemaValidationService
      */
     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');
+        return [
+            ClassMetadataInfo::ONE_TO_ONE => 'OneToOne',
+            ClassMetadataInfo::MANY_TO_ONE => 'ManyToOne',
+            ClassMetadataInfo::ONE_TO_MANY => 'OneToMany',
+            ClassMetadataInfo::MANY_TO_MANY => 'ManyToMany',
+        ][$relation['type']] ?? 'Unknown';
     }
 }