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