diff --git a/EntityGeneratorBundle/Doctrine/Dto/DtoManipulator.php b/EntityGeneratorBundle/Doctrine/Dto/DtoManipulator.php index 82f03f2..6fbfeef 100644 --- a/EntityGeneratorBundle/Doctrine/Dto/DtoManipulator.php +++ b/EntityGeneratorBundle/Doctrine/Dto/DtoManipulator.php @@ -17,6 +17,7 @@ use PhpParser\Parser; use Symfony\Bundle\MakerBundle\Doctrine\BaseCollectionRelation; use Symfony\Bundle\MakerBundle\Doctrine\BaseSingleRelation; +use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper; use Symfony\Bundle\MakerBundle\Doctrine\RelationManyToMany; use Symfony\Bundle\MakerBundle\Doctrine\RelationManyToOne; use Symfony\Bundle\MakerBundle\Doctrine\RelationOneToMany; @@ -49,8 +50,10 @@ final class DtoManipulator implements ManipulatorInterface /** @var UseStatement[] */ private $useStatements = []; - public function __construct(string $sourceCode) - { + public function __construct( + string $sourceCode, + private DoctrineHelper $doctrineHelper, + ) { $this->lexer = new Lexer\Emulative([ 'usedAttributes' => [ 'comments', @@ -418,9 +421,25 @@ private function addSingularRelation(BaseSingleRelation $relation, $classMetadat $classMetadata, ); + $targetClassName = substr( + $relation->getTargetClassName(), + 0, + -3 + ); + + /** @var \IvozDevTools\EntityGeneratorBundle\Doctrine\Metadata\ClassMetadata $targetClassMetadata */ + $targetClassMetadata = $this->doctrineHelper->getMetadata($targetClassName); + $identifier = $targetClassMetadata->getIdentifier(); + $targetPkField = $targetClassMetadata->getFieldMapping( + current($identifier) + ); + $targetPkHint = $this->getEntityTypeHint( + $targetPkField["type"] + ); + $this->addIdGetter( $relation->getPropertyName(), - $relation->getCustomReturnType() ?: $typeHint, + $relation->getCustomReturnType() ?: $targetPkHint, true, [] ); diff --git a/EntityGeneratorBundle/Doctrine/Dto/DtoRegenerator.php b/EntityGeneratorBundle/Doctrine/Dto/DtoRegenerator.php index fd42bfd..8040831 100644 --- a/EntityGeneratorBundle/Doctrine/Dto/DtoRegenerator.php +++ b/EntityGeneratorBundle/Doctrine/Dto/DtoRegenerator.php @@ -162,7 +162,8 @@ private function createClassManipulator( { $classContent = $content ?? $this->fileManager->getFileContents($classPath); return new DtoManipulator( - $classContent + $classContent, + $this->doctrineHelper ); } diff --git a/EntityGeneratorBundle/Doctrine/Dto/FkGetter.php b/EntityGeneratorBundle/Doctrine/Dto/FkGetter.php index d276de1..d7e262e 100644 --- a/EntityGeneratorBundle/Doctrine/Dto/FkGetter.php +++ b/EntityGeneratorBundle/Doctrine/Dto/FkGetter.php @@ -10,11 +10,18 @@ class FkGetter extends Getter public function toString(string $nlLeftPad = ''): string { $methodName = 'get' . Str::asCamelCase($this->propertyName); + $returnHint = ''; + if ($this->returnType && strpos($this->returnType, '|')) { + $returnHint = ': ' . $this->returnType . '|null'; + } elseif ($this->returnType) { + $returnHint = ': ?' . $this->returnType; + } $response = []; $response[] = sprintf( - 'public function %sId()', - $methodName + 'public function %sId()%s', + $methodName, + $returnHint ); $response[] = '{'; $response[] = ' if ($dto = $this->' . $methodName . '()) {'; diff --git a/EntityGeneratorBundle/Doctrine/EntityTrait/TraitManipulator.php b/EntityGeneratorBundle/Doctrine/EntityTrait/TraitManipulator.php index 7570ae4..5bc5aeb 100644 --- a/EntityGeneratorBundle/Doctrine/EntityTrait/TraitManipulator.php +++ b/EntityGeneratorBundle/Doctrine/EntityTrait/TraitManipulator.php @@ -672,7 +672,7 @@ private function getAssociationMappings(ClassMetadata $classMetadata): array $classMetadata->associationMappings, function ($property) { return in_array( - $property['type'] ?? null, + $property['type'], [ ClassMetadata::ONE_TO_MANY, ClassMetadata::MANY_TO_MANY diff --git a/EntityGeneratorBundle/Doctrine/EntityTrait/TraitRegenerator.php b/EntityGeneratorBundle/Doctrine/EntityTrait/TraitRegenerator.php index b62fa32..4675922 100644 --- a/EntityGeneratorBundle/Doctrine/EntityTrait/TraitRegenerator.php +++ b/EntityGeneratorBundle/Doctrine/EntityTrait/TraitRegenerator.php @@ -115,7 +115,7 @@ private function getMappedFieldsInEntity(ClassMetadata $classMetadata) $classMetadata->associationMappings, function ($mapping) { return in_array( - $mapping['type'] ?? null, + $mapping['type'], [ ClassMetadata::ONE_TO_ONE, ClassMetadata::ONE_TO_MANY, diff --git a/EntityGeneratorBundle/Doctrine/Property.php b/EntityGeneratorBundle/Doctrine/Property.php index 0e43904..4b86973 100644 --- a/EntityGeneratorBundle/Doctrine/Property.php +++ b/EntityGeneratorBundle/Doctrine/Property.php @@ -4,6 +4,7 @@ class Property implements CodeGeneratorUnitInterface { + /** @var mixed | null */ private $defaultValue; public function __construct( @@ -130,7 +131,7 @@ public function toString(string $nlLeftPad = ''): string : 'false'; break; case 'string': - if (!is_null($this->defaultValue)) { + if (!empty($this->defaultValue)) { $defaultValue = '\'' . $this->defaultValue . '\''; } break; diff --git a/EntityGeneratorBundle/Doctrine/Repository/RepositoryRegenerator.php b/EntityGeneratorBundle/Doctrine/Repository/RepositoryRegenerator.php index 05e2533..a6360d8 100644 --- a/EntityGeneratorBundle/Doctrine/Repository/RepositoryRegenerator.php +++ b/EntityGeneratorBundle/Doctrine/Repository/RepositoryRegenerator.php @@ -74,13 +74,18 @@ public function makeEmptyInterface($classMetadata) return; } + $entityClassNameSegments = explode("\\", $classMetadata->name); + $entityClassName = end($entityClassNameSegments); + $classMetadata->name = $fqdn; $classMetadata->rootEntityName = $fqdn; [$classPath, $content] = $this->getDoctrineClassTemplate( $classMetadata, 'doctrine/RepositoryInterface.tpl.php', - [] + [ + 'entity_classname' => $entityClassName + ] ); $manipulator = $this->createClassManipulator( @@ -140,38 +145,6 @@ private function getDoctrineClassTemplate( ]; } - private function getClassTemplate( - ClassMetadata $metadata, - $templateName - ): array - { - [$path, $variables] = $this->generator->generateClassContentVariables( - $metadata->name, - $templateName, - [] - ); - - if (file_exists($variables['relative_path'])) { - $variables['relative_path'] = realpath($variables['relative_path']); - } else { - $variables['relative_path'] = str_replace( - 'vendor/composer/../../', - '', - $variables['relative_path'] - ); - } - - - return [ - $variables['relative_path'], - $this->fileManager->parseTemplate( - $path, - $variables - ) - ]; - } - - private function createClassManipulator( string $classPath, ?string $content diff --git a/EntityGeneratorBundle/Maker/MakeEntities.php b/EntityGeneratorBundle/Maker/MakeEntities.php index 23b6577..8cac7d5 100644 --- a/EntityGeneratorBundle/Maker/MakeEntities.php +++ b/EntityGeneratorBundle/Maker/MakeEntities.php @@ -138,491 +138,6 @@ private function getEntityNamespaces(string $targetNamespace): ?string return null; } - private function askForNextField(ConsoleStyle $io, array $fields, string $entityClass, bool $isFirstField) - { - $io->writeln(''); - - if ($isFirstField) { - $questionText = 'New property name (press to stop adding fields)'; - } else { - $questionText = 'Add another property? Enter the property name (or press to stop adding fields)'; - } - - $fieldName = $io->ask($questionText, null, function ($name) use ($fields) { - // allow it to be empty - if (!$name) { - return $name; - } - - if (\in_array($name, $fields)) { - throw new \InvalidArgumentException(sprintf('The "%s" property already exists.', $name)); - } - - return Validator::validateDoctrineFieldName($name, $this->doctrineHelper->getRegistry()); - }); - - if (!$fieldName) { - return null; - } - - $defaultType = 'string'; - // try to guess the type by the field name prefix/suffix - // convert to snake case for simplicity - $snakeCasedField = Str::asSnakeCase($fieldName); - - if ('_at' === $suffix = substr($snakeCasedField, -3)) { - $defaultType = 'datetime'; - } elseif ('_id' === $suffix) { - $defaultType = 'integer'; - } elseif (0 === strpos($snakeCasedField, 'is_')) { - $defaultType = 'boolean'; - } elseif (0 === strpos($snakeCasedField, 'has_')) { - $defaultType = 'boolean'; - } elseif ('uuid' === $snakeCasedField) { - $defaultType = 'uuid'; - } elseif ('guid' === $snakeCasedField) { - $defaultType = 'guid'; - } - - $type = null; - $types = Type::getTypesMap(); - - unset($types[Types::JSON]); - - $allValidTypes = array_merge( - array_keys($types), - EntityRelation::getValidRelationTypes(), - ['relation'] - ); - while (null === $type) { - $question = new Question('Field type (enter ? to see all types)', $defaultType); - $question->setAutocompleterValues($allValidTypes); - $type = $io->askQuestion($question); - - if ('?' === $type) { - $this->printAvailableTypes($io); - $io->writeln(''); - - $type = null; - } elseif (!\in_array($type, $allValidTypes)) { - $this->printAvailableTypes($io); - $io->error(sprintf('Invalid type "%s".', $type)); - $io->writeln(''); - - $type = null; - } - } - - if ('relation' === $type || \in_array($type, EntityRelation::getValidRelationTypes())) { - return $this->askRelationDetails($io, $entityClass, $type, $fieldName); - } - - // this is a normal field - $data = ['fieldName' => $fieldName, 'type' => $type]; - if ('string' === $type) { - // default to 255, avoid the question - $data['length'] = $io->ask('Field length', '255', [Validator::class, 'validateLength']); - } elseif ('decimal' === $type) { - // 10 is the default value given in \Doctrine\DBAL\Schema\Column::$_precision - $data['precision'] = $io->ask('Precision (total number of digits stored: 100.00 would be 5)', '10', [Validator::class, 'validatePrecision']); - - // 0 is the default value given in \Doctrine\DBAL\Schema\Column::$_scale - $data['scale'] = $io->ask('Scale (number of decimals to store: 100.00 would be 2)', '0', [Validator::class, 'validateScale']); - } - - if ($io->confirm('Can this field be null in the database (nullable)', false)) { - $data['nullable'] = true; - } - - return $data; - } - - private function printAvailableTypes(ConsoleStyle $io) - { - $allTypes = Type::getTypesMap(); - - if ('Hyper' === getenv('TERM_PROGRAM')) { - $wizard = 'wizard 🧙'; - } else { - $wizard = '\\' === \DIRECTORY_SEPARATOR ? 'wizard' : 'wizard 🧙'; - } - - $typesTable = [ - 'main' => [ - 'string' => [], - 'text' => [], - 'boolean' => [], - 'integer' => ['smallint', 'bigint'], - 'float' => [], - ], - 'relation' => [ - 'relation' => 'a '.$wizard.' will help you build the relation', - EntityRelation::MANY_TO_ONE => [], - EntityRelation::ONE_TO_MANY => [], - EntityRelation::MANY_TO_MANY => [], - EntityRelation::ONE_TO_ONE => [], - ], - 'array_object' => [ - 'array' => ['simple_array'], - 'json' => [], - 'object' => [], - 'binary' => [], - 'blob' => [], - ], - 'date_time' => [ - 'datetime' => ['datetime_immutable'], - 'datetimetz' => ['datetimetz_immutable'], - 'date' => ['date_immutable'], - 'time' => ['time_immutable'], - 'dateinterval' => [], - ], - ]; - - $printSection = function (array $sectionTypes) use ($io, &$allTypes) { - foreach ($sectionTypes as $mainType => $subTypes) { - unset($allTypes[$mainType]); - $line = sprintf(' * %s', $mainType); - - if (\is_string($subTypes) && $subTypes) { - $line .= sprintf(' (%s)', $subTypes); - } elseif (\is_array($subTypes) && !empty($subTypes)) { - $line .= sprintf(' (or %s)', implode(', ', array_map(function ($subType) { - return sprintf('%s', $subType); - }, $subTypes))); - - foreach ($subTypes as $subType) { - unset($allTypes[$subType]); - } - } - - $io->writeln($line); - } - - $io->writeln(''); - }; - - $io->writeln('Main types'); - $printSection($typesTable['main']); - - $io->writeln('Relationships / Associations'); - $printSection($typesTable['relation']); - - $io->writeln('Array/Object Types'); - $printSection($typesTable['array_object']); - - $io->writeln('Date/Time Types'); - $printSection($typesTable['date_time']); - - $io->writeln('Other Types'); - // empty the values - $allTypes = array_map(function () { - return []; - }, $allTypes); - $printSection($allTypes); - } - - private function createEntityClassQuestion(string $questionText): Question - { - $question = new Question($questionText); - $question->setValidator([Validator::class, 'notBlank']); - $question->setAutocompleterValues($this->doctrineHelper->getEntitiesForAutocomplete()); - - return $question; - } - - private function askRelationDetails(ConsoleStyle $io, string $generatedEntityClass, string $type, string $newFieldName) - { - // ask the targetEntity - $targetEntityClass = null; - while (null === $targetEntityClass) { - $question = $this->createEntityClassQuestion('What class should this entity be related to?'); - - $answeredEntityClass = $io->askQuestion($question); - - // find the correct class name - but give priority over looking - // in the Entity namespace versus just checking the full class - // name to avoid issues with classes like "Directory" that exist - // in PHP's core. - if (class_exists($this->getEntityNamespace().'\\'.$answeredEntityClass)) { - $targetEntityClass = $this->getEntityNamespace().'\\'.$answeredEntityClass; - } elseif (class_exists($answeredEntityClass)) { - $targetEntityClass = $answeredEntityClass; - } else { - $io->error(sprintf('Unknown class "%s"', $answeredEntityClass)); - continue; - } - } - - // help the user select the type - if ('relation' === $type) { - $type = $this->askRelationType($io, $generatedEntityClass, $targetEntityClass); - } - - $askFieldName = function (string $targetClass, string $defaultValue) use ($io) { - return $io->ask( - sprintf('New field name inside %s', Str::getShortClassName($targetClass)), - $defaultValue, - function ($name) use ($targetClass) { - // it's still *possible* to create duplicate properties - by - // trying to generate the same property 2 times during the - // same make:entity run. property_exists() only knows about - // properties that *originally* existed on this class. - if (property_exists($targetClass, $name)) { - throw new \InvalidArgumentException(sprintf('The "%s" class already has a "%s" property.', $targetClass, $name)); - } - - return Validator::validateDoctrineFieldName($name, $this->doctrineHelper->getRegistry()); - } - ); - }; - - $askIsNullable = function (string $propertyName, string $targetClass) use ($io) { - return $io->confirm(sprintf( - 'Is the %s.%s property allowed to be null (nullable)?', - Str::getShortClassName($targetClass), - $propertyName - )); - }; - - $askOrphanRemoval = function (string $owningClass, string $inverseClass) use ($io) { - $io->text([ - 'Do you want to activate orphanRemoval on your relationship?', - sprintf( - 'A %s is "orphaned" when it is removed from its related %s.', - Str::getShortClassName($owningClass), - Str::getShortClassName($inverseClass) - ), - sprintf( - 'e.g. $%s->remove%s($%s)', - Str::asLowerCamelCase(Str::getShortClassName($inverseClass)), - Str::asCamelCase(Str::getShortClassName($owningClass)), - Str::asLowerCamelCase(Str::getShortClassName($owningClass)) - ), - '', - sprintf( - 'NOTE: If a %s may *change* from one %s to another, answer "no".', - Str::getShortClassName($owningClass), - Str::getShortClassName($inverseClass) - ), - ]); - - return $io->confirm(sprintf('Do you want to automatically delete orphaned %s objects (orphanRemoval)?', $owningClass), false); - }; - - $askInverseSide = function (EntityRelation $relation) use ($io) { - if ($this->isClassInVendor($relation->getInverseClass())) { - $relation->setMapInverseRelation(false); - - return; - } - - // recommend an inverse side, except for OneToOne, where it's inefficient - $recommendMappingInverse = EntityRelation::ONE_TO_ONE !== $relation->getType(); - - $getterMethodName = 'get'.Str::asCamelCase(Str::getShortClassName($relation->getOwningClass())); - if (EntityRelation::ONE_TO_ONE !== $relation->getType()) { - // pluralize! - $getterMethodName = Str::singularCamelCaseToPluralCamelCase($getterMethodName); - } - $mapInverse = $io->confirm( - sprintf( - 'Do you want to add a new property to %s so that you can access/update %s objects from it - e.g. $%s->%s()?', - Str::getShortClassName($relation->getInverseClass()), - Str::getShortClassName($relation->getOwningClass()), - Str::asLowerCamelCase(Str::getShortClassName($relation->getInverseClass())), - $getterMethodName - ), - $recommendMappingInverse - ); - $relation->setMapInverseRelation($mapInverse); - }; - - switch ($type) { - case EntityRelation::MANY_TO_ONE: - $relation = new EntityRelation( - EntityRelation::MANY_TO_ONE, - $generatedEntityClass, - $targetEntityClass - ); - $relation->setOwningProperty($newFieldName); - - $relation->setIsNullable($askIsNullable( - $relation->getOwningProperty(), - $relation->getOwningClass() - )); - - $askInverseSide($relation); - if ($relation->getMapInverseRelation()) { - $io->comment(sprintf( - 'A new property will also be added to the %s class so that you can access the related %s objects from it.', - Str::getShortClassName($relation->getInverseClass()), - Str::getShortClassName($relation->getOwningClass()) - )); - $relation->setInverseProperty($askFieldName( - $relation->getInverseClass(), - Str::singularCamelCaseToPluralCamelCase(Str::getShortClassName($relation->getOwningClass())) - )); - - // orphan removal only applies if the inverse relation is set - if (!$relation->isNullable()) { - $relation->setOrphanRemoval($askOrphanRemoval( - $relation->getOwningClass(), - $relation->getInverseClass() - )); - } - } - - break; - case EntityRelation::ONE_TO_MANY: - // we *actually* create a ManyToOne, but populate it differently - $relation = new EntityRelation( - EntityRelation::MANY_TO_ONE, - $targetEntityClass, - $generatedEntityClass - ); - $relation->setInverseProperty($newFieldName); - - $io->comment(sprintf( - 'A new property will also be added to the %s class so that you can access and set the related %s object from it.', - Str::getShortClassName($relation->getOwningClass()), - Str::getShortClassName($relation->getInverseClass()) - )); - $relation->setOwningProperty($askFieldName( - $relation->getOwningClass(), - Str::asLowerCamelCase(Str::getShortClassName($relation->getInverseClass())) - )); - - $relation->setIsNullable($askIsNullable( - $relation->getOwningProperty(), - $relation->getOwningClass() - )); - - if (!$relation->isNullable()) { - $relation->setOrphanRemoval($askOrphanRemoval( - $relation->getOwningClass(), - $relation->getInverseClass() - )); - } - - break; - case EntityRelation::MANY_TO_MANY: - $relation = new EntityRelation( - EntityRelation::MANY_TO_MANY, - $generatedEntityClass, - $targetEntityClass - ); - $relation->setOwningProperty($newFieldName); - - $askInverseSide($relation); - if ($relation->getMapInverseRelation()) { - $io->comment(sprintf( - 'A new property will also be added to the %s class so that you can access the related %s objects from it.', - Str::getShortClassName($relation->getInverseClass()), - Str::getShortClassName($relation->getOwningClass()) - )); - $relation->setInverseProperty($askFieldName( - $relation->getInverseClass(), - Str::singularCamelCaseToPluralCamelCase(Str::getShortClassName($relation->getOwningClass())) - )); - } - - break; - case EntityRelation::ONE_TO_ONE: - $relation = new EntityRelation( - EntityRelation::ONE_TO_ONE, - $generatedEntityClass, - $targetEntityClass - ); - $relation->setOwningProperty($newFieldName); - - $relation->setIsNullable($askIsNullable( - $relation->getOwningProperty(), - $relation->getOwningClass() - )); - - $askInverseSide($relation); - if ($relation->getMapInverseRelation()) { - $io->comment(sprintf( - 'A new property will also be added to the %s class so that you can access the related %s object from it.', - Str::getShortClassName($relation->getInverseClass()), - Str::getShortClassName($relation->getOwningClass()) - )); - $relation->setInverseProperty($askFieldName( - $relation->getInverseClass(), - Str::asLowerCamelCase(Str::getShortClassName($relation->getOwningClass())) - )); - } - - break; - default: - throw new \InvalidArgumentException('Invalid type: '.$type); - } - - return $relation; - } - - private function askRelationType(ConsoleStyle $io, string $entityClass, string $targetEntityClass) - { - $io->writeln('What type of relationship is this?'); - - $originalEntityShort = Str::getShortClassName($entityClass); - $targetEntityShort = Str::getShortClassName($targetEntityClass); - $rows = []; - $rows[] = [ - EntityRelation::MANY_TO_ONE, - sprintf("Each %s relates to (has) one %s.\nEach %s can relate to (can have) many %s objects", $originalEntityShort, $targetEntityShort, $targetEntityShort, $originalEntityShort), - ]; - $rows[] = ['', '']; - $rows[] = [ - EntityRelation::ONE_TO_MANY, - sprintf("Each %s can relate to (can have) many %s objects.\nEach %s relates to (has) one %s", $originalEntityShort, $targetEntityShort, $targetEntityShort, $originalEntityShort), - ]; - $rows[] = ['', '']; - $rows[] = [ - EntityRelation::MANY_TO_MANY, - sprintf("Each %s can relate to (can have) many %s objects.\nEach %s can also relate to (can also have) many %s objects", $originalEntityShort, $targetEntityShort, $targetEntityShort, $originalEntityShort), - ]; - $rows[] = ['', '']; - $rows[] = [ - EntityRelation::ONE_TO_ONE, - sprintf("Each %s relates to (has) exactly one %s.\nEach %s also relates to (has) exactly one %s.", $originalEntityShort, $targetEntityShort, $targetEntityShort, $originalEntityShort), - ]; - - $io->table([ - 'Type', - 'Description', - ], $rows); - - $question = new Question(sprintf( - 'Relation type? [%s]', - implode(', ', EntityRelation::getValidRelationTypes()) - )); - $question->setAutocompleterValues(EntityRelation::getValidRelationTypes()); - $question->setValidator(function ($type) { - if (!\in_array($type, EntityRelation::getValidRelationTypes())) { - throw new \InvalidArgumentException(sprintf('Invalid type: use one of: %s', implode(', ', EntityRelation::getValidRelationTypes()))); - } - - return $type; - }); - - return $io->askQuestion($question); - } - - private function getPathOfClass(string $class): string - { - $classDetails = new ClassDetails($class); - - return $classDetails->getPath(); - } - - private function isClassInVendor(string $class): bool - { - $path = $this->getPathOfClass($class); - - return $this->fileManager->isPathInVendor($path); - } - /** * @param string $classOrNamespace * @param \IvozDevTools\EntityGeneratorBundle\Generator $generator @@ -641,9 +156,4 @@ private function regenerateEntities( $classOrNamespace ); } - - private function getEntityNamespace(): string - { - return $this->doctrineHelper->getEntityNamespace(); - } } diff --git a/EntityGeneratorBundle/Maker/MakeInterfaces.php b/EntityGeneratorBundle/Maker/MakeInterfaces.php index 99d464a..000ea50 100755 --- a/EntityGeneratorBundle/Maker/MakeInterfaces.php +++ b/EntityGeneratorBundle/Maker/MakeInterfaces.php @@ -137,308 +137,6 @@ private function getEntityNamespaces(string $targetNamespace): ?string return null; } - private function createEntityClassQuestion(string $questionText): Question - { - $question = new Question($questionText); - $question->setValidator([Validator::class, 'notBlank']); - $question->setAutocompleterValues($this->doctrineHelper->getEntitiesForAutocomplete()); - - return $question; - } - - private function askRelationDetails(ConsoleStyle $io, string $generatedEntityClass, string $type, string $newFieldName) - { - // ask the targetEntity - $targetEntityClass = null; - while (null === $targetEntityClass) { - $question = $this->createEntityClassQuestion('What class should this entity be related to?'); - - $answeredEntityClass = $io->askQuestion($question); - - // find the correct class name - but give priority over looking - // in the Entity namespace versus just checking the full class - // name to avoid issues with classes like "Directory" that exist - // in PHP's core. - if (class_exists($this->getEntityNamespace().'\\'.$answeredEntityClass)) { - $targetEntityClass = $this->getEntityNamespace().'\\'.$answeredEntityClass; - } elseif (class_exists($answeredEntityClass)) { - $targetEntityClass = $answeredEntityClass; - } else { - $io->error(sprintf('Unknown class "%s"', $answeredEntityClass)); - continue; - } - } - - // help the user select the type - if ('relation' === $type) { - $type = $this->askRelationType($io, $generatedEntityClass, $targetEntityClass); - } - - $askFieldName = function (string $targetClass, string $defaultValue) use ($io) { - return $io->ask( - sprintf('New field name inside %s', Str::getShortClassName($targetClass)), - $defaultValue, - function ($name) use ($targetClass) { - // it's still *possible* to create duplicate properties - by - // trying to generate the same property 2 times during the - // same make:entity run. property_exists() only knows about - // properties that *originally* existed on this class. - if (property_exists($targetClass, $name)) { - throw new \InvalidArgumentException(sprintf('The "%s" class already has a "%s" property.', $targetClass, $name)); - } - - return Validator::validateDoctrineFieldName($name, $this->doctrineHelper->getRegistry()); - } - ); - }; - - $askIsNullable = function (string $propertyName, string $targetClass) use ($io) { - return $io->confirm(sprintf( - 'Is the %s.%s property allowed to be null (nullable)?', - Str::getShortClassName($targetClass), - $propertyName - )); - }; - - $askOrphanRemoval = function (string $owningClass, string $inverseClass) use ($io) { - $io->text([ - 'Do you want to activate orphanRemoval on your relationship?', - sprintf( - 'A %s is "orphaned" when it is removed from its related %s.', - Str::getShortClassName($owningClass), - Str::getShortClassName($inverseClass) - ), - sprintf( - 'e.g. $%s->remove%s($%s)', - Str::asLowerCamelCase(Str::getShortClassName($inverseClass)), - Str::asCamelCase(Str::getShortClassName($owningClass)), - Str::asLowerCamelCase(Str::getShortClassName($owningClass)) - ), - '', - sprintf( - 'NOTE: If a %s may *change* from one %s to another, answer "no".', - Str::getShortClassName($owningClass), - Str::getShortClassName($inverseClass) - ), - ]); - - return $io->confirm(sprintf('Do you want to automatically delete orphaned %s objects (orphanRemoval)?', $owningClass), false); - }; - - $askInverseSide = function (EntityRelation $relation) use ($io) { - if ($this->isClassInVendor($relation->getInverseClass())) { - $relation->setMapInverseRelation(false); - - return; - } - - // recommend an inverse side, except for OneToOne, where it's inefficient - $recommendMappingInverse = EntityRelation::ONE_TO_ONE !== $relation->getType(); - - $getterMethodName = 'get'.Str::asCamelCase(Str::getShortClassName($relation->getOwningClass())); - if (EntityRelation::ONE_TO_ONE !== $relation->getType()) { - // pluralize! - $getterMethodName = Str::singularCamelCaseToPluralCamelCase($getterMethodName); - } - $mapInverse = $io->confirm( - sprintf( - 'Do you want to add a new property to %s so that you can access/update %s objects from it - e.g. $%s->%s()?', - Str::getShortClassName($relation->getInverseClass()), - Str::getShortClassName($relation->getOwningClass()), - Str::asLowerCamelCase(Str::getShortClassName($relation->getInverseClass())), - $getterMethodName - ), - $recommendMappingInverse - ); - $relation->setMapInverseRelation($mapInverse); - }; - - switch ($type) { - case EntityRelation::MANY_TO_ONE: - $relation = new EntityRelation( - EntityRelation::MANY_TO_ONE, - $generatedEntityClass, - $targetEntityClass - ); - $relation->setOwningProperty($newFieldName); - - $relation->setIsNullable($askIsNullable( - $relation->getOwningProperty(), - $relation->getOwningClass() - )); - - $askInverseSide($relation); - if ($relation->getMapInverseRelation()) { - $io->comment(sprintf( - 'A new property will also be added to the %s class so that you can access the related %s objects from it.', - Str::getShortClassName($relation->getInverseClass()), - Str::getShortClassName($relation->getOwningClass()) - )); - $relation->setInverseProperty($askFieldName( - $relation->getInverseClass(), - Str::singularCamelCaseToPluralCamelCase(Str::getShortClassName($relation->getOwningClass())) - )); - - // orphan removal only applies if the inverse relation is set - if (!$relation->isNullable()) { - $relation->setOrphanRemoval($askOrphanRemoval( - $relation->getOwningClass(), - $relation->getInverseClass() - )); - } - } - - break; - case EntityRelation::ONE_TO_MANY: - // we *actually* create a ManyToOne, but populate it differently - $relation = new EntityRelation( - EntityRelation::MANY_TO_ONE, - $targetEntityClass, - $generatedEntityClass - ); - $relation->setInverseProperty($newFieldName); - - $io->comment(sprintf( - 'A new property will also be added to the %s class so that you can access and set the related %s object from it.', - Str::getShortClassName($relation->getOwningClass()), - Str::getShortClassName($relation->getInverseClass()) - )); - $relation->setOwningProperty($askFieldName( - $relation->getOwningClass(), - Str::asLowerCamelCase(Str::getShortClassName($relation->getInverseClass())) - )); - - $relation->setIsNullable($askIsNullable( - $relation->getOwningProperty(), - $relation->getOwningClass() - )); - - if (!$relation->isNullable()) { - $relation->setOrphanRemoval($askOrphanRemoval( - $relation->getOwningClass(), - $relation->getInverseClass() - )); - } - - break; - case EntityRelation::MANY_TO_MANY: - $relation = new EntityRelation( - EntityRelation::MANY_TO_MANY, - $generatedEntityClass, - $targetEntityClass - ); - $relation->setOwningProperty($newFieldName); - - $askInverseSide($relation); - if ($relation->getMapInverseRelation()) { - $io->comment(sprintf( - 'A new property will also be added to the %s class so that you can access the related %s objects from it.', - Str::getShortClassName($relation->getInverseClass()), - Str::getShortClassName($relation->getOwningClass()) - )); - $relation->setInverseProperty($askFieldName( - $relation->getInverseClass(), - Str::singularCamelCaseToPluralCamelCase(Str::getShortClassName($relation->getOwningClass())) - )); - } - - break; - case EntityRelation::ONE_TO_ONE: - $relation = new EntityRelation( - EntityRelation::ONE_TO_ONE, - $generatedEntityClass, - $targetEntityClass - ); - $relation->setOwningProperty($newFieldName); - - $relation->setIsNullable($askIsNullable( - $relation->getOwningProperty(), - $relation->getOwningClass() - )); - - $askInverseSide($relation); - if ($relation->getMapInverseRelation()) { - $io->comment(sprintf( - 'A new property will also be added to the %s class so that you can access the related %s object from it.', - Str::getShortClassName($relation->getInverseClass()), - Str::getShortClassName($relation->getOwningClass()) - )); - $relation->setInverseProperty($askFieldName( - $relation->getInverseClass(), - Str::asLowerCamelCase(Str::getShortClassName($relation->getOwningClass())) - )); - } - - break; - default: - throw new \InvalidArgumentException('Invalid type: '.$type); - } - - return $relation; - } - - private function askRelationType(ConsoleStyle $io, string $entityClass, string $targetEntityClass) - { - $io->writeln('What type of relationship is this?'); - - $originalEntityShort = Str::getShortClassName($entityClass); - $targetEntityShort = Str::getShortClassName($targetEntityClass); - $rows = []; - $rows[] = [ - EntityRelation::MANY_TO_ONE, - sprintf("Each %s relates to (has) one %s.\nEach %s can relate to (can have) many %s objects", $originalEntityShort, $targetEntityShort, $targetEntityShort, $originalEntityShort), - ]; - $rows[] = ['', '']; - $rows[] = [ - EntityRelation::ONE_TO_MANY, - sprintf("Each %s can relate to (can have) many %s objects.\nEach %s relates to (has) one %s", $originalEntityShort, $targetEntityShort, $targetEntityShort, $originalEntityShort), - ]; - $rows[] = ['', '']; - $rows[] = [ - EntityRelation::MANY_TO_MANY, - sprintf("Each %s can relate to (can have) many %s objects.\nEach %s can also relate to (can also have) many %s objects", $originalEntityShort, $targetEntityShort, $targetEntityShort, $originalEntityShort), - ]; - $rows[] = ['', '']; - $rows[] = [ - EntityRelation::ONE_TO_ONE, - sprintf("Each %s relates to (has) exactly one %s.\nEach %s also relates to (has) exactly one %s.", $originalEntityShort, $targetEntityShort, $targetEntityShort, $originalEntityShort), - ]; - - $io->table([ - 'Type', - 'Description', - ], $rows); - - $question = new Question(sprintf( - 'Relation type? [%s]', - implode(', ', EntityRelation::getValidRelationTypes()) - )); - $question->setAutocompleterValues(EntityRelation::getValidRelationTypes()); - $question->setValidator(function ($type) { - if (!\in_array($type, EntityRelation::getValidRelationTypes())) { - throw new \InvalidArgumentException(sprintf('Invalid type: use one of: %s', implode(', ', EntityRelation::getValidRelationTypes()))); - } - - return $type; - }); - - return $io->askQuestion($question); - } - - private function getPathOfClass(string $class): string - { - $classDetails = new ClassDetails($class); - - return $classDetails->getPath(); - } - - private function isClassInVendor(string $class): bool - { - $path = $this->getPathOfClass($class); - - return $this->fileManager->isPathInVendor($path); - } - /** * @param string $classOrNamespace * @param \IvozDevTools\EntityGeneratorBundle\Generator $generator @@ -457,9 +155,4 @@ private function regenerateEntities( $classOrNamespace ); } - - private function getEntityNamespace(): string - { - return $this->doctrineHelper->getEntityNamespace(); - } } diff --git a/EntityGeneratorBundle/Resources/skeleton/doctrine/DoctrineRepository.tpl.php b/EntityGeneratorBundle/Resources/skeleton/doctrine/DoctrineRepository.tpl.php index 224e39e..b82ad0d 100644 --- a/EntityGeneratorBundle/Resources/skeleton/doctrine/DoctrineRepository.tpl.php +++ b/EntityGeneratorBundle/Resources/skeleton/doctrine/DoctrineRepository.tpl.php @@ -2,11 +2,13 @@ namespace ; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; +use Ivoz\Core\Domain\Service\EntityPersisterInterface; +use Ivoz\Core\Infrastructure\Persistence\Doctrine\Repository\DoctrineRepository; use ; use ; - -use Doctrine\Persistence\ManagerRegistry; +use Interface; +use Dto; /** * @@ -14,13 +16,19 @@ * This class was generated by ivoz:make:repositories command. * Add your own custom repository methods below. * - * @template-extends ServiceEntityRepository<> + * @extends DoctrineRepository<Interface, Dto> */ -class extends ServiceEntityRepository implements +class extends DoctrineRepository implements { - public function __construct(ManagerRegistry $registry) - { - parent::__construct($registry, ::class); + public function __construct( + ManagerRegistry $registry, + EntityPersisterInterface $entityPersisterInterface, + ) { + parent::__construct( + $registry, + ::class, + $entityPersisterInterface + ); } } \ No newline at end of file diff --git a/EntityGeneratorBundle/Resources/skeleton/doctrine/RepositoryInterface.tpl.php b/EntityGeneratorBundle/Resources/skeleton/doctrine/RepositoryInterface.tpl.php index 29139ca..199ebe5 100644 --- a/EntityGeneratorBundle/Resources/skeleton/doctrine/RepositoryInterface.tpl.php +++ b/EntityGeneratorBundle/Resources/skeleton/doctrine/RepositoryInterface.tpl.php @@ -2,9 +2,11 @@ namespace ; -use Doctrine\Common\Collections\Selectable; -use Doctrine\Persistence\ObjectRepository; +use Ivoz\Core\Domain\Service\Repository\RepositoryInterface; -interface extends ObjectRepository, Selectable +/** + * @extends RepositoryInterface<Interface, Dto> + */ +interface extends RepositoryInterface { } diff --git a/composer.json b/composer.json index 1a285e8..e948890 100644 --- a/composer.json +++ b/composer.json @@ -28,8 +28,8 @@ "doctrine/orm": "^2.5", "doctrine/doctrine-bundle": "^2.0", "symfony/console": "^5.1", - "symfony/maker-bundle": "^1.33", - "irontec/ivoz-core": "^4.16.7" + "symfony/maker-bundle": "<=1.38", + "irontec/ivoz-core": "^4.17.0" }, "require-dev": { "phpstan/phpstan": "^1.0"