|
|
@@ -3,10 +3,30 @@ declare(strict_types=1);
|
|
|
|
|
|
namespace App\Service\Doctrine\SchemaValidation;
|
|
|
|
|
|
+use ApiPlatform\Metadata\ApiResource;
|
|
|
use App\Service\ApiLegacy\ApiLegacyRequestService;
|
|
|
+use App\Service\Utils\FileUtils;
|
|
|
+use App\Service\Utils\Path;
|
|
|
+use Doctrine\Common\Collections\Collection;
|
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
|
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
|
|
+use Doctrine\ORM\Mapping\Column;
|
|
|
+use Doctrine\ORM\Mapping\Entity;
|
|
|
+use Doctrine\ORM\Mapping\JoinColumn;
|
|
|
+use Doctrine\ORM\Mapping\ManyToMany;
|
|
|
+use Doctrine\ORM\Mapping\ManyToOne;
|
|
|
use Doctrine\ORM\Mapping\MappingException;
|
|
|
+use Doctrine\ORM\Mapping\OneToMany;
|
|
|
+use Doctrine\ORM\Mapping\OneToOne;
|
|
|
+use Nette\PhpGenerator\Attribute;
|
|
|
+use Nette\PhpGenerator\ClassType;
|
|
|
+use Nette\PhpGenerator\Method;
|
|
|
+use Nette\PhpGenerator\Parameter;
|
|
|
+use Nette\PhpGenerator\PhpFile;
|
|
|
+use Nette\PhpGenerator\PhpNamespace;
|
|
|
+use Nette\PhpGenerator\Printer;
|
|
|
+use Nette\PhpGenerator\Property;
|
|
|
+use Nette\PhpGenerator\PsrPrinter;
|
|
|
use RuntimeException;
|
|
|
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
|
|
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
|
|
@@ -17,15 +37,17 @@ 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
|
|
|
{
|
|
|
+ protected array $entityNamesMapping = [];
|
|
|
+
|
|
|
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,
|
|
|
@@ -33,7 +55,7 @@ class SchemaValidationService
|
|
|
*
|
|
|
* [<entity> → Difference | [<field> → Difference]]
|
|
|
*
|
|
|
- * @return array
|
|
|
+ * @return array<string, Difference | array<Difference>>
|
|
|
* @throws ClientExceptionInterface
|
|
|
* @throws MappingException
|
|
|
* @throws RedirectionExceptionInterface
|
|
|
@@ -45,9 +67,7 @@ class SchemaValidationService
|
|
|
$schemaV1 = $this->getV1Schema();
|
|
|
$schemaV2 = $this->getV2Schema();
|
|
|
|
|
|
- $diff = $this->getDiff($schemaV1, $schemaV2, $filter);
|
|
|
-
|
|
|
- return $diff;
|
|
|
+ return $this->getDiff($schemaV1, $schemaV2, $filter);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -62,7 +82,9 @@ class SchemaValidationService
|
|
|
$schema = [];
|
|
|
|
|
|
foreach ($metadata as $entityMetadata) {
|
|
|
- $entityClassName = $this->extractClassName($entityMetadata->getName());
|
|
|
+ $entityClassName = $this->fullNameToEntityName($entityMetadata->getName());
|
|
|
+
|
|
|
+ $this->entityNamesMapping[$entityClassName] = $entityMetadata->getName();
|
|
|
|
|
|
$schema[$entityClassName] = [];
|
|
|
|
|
|
@@ -78,11 +100,6 @@ class SchemaValidationService
|
|
|
return $schema;
|
|
|
}
|
|
|
|
|
|
- protected function extractClassName(string $entity): string {
|
|
|
- $parts = explode('\\', $entity);
|
|
|
- return array_pop($parts);
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* Retrieve the V1 schema
|
|
|
*
|
|
|
@@ -92,7 +109,8 @@ class SchemaValidationService
|
|
|
* @throws ServerExceptionInterface
|
|
|
* @throws TransportExceptionInterface
|
|
|
*/
|
|
|
- protected function getV1Schema(): array {
|
|
|
+ protected function getV1Schema(): array
|
|
|
+ {
|
|
|
$response = $this->apiLegacyRequestService->get('/_internal/doctrine/schema');
|
|
|
|
|
|
return json_decode($response->getContent(), true);
|
|
|
@@ -105,7 +123,8 @@ class SchemaValidationService
|
|
|
* @param array<string, array<string| array<string|int>>> $schemaV2
|
|
|
* @return array<string, Difference | array<Difference>>
|
|
|
*/
|
|
|
- protected function getDiff(array $schemaV1, array $schemaV2, ?DiffTypeEnum $filter = null): array {
|
|
|
+ protected function getDiff(array $schemaV1, array $schemaV2, ?DiffTypeEnum $filter = null): array
|
|
|
+ {
|
|
|
$diff = [
|
|
|
];
|
|
|
|
|
|
@@ -114,11 +133,29 @@ class SchemaValidationService
|
|
|
if (!$this->isEntityInSchema($schemaV2, $entity)) {
|
|
|
// L'entité n'existe pas en V2
|
|
|
if (!$filter || $filter === DiffTypeEnum::MISSING_ENTITY) {
|
|
|
- $diff[$entity] = new Difference(DiffTypeEnum::MISSING_ENTITY, "Entity `$entity` is missing in V2");
|
|
|
+ $diff[$entity] = new Difference(
|
|
|
+ DiffTypeEnum::MISSING_ENTITY,
|
|
|
+ "Entity `$entity` is missing in V2",
|
|
|
+ $fields
|
|
|
+ );
|
|
|
}
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
+ foreach ($fields as $field => $fieldTypeV1) {
|
|
|
+ if (
|
|
|
+ !$this->isPropertyInSchema($schemaV2, $entity, $field) &&
|
|
|
+ $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
|
|
|
+ $schemaV1[$entity][$field .'s'] = $fieldTypeV1;
|
|
|
+ unset($schemaV1[$entity][$field]);
|
|
|
+ $fields = $schemaV1[$entity];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
$diff[$entity] = [];
|
|
|
|
|
|
foreach ($fields as $field => $fieldTypeV1) {
|
|
|
@@ -126,9 +163,22 @@ class SchemaValidationService
|
|
|
if (!$this->isPropertyInSchema($schemaV2, $entity, $field)) {
|
|
|
// Le champ n'existe pas en V2
|
|
|
if ($this->isRelationField($schemaV1, $entity, $field)) {
|
|
|
- $diff[$entity][$field] = new Difference(DiffTypeEnum::MISSING_RELATION, "Relation " . $this->getRelationTypeLabel($fieldTypeV1) . " `$field` is missing in V2");
|
|
|
+ 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",
|
|
|
+ $fieldTypeV1,
|
|
|
+ );
|
|
|
} else {
|
|
|
- $diff[$entity][$field] = new Difference(DiffTypeEnum::MISSING_PROPERTY, "Property `$field` is missing in V2");
|
|
|
+ $diff[$entity][$field] = new Difference(
|
|
|
+ DiffTypeEnum::MISSING_PROPERTY,
|
|
|
+ "Property `$field` is missing in V2",
|
|
|
+ $fieldTypeV1
|
|
|
+ );
|
|
|
}
|
|
|
continue;
|
|
|
}
|
|
|
@@ -140,11 +190,19 @@ class SchemaValidationService
|
|
|
// 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`)");
|
|
|
+ $diff[$entity][$field] = new Difference(
|
|
|
+ DiffTypeEnum::DIFFERENT_TYPE,
|
|
|
+ "Property `$field` has a different type (V1: `$fieldTypeV1`, V2: `$fieldTypeV2`)",
|
|
|
+ $fieldTypeV1
|
|
|
+ );
|
|
|
}
|
|
|
} 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");
|
|
|
+ $diff[$entity][$field] = new Difference(
|
|
|
+ DiffTypeEnum::DIFFERENT_TYPE,
|
|
|
+ "Property $field is a relation in V1 but not in V2",
|
|
|
+ $fieldTypeV1
|
|
|
+ );
|
|
|
} else {
|
|
|
// Le champ est une relation dans les deux schémas, on compare leurs configurations
|
|
|
$difference = $this->getRelationDiff($fieldTypeV1, $fieldTypeV2);
|
|
|
@@ -158,7 +216,9 @@ class SchemaValidationService
|
|
|
if ($filter !== null) {
|
|
|
$diff[$entity] = array_filter(
|
|
|
$diff[$entity],
|
|
|
- function (Difference $difference) use ($filter) { return $difference->getType() === $filter; }
|
|
|
+ function (Difference $difference) use ($filter) {
|
|
|
+ return $difference->getType() === $filter;
|
|
|
+ }
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
@@ -173,7 +233,8 @@ class SchemaValidationService
|
|
|
* @param string $entity
|
|
|
* @return bool
|
|
|
*/
|
|
|
- protected function isEntityInSchema(array $schema, string $entity): bool {
|
|
|
+ protected function isEntityInSchema(array $schema, string $entity): bool
|
|
|
+ {
|
|
|
return isset($schema[$entity]);
|
|
|
}
|
|
|
|
|
|
@@ -185,19 +246,21 @@ class SchemaValidationService
|
|
|
* @param string $property
|
|
|
* @return bool
|
|
|
*/
|
|
|
- protected function isPropertyInSchema(array $schema, string $entity, string $property): bool {
|
|
|
+ protected function isPropertyInSchema(array $schema, string $entity, string $property): bool
|
|
|
+ {
|
|
|
return isset($schema[$entity][$property]);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Is the given field is a relation field.
|
|
|
+ * Is the given field a relation field.
|
|
|
*
|
|
|
- * @param array<string, array<string | array<string|int>> $schema
|
|
|
+ * @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 {
|
|
|
+ protected function isRelationField(array $schema, string $entity, string $relation): bool
|
|
|
+ {
|
|
|
return isset($schema[$entity][$relation]) && is_array($schema[$entity][$relation]);
|
|
|
}
|
|
|
|
|
|
@@ -207,7 +270,8 @@ class SchemaValidationService
|
|
|
* @param array<string, string|int> $relation
|
|
|
* @return string
|
|
|
*/
|
|
|
- protected function getRelationTypeLabel(array $relation): string {
|
|
|
+ protected function getRelationTypeLabel(array $relation): string
|
|
|
+ {
|
|
|
if ($relation['type'] === ClassMetadataInfo::ONE_TO_ONE) {
|
|
|
return 'OneToOne';
|
|
|
} elseif ($relation['type'] === ClassMetadataInfo::MANY_TO_ONE) {
|
|
|
@@ -228,12 +292,13 @@ class SchemaValidationService
|
|
|
* @param array<string, string> $relationCompared
|
|
|
* @return Difference|null
|
|
|
*/
|
|
|
- protected function getRelationDiff(array $relationReference, array $relationCompared): 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)}"
|
|
|
+ "Relation type is different : {$this->getRelationTypeLabel($relationReference)} !== {$this->getRelationTypeLabel($relationCompared)}",
|
|
|
+ $relationReference
|
|
|
);
|
|
|
}
|
|
|
|
|
|
@@ -242,7 +307,8 @@ class SchemaValidationService
|
|
|
) {
|
|
|
return new Difference(
|
|
|
DiffTypeEnum::DIFFERENT_RELATION_CONFIGURATION,
|
|
|
- "Relation configuration is different (targetEntity)"
|
|
|
+ "Relation configuration is different (targetEntity)",
|
|
|
+ $relationReference
|
|
|
);
|
|
|
}
|
|
|
|
|
|
@@ -251,7 +317,8 @@ class SchemaValidationService
|
|
|
) {
|
|
|
return new Difference(
|
|
|
DiffTypeEnum::DIFFERENT_RELATION_CONFIGURATION,
|
|
|
- "Relation configuration is different (mappedBy)"
|
|
|
+ "Relation configuration is different (mappedBy)",
|
|
|
+ $relationReference
|
|
|
);
|
|
|
}
|
|
|
|
|
|
@@ -264,8 +331,375 @@ class SchemaValidationService
|
|
|
* @param string $fullName
|
|
|
* @return string
|
|
|
*/
|
|
|
- protected function fullNameToEntityName(string $fullName): string {
|
|
|
+ protected function fullNameToEntityName(string $fullName): string
|
|
|
+ {
|
|
|
$parts = explode('\\', $fullName);
|
|
|
return array_pop($parts);
|
|
|
}
|
|
|
+
|
|
|
+ protected function getFullNameFromEntityName(string $entityName): ?string
|
|
|
+ {
|
|
|
+ return $this->entityNamesMapping[$entityName] ?? null;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function getNamespaceFromEntityName(string $entityName): ?string
|
|
|
+ {
|
|
|
+ $fullName = $this->getFullNameFromEntityName($entityName);
|
|
|
+ if (!$fullName) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ $parts = explode('\\', $fullName);
|
|
|
+ array_pop($parts);
|
|
|
+ return implode('\\', $parts);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
+
|
|
|
+ foreach ($diff as $entity => $differences) {
|
|
|
+ if (empty($differences)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $class = $this->makeSnippetEntityClass($entity);
|
|
|
+
|
|
|
+ $methods = [];
|
|
|
+
|
|
|
+ if (is_array($differences)) {
|
|
|
+ foreach ($differences as $field => $difference) {
|
|
|
+ if (!is_array($difference->getExpectedType())
|
|
|
+ ) {
|
|
|
+ $prop = $this->makeSnippetEntitySimpleProp($field, $difference->getExpectedType());
|
|
|
+ $methods[] = $this->makeSnippetGetterForProp($prop);
|
|
|
+ $methods[] = $this->makeSnippetSetterForProp($prop);
|
|
|
+
|
|
|
+ } else {
|
|
|
+ $prop = $this->makeSnippetEntityCollectionProp($field, $difference->getExpectedType());
|
|
|
+ $methods[] = $this->makeSnippetGetterForProp($prop);
|
|
|
+
|
|
|
+ if (
|
|
|
+ isset($difference->getExpectedType()['type']) &&
|
|
|
+ in_array($difference->getExpectedType()['type'], [ClassMetadataInfo::ONE_TO_MANY, ClassMetadataInfo::MANY_TO_MANY])
|
|
|
+ ) {
|
|
|
+ $methods[] = $this->makeSnippetAdderForCollection($prop);
|
|
|
+ $methods[] = $this->makeSnippetRemoverForCollection($prop);
|
|
|
+ } else {
|
|
|
+ $methods[] = $this->makeSnippetSetterForProp($prop);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $class->addMember($prop);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ $class->addProperty('id', 'int');
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($methods as $method) {
|
|
|
+ $class->addMember($method);
|
|
|
+ }
|
|
|
+
|
|
|
+ $file = new PhpFile;
|
|
|
+ $file->addComment('This file is auto-generated.');
|
|
|
+ $file->setStrictTypes();
|
|
|
+
|
|
|
+ $namespaceValue = $this->getNamespaceFromEntityName($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');
|
|
|
+
|
|
|
+ if (is_array($differences)) {
|
|
|
+ foreach ($this->getEntityToImportFromRelations($differences) as $use) {
|
|
|
+ $namespace->addUse($use);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $namespace->add($class);
|
|
|
+
|
|
|
+ $file->addNamespace($namespace);
|
|
|
+
|
|
|
+ $fullname = $this->getFullNameFromEntityName($entity) ?? $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);
|
|
|
+ }
|
|
|
+
|
|
|
+ $f = fopen($fileName, 'w+');
|
|
|
+ try {
|
|
|
+ fwrite($f, $printer->printFile($file));
|
|
|
+ } finally {
|
|
|
+ fclose($f);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function makeSnippetEntityClass(string $entity): ClassType
|
|
|
+ {
|
|
|
+ $class = new ClassType($entity);
|
|
|
+
|
|
|
+ $class->setAttributes([
|
|
|
+ new Attribute(ApiResource::class, ['operations' => []]),
|
|
|
+ new Attribute(Entity::class, [])
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return $class;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function getEntityToImportFromRelations(array $differences): array
|
|
|
+ {
|
|
|
+ $imports = [];
|
|
|
+
|
|
|
+ foreach ($differences as $field => $difference) {
|
|
|
+ if (
|
|
|
+ !is_array($difference->getExpectedType()) ||
|
|
|
+ !in_array(isset($difference->getExpectedType()['type']), [ClassMetadataInfo::ONE_TO_ONE, ClassMetadataInfo::MANY_TO_ONE]) ||
|
|
|
+ !isset($difference->getExpectedType()['targetEntity'])
|
|
|
+ ) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $fullName = $this->fullNameToEntityName($difference->getExpectedType()['targetEntity']);
|
|
|
+ if (!$fullName) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $entityName = $this->getFullNameFromEntityName($fullName);
|
|
|
+ if (!$entityName) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $imports[] = $entityName;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $imports;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+
|
|
|
+ $prop = new Property($name);
|
|
|
+ $prop->setPrivate();
|
|
|
+ $prop->setType($php_type);
|
|
|
+
|
|
|
+ if ($type === 'text') {
|
|
|
+ $prop->addAttribute(Column::class, ['length' => 255, 'options' => ['nullable' => true]]);
|
|
|
+ } elseif ($type === 'integer') {
|
|
|
+ $prop->addAttribute(Column::class, ['type' => 'integer', 'options' => ['nullable' => true]]);
|
|
|
+ } elseif ($type === 'boolean') {
|
|
|
+ $prop->addAttribute(Column::class, ['options' => ['default' => false]]);
|
|
|
+ } elseif ($type === 'datetime') {
|
|
|
+ $prop->addAttribute(Column::class, ['type' => 'date', 'options' => ['nullable' => true]]);
|
|
|
+ } elseif ($type === 'json_array') {
|
|
|
+ $prop->addAttribute(Column::class, ['type' => 'json', 'options' => ['nullable' => true]]);
|
|
|
+ } else {
|
|
|
+ $prop->addAttribute(Column::class, []);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $prop;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function makeSnippetEntityCollectionProp(string $name, array $type): Property
|
|
|
+ {
|
|
|
+ $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->fullNameToEntityName($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'];
|
|
|
+ }
|
|
|
+
|
|
|
+ $prop->addAttribute(OneToMany::class, $options);
|
|
|
+ } else if ($type['type'] === ClassMetadataInfo::MANY_TO_MANY) {
|
|
|
+ $prop->setType(Collection::class);
|
|
|
+
|
|
|
+ $options = [];
|
|
|
+ if (isset($type['mappedBy'])) {
|
|
|
+ $options['mappedBy'] = $type['mappedBy'];
|
|
|
+ }
|
|
|
+ if (isset($type['targetEntity'])) {
|
|
|
+ $options['targetEntity'] = $this->fullNameToEntityName($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'];
|
|
|
+ }
|
|
|
+
|
|
|
+ $prop->addAttribute(ManyToMany::class, $options);
|
|
|
+ } else if ($type['type'] === ClassMetadataInfo::ONE_TO_ONE) {
|
|
|
+ $newType = 'mixed';
|
|
|
+ if (isset($type['targetEntity'])) {
|
|
|
+ $targetEntityName = $this->fullNameToEntityName($type['targetEntity']);
|
|
|
+ $localFullName = $this->getFullNameFromEntityName($targetEntityName);
|
|
|
+ if ($localFullName) {
|
|
|
+ $newType = $localFullName;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $prop->setType($newType);
|
|
|
+
|
|
|
+ $options = [];
|
|
|
+ if (isset($type['inversedBy'])) {
|
|
|
+ $options['inversedBy'] = $type['inversedBy'];
|
|
|
+ }
|
|
|
+ if (isset($type['targetEntity'])) {
|
|
|
+ $options['targetEntity'] = $this->fullNameToEntityName($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->fullNameToEntityName($type['targetEntity']);
|
|
|
+ $localFullName = $this->getFullNameFromEntityName($targetEntityName);
|
|
|
+ if ($localFullName) {
|
|
|
+ $newType = $localFullName;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $prop->setType($newType);
|
|
|
+
|
|
|
+ $options = [];
|
|
|
+ if (isset($type['cascade'])) {
|
|
|
+ $options['cascade'] = $type['cascade'];
|
|
|
+ }
|
|
|
+ if (isset($type['inversedBy'])) {
|
|
|
+ $options['inversedBy'] = $type['inversedBy'];
|
|
|
+ }
|
|
|
+
|
|
|
+ $prop->addAttribute(ManyToOne::class, $options);
|
|
|
+ $prop->addAttribute(JoinColumn::class, ['referencedColumnName' => 'id', 'nullable' => false, 'onDelete' => 'SET NULL']);
|
|
|
+
|
|
|
+ } else {
|
|
|
+ throw new RuntimeException('Unknown relation type');
|
|
|
+ }
|
|
|
+
|
|
|
+ return $prop;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function makeSnippetGetterForProp(Property $prop): Method {
|
|
|
+ $method = new Method('get' . ucfirst($prop->getName()));
|
|
|
+ $method->setReturnType($prop->getType());
|
|
|
+ $method->setBody('return $this->' . $prop->getName() . ';');
|
|
|
+ return $method;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function makeSnippetSetterForProp(Property $prop): Method {
|
|
|
+ $method = new Method('set' . ucfirst($prop->getName()));
|
|
|
+
|
|
|
+ $parameter = new Parameter($prop->getName());
|
|
|
+ $parameter->setType($prop->getType());
|
|
|
+ $method->setParameters([$parameter]);
|
|
|
+
|
|
|
+ $method->setReturnType('self');
|
|
|
+ $method->setBody(
|
|
|
+ implode(
|
|
|
+ "\n",
|
|
|
+ [
|
|
|
+ '$this->' . $prop->getName() . ' = $' . $prop->getName() . ';',
|
|
|
+ 'return $this;',
|
|
|
+ ]
|
|
|
+ )
|
|
|
+ );
|
|
|
+ return $method;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function makeSnippetAdderForCollection(Property $prop): Method {
|
|
|
+
|
|
|
+ $singularPropName = rtrim($prop->getName(), 's');
|
|
|
+
|
|
|
+ $method = new Method('add' . ucfirst($singularPropName));
|
|
|
+
|
|
|
+ $parameter = new Parameter($singularPropName);
|
|
|
+ $parameter->setType($prop->getType());
|
|
|
+ $method->setParameters([$parameter]);
|
|
|
+
|
|
|
+ $method->setReturnType('self');
|
|
|
+ $method->setBody(
|
|
|
+ implode(
|
|
|
+ "\n",
|
|
|
+ [
|
|
|
+ 'if (!$this->' . $prop->getName() . '->contains($' . $singularPropName . ')) {',
|
|
|
+ ' $this->' . $prop->getName() . '[] = $' . $singularPropName . ';',
|
|
|
+ '}',
|
|
|
+ '',
|
|
|
+ 'return $this;',
|
|
|
+ ]
|
|
|
+ )
|
|
|
+ );
|
|
|
+ return $method;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function makeSnippetRemoverForCollection(Property $prop): Method {
|
|
|
+ $singularPropName = rtrim($prop->getName(), 's');
|
|
|
+
|
|
|
+ $method = new Method('remove' . ucfirst($singularPropName));
|
|
|
+
|
|
|
+ $parameter = new Parameter($singularPropName);
|
|
|
+ $parameter->setType($prop->getType());
|
|
|
+ $method->setParameters([$parameter]);
|
|
|
+
|
|
|
+ $method->setReturnType('self');
|
|
|
+ $method->setBody(
|
|
|
+ implode(
|
|
|
+ "\n",
|
|
|
+ [
|
|
|
+ '$this->' . $prop->getName() . '->removeElement($' . $singularPropName . ');',
|
|
|
+ '',
|
|
|
+ 'return $this;',
|
|
|
+ ]
|
|
|
+ )
|
|
|
+ );
|
|
|
+ return $method;
|
|
|
+ }
|
|
|
}
|