/
home
/
obinna
/
html
/
mixchief_app
/
vendor
/
symfony
/
maker-bundle
/
src
/
Maker
/
Upload File
HOME
<?php /* * This file is part of the Symfony MakerBundle package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bundle\MakerBundle\Maker; use ApiPlatform\Core\Annotation\ApiResource; use Doctrine\DBAL\Types\Type; use Symfony\Bundle\MakerBundle\ConsoleStyle; use Symfony\Bundle\MakerBundle\DependencyBuilder; use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper; use Symfony\Bundle\MakerBundle\Doctrine\EntityClassGenerator; use Symfony\Bundle\MakerBundle\Doctrine\EntityRegenerator; use Symfony\Bundle\MakerBundle\Doctrine\EntityRelation; use Symfony\Bundle\MakerBundle\Doctrine\ORMDependencyBuilder; use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; use Symfony\Bundle\MakerBundle\FileManager; use Symfony\Bundle\MakerBundle\Generator; use Symfony\Bundle\MakerBundle\InputAwareMakerInterface; use Symfony\Bundle\MakerBundle\InputConfiguration; use Symfony\Bundle\MakerBundle\Str; use Symfony\Bundle\MakerBundle\Util\ClassDetails; use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; use Symfony\Bundle\MakerBundle\Validator; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; use Symfony\UX\Turbo\Attribute\Broadcast; /** * @author Javier Eguiluz <javier.eguiluz@gmail.com> * @author Ryan Weaver <weaverryan@gmail.com> * @author Kévin Dunglas <dunglas@gmail.com> */ final class MakeEntity extends AbstractMaker implements InputAwareMakerInterface { private $fileManager; private $doctrineHelper; private $generator; private $entityClassGenerator; public function __construct(FileManager $fileManager, DoctrineHelper $doctrineHelper, string $projectDirectory, Generator $generator = null, EntityClassGenerator $entityClassGenerator = null) { $this->fileManager = $fileManager; $this->doctrineHelper = $doctrineHelper; // $projectDirectory is unused, argument kept for BC if (null === $generator) { @trigger_error(sprintf('Passing a "%s" instance as 4th argument is mandatory since version 1.5.', Generator::class), \E_USER_DEPRECATED); $this->generator = new Generator($fileManager, 'App\\'); } else { $this->generator = $generator; } if (null === $entityClassGenerator) { @trigger_error(sprintf('Passing a "%s" instance as 5th argument is mandatory since version 1.15.1', EntityClassGenerator::class), \E_USER_DEPRECATED); $this->entityClassGenerator = new EntityClassGenerator($generator, $this->doctrineHelper); } else { $this->entityClassGenerator = $entityClassGenerator; } } public static function getCommandName(): string { return 'make:entity'; } public static function getCommandDescription(): string { return 'Creates or updates a Doctrine entity class, and optionally an API Platform resource'; } public function configureCommand(Command $command, InputConfiguration $inputConf) { $command ->addArgument('name', InputArgument::OPTIONAL, sprintf('Class name of the entity to create or update (e.g. <fg=yellow>%s</>)', Str::asClassName(Str::getRandomTerm()))) ->addOption('api-resource', 'a', InputOption::VALUE_NONE, 'Mark this class as an API Platform resource (expose a CRUD API for it)') ->addOption('broadcast', 'b', InputOption::VALUE_NONE, 'Add the ability to broadcast entity updates using Symfony UX Turbo?') ->addOption('regenerate', null, InputOption::VALUE_NONE, 'Instead of adding new fields, simply generate the methods (e.g. getter/setter) for existing fields') ->addOption('overwrite', null, InputOption::VALUE_NONE, 'Overwrite any existing getter/setter methods') ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeEntity.txt')) ; $inputConf->setArgumentAsNonInteractive('name'); } public function interact(InputInterface $input, ConsoleStyle $io, Command $command) { if ($input->getArgument('name')) { return; } if ($input->getOption('regenerate')) { $io->block([ 'This command will generate any missing methods (e.g. getters & setters) for a class or all classes in a namespace.', 'To overwrite any existing methods, re-run this command with the --overwrite flag', ], null, 'fg=yellow'); $classOrNamespace = $io->ask('Enter a class or namespace to regenerate', $this->getEntityNamespace(), [Validator::class, 'notBlank']); $input->setArgument('name', $classOrNamespace); return; } $argument = $command->getDefinition()->getArgument('name'); $question = $this->createEntityClassQuestion($argument->getDescription()); $entityClassName = $io->askQuestion($question); $input->setArgument('name', $entityClassName); if ( !$input->getOption('api-resource') && class_exists(ApiResource::class) && !class_exists($this->generator->createClassNameDetails($entityClassName, 'Entity\\')->getFullName()) ) { $description = $command->getDefinition()->getOption('api-resource')->getDescription(); $question = new ConfirmationQuestion($description, false); $isApiResource = $io->askQuestion($question); $input->setOption('api-resource', $isApiResource); } if ( !$input->getOption('broadcast') && class_exists(Broadcast::class) && !class_exists($this->generator->createClassNameDetails($entityClassName, 'Entity\\')->getFullName()) ) { $description = $command->getDefinition()->getOption('broadcast')->getDescription(); $question = new ConfirmationQuestion($description, false); $isBroadcast = $io->askQuestion($question); $input->setOption('broadcast', $isBroadcast); } } public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) { $overwrite = $input->getOption('overwrite'); // the regenerate option has entirely custom behavior if ($input->getOption('regenerate')) { $this->regenerateEntities($input->getArgument('name'), $overwrite, $generator); $this->writeSuccessMessage($io); return; } $entityClassDetails = $generator->createClassNameDetails( $input->getArgument('name'), 'Entity\\' ); if (!$this->doctrineHelper->isDoctrineSupportingAttributes() && $this->doctrineHelper->doesClassUsesAttributes($entityClassDetails->getFullName())) { throw new RuntimeCommandException('To use Doctrine entity attributes you\'ll need PHP 8, doctrine/orm 2.9, doctrine/doctrine-bundle 2.4 and symfony/framework-bundle 5.2.'); } $classExists = class_exists($entityClassDetails->getFullName()); if (!$classExists) { $broadcast = $input->getOption('broadcast'); $entityPath = $this->entityClassGenerator->generateEntityClass( $entityClassDetails, $input->getOption('api-resource'), false, true, $broadcast ); if ($broadcast) { $shortName = $entityClassDetails->getShortName(); $generator->generateTemplate( sprintf('broadcast/%s.stream.html.twig', $shortName), 'doctrine/broadcast_twig_template.tpl.php', [ 'class_name' => Str::asSnakeCase($shortName), 'class_name_plural' => Str::asSnakeCase(Str::singularCamelCaseToPluralCamelCase($shortName)), ] ); } $generator->writeChanges(); } if ( !$this->doesEntityUseAnnotationMapping($entityClassDetails->getFullName()) && !$this->doesEntityUseAttributeMapping($entityClassDetails->getFullName()) ) { throw new RuntimeCommandException(sprintf('Only annotation or attribute mapping is supported by make:entity, but the <info>%s</info> class uses a different format. If you would like this command to generate the properties & getter/setter methods, add your mapping configuration, and then re-run this command with the <info>--regenerate</info> flag.', $entityClassDetails->getFullName())); } if ($classExists) { $entityPath = $this->getPathOfClass($entityClassDetails->getFullName()); $io->text([ 'Your entity already exists! So let\'s add some new fields!', ]); } else { $io->text([ '', 'Entity generated! Now let\'s add some fields!', 'You can always add more fields later manually or by re-running this command.', ]); } $currentFields = $this->getPropertyNames($entityClassDetails->getFullName()); $manipulator = $this->createClassManipulator($entityPath, $io, $overwrite, $entityClassDetails->getFullName()); $isFirstField = true; while (true) { $newField = $this->askForNextField($io, $currentFields, $entityClassDetails->getFullName(), $isFirstField); $isFirstField = false; if (null === $newField) { break; } $fileManagerOperations = []; $fileManagerOperations[$entityPath] = $manipulator; if (\is_array($newField)) { $annotationOptions = $newField; unset($annotationOptions['fieldName']); $manipulator->addEntityField($newField['fieldName'], $annotationOptions); $currentFields[] = $newField['fieldName']; } elseif ($newField instanceof EntityRelation) { // both overridden below for OneToMany $newFieldName = $newField->getOwningProperty(); if ($newField->isSelfReferencing()) { $otherManipulatorFilename = $entityPath; $otherManipulator = $manipulator; } else { $otherManipulatorFilename = $this->getPathOfClass($newField->getInverseClass()); $otherManipulator = $this->createClassManipulator($otherManipulatorFilename, $io, $overwrite, $entityClassDetails->getFullName()); } switch ($newField->getType()) { case EntityRelation::MANY_TO_ONE: if ($newField->getOwningClass() === $entityClassDetails->getFullName()) { // THIS class will receive the ManyToOne $manipulator->addManyToOneRelation($newField->getOwningRelation()); if ($newField->getMapInverseRelation()) { $otherManipulator->addOneToManyRelation($newField->getInverseRelation()); } } else { // the new field being added to THIS entity is the inverse $newFieldName = $newField->getInverseProperty(); $otherManipulatorFilename = $this->getPathOfClass($newField->getOwningClass()); $otherManipulator = $this->createClassManipulator($otherManipulatorFilename, $io, $overwrite, $entityClassDetails->getFullName()); // The *other* class will receive the ManyToOne $otherManipulator->addManyToOneRelation($newField->getOwningRelation()); if (!$newField->getMapInverseRelation()) { throw new \Exception('Somehow a OneToMany relationship is being created, but the inverse side will not be mapped?'); } $manipulator->addOneToManyRelation($newField->getInverseRelation()); } break; case EntityRelation::MANY_TO_MANY: $manipulator->addManyToManyRelation($newField->getOwningRelation()); if ($newField->getMapInverseRelation()) { $otherManipulator->addManyToManyRelation($newField->getInverseRelation()); } break; case EntityRelation::ONE_TO_ONE: $manipulator->addOneToOneRelation($newField->getOwningRelation()); if ($newField->getMapInverseRelation()) { $otherManipulator->addOneToOneRelation($newField->getInverseRelation()); } break; default: throw new \Exception('Invalid relation type'); } // save the inverse side if it's being mapped if ($newField->getMapInverseRelation()) { $fileManagerOperations[$otherManipulatorFilename] = $otherManipulator; } $currentFields[] = $newFieldName; } else { throw new \Exception('Invalid value'); } foreach ($fileManagerOperations as $path => $manipulatorOrMessage) { if (\is_string($manipulatorOrMessage)) { $io->comment($manipulatorOrMessage); } else { $this->fileManager->dumpFile($path, $manipulatorOrMessage->getSourceCode()); } } } $this->writeSuccessMessage($io); $io->text([ 'Next: When you\'re ready, create a migration with <info>php bin/console make:migration</info>', '', ]); } public function configureDependencies(DependencyBuilder $dependencies, InputInterface $input = null) { if (null !== $input && $input->getOption('api-resource')) { $dependencies->addClassDependency( ApiResource::class, 'api' ); } if (null !== $input && $input->getOption('broadcast')) { $dependencies->addClassDependency( Broadcast::class, 'ux-turbo-mercure' ); } ORMDependencyBuilder::buildDependencies($dependencies); } private function askForNextField(ConsoleStyle $io, array $fields, string $entityClass, bool $isFirstField) { $io->writeln(''); if ($isFirstField) { $questionText = 'New property name (press <return> to stop adding fields)'; } else { $questionText = 'Add another property? Enter the property name (or press <return> 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_immutable'; } 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 = $this->getTypesMap(); $allValidTypes = array_merge( array_keys($types), EntityRelation::getValidRelationTypes(), ['relation'] ); while (null === $type) { $question = new Question('Field type (enter <comment>?</comment> 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 = $this->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(' * <comment>%s</comment>', $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('<comment>%s</comment>', $subType); }, $subTypes))); foreach ($subTypes as $subType) { unset($allTypes[$subType]); } } $io->writeln($line); } $io->writeln(''); }; $io->writeln('<info>Main types</info>'); $printSection($typesTable['main']); $io->writeln('<info>Relationships / Associations</info>'); $printSection($typesTable['relation']); $io->writeln('<info>Array/Object Types</info>'); $printSection($typesTable['array_object']); $io->writeln('<info>Date/Time Types</info>'); $printSection($typesTable['date_time']); $io->writeln('<info>Other Types</info>'); // 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 <comment>%s</comment>.<comment>%s</comment> 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 <comment>orphanRemoval</comment> on your relationship?', sprintf( 'A <comment>%s</comment> is "orphaned" when it is removed from its related <comment>%s</comment>.', Str::getShortClassName($owningClass), Str::getShortClassName($inverseClass) ), sprintf( 'e.g. <comment>$%s->remove%s($%s)</comment>', Str::asLowerCamelCase(Str::getShortClassName($inverseClass)), Str::asCamelCase(Str::getShortClassName($owningClass)), Str::asLowerCamelCase(Str::getShortClassName($owningClass)) ), '', sprintf( 'NOTE: If a <comment>%s</comment> may *change* from one <comment>%s</comment> to another, answer "no".', Str::getShortClassName($owningClass), Str::getShortClassName($inverseClass) ), ]); return $io->confirm(sprintf('Do you want to automatically delete orphaned <comment>%s</comment> 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 <comment>%s</comment> so that you can access/update <comment>%s</comment> objects from it - e.g. <comment>$%s->%s()</comment>?', 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 <comment>%s</comment> class so that you can access the related <comment>%s</comment> 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 <comment>%s</comment> class so that you can access and set the related <comment>%s</comment> 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 <comment>%s</comment> class so that you can access the related <comment>%s</comment> 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 <comment>%s</comment> class so that you can access the related <comment>%s</comment> 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 <comment>%s</comment> relates to (has) <info>one</info> <comment>%s</comment>.\nEach <comment>%s</comment> can relate to (can have) <info>many</info> <comment>%s</comment> objects", $originalEntityShort, $targetEntityShort, $targetEntityShort, $originalEntityShort), ]; $rows[] = ['', '']; $rows[] = [ EntityRelation::ONE_TO_MANY, sprintf("Each <comment>%s</comment> can relate to (can have) <info>many</info> <comment>%s</comment> objects.\nEach <comment>%s</comment> relates to (has) <info>one</info> <comment>%s</comment>", $originalEntityShort, $targetEntityShort, $targetEntityShort, $originalEntityShort), ]; $rows[] = ['', '']; $rows[] = [ EntityRelation::MANY_TO_MANY, sprintf("Each <comment>%s</comment> can relate to (can have) <info>many</info> <comment>%s</comment> objects.\nEach <comment>%s</comment> can also relate to (can also have) <info>many</info> <comment>%s</comment> objects", $originalEntityShort, $targetEntityShort, $targetEntityShort, $originalEntityShort), ]; $rows[] = ['', '']; $rows[] = [ EntityRelation::ONE_TO_ONE, sprintf("Each <comment>%s</comment> relates to (has) exactly <info>one</info> <comment>%s</comment>.\nEach <comment>%s</comment> also relates to (has) exactly <info>one</info> <comment>%s</comment>.", $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 createClassManipulator(string $path, ConsoleStyle $io, bool $overwrite, string $className): ClassSourceManipulator { $useAttributes = $this->doctrineHelper->doesClassUsesAttributes($className) && $this->doctrineHelper->isDoctrineSupportingAttributes(); $useAnnotations = $this->doctrineHelper->isClassAnnotated($className) || !$useAttributes; $manipulator = new ClassSourceManipulator($this->fileManager->getFileContents($path), $overwrite, $useAnnotations, true, $useAttributes); $manipulator->setIo($io); return $manipulator; } 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); } private function regenerateEntities(string $classOrNamespace, bool $overwrite, Generator $generator) { $regenerator = new EntityRegenerator($this->doctrineHelper, $this->fileManager, $generator, $this->entityClassGenerator, $overwrite); $regenerator->regenerateEntities($classOrNamespace); } private function getPropertyNames(string $class): array { if (!class_exists($class)) { return []; } $reflClass = new \ReflectionClass($class); return array_map(function (\ReflectionProperty $prop) { return $prop->getName(); }, $reflClass->getProperties()); } private function doesEntityUseAnnotationMapping(string $className): bool { if (!class_exists($className)) { $otherClassMetadatas = $this->doctrineHelper->getMetadata(Str::getNamespace($className).'\\', true); // if we have no metadata, we should assume this is the first class being mapped if (empty($otherClassMetadatas)) { return false; } $className = reset($otherClassMetadatas)->getName(); } return $this->doctrineHelper->isClassAnnotated($className); } private function doesEntityUseAttributeMapping(string $className): bool { if (\PHP_VERSION < 80000) { return false; } if (!class_exists($className)) { $otherClassMetadatas = $this->doctrineHelper->getMetadata(Str::getNamespace($className).'\\', true); // if we have no metadata, we should assume this is the first class being mapped if (empty($otherClassMetadatas)) { return false; } $className = reset($otherClassMetadatas)->getName(); } return $this->doctrineHelper->doesClassUsesAttributes($className); } private function getEntityNamespace(): string { return $this->doctrineHelper->getEntityNamespace(); } private function getTypesMap(): array { $types = Type::getTypesMap(); // remove deprecated json_array if it exists if (\defined(sprintf('%s::JSON_ARRAY', Type::class))) { unset($types[Type::JSON_ARRAY]); } return $types; } }