vendor/symfony/maker-bundle/src/Maker/MakeUser.php line 245

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony MakerBundle package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Bundle\MakerBundle\Maker;
  11. use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
  12. use Symfony\Bundle\MakerBundle\ConsoleStyle;
  13. use Symfony\Bundle\MakerBundle\DependencyBuilder;
  14. use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper;
  15. use Symfony\Bundle\MakerBundle\Doctrine\EntityClassGenerator;
  16. use Symfony\Bundle\MakerBundle\Doctrine\ORMDependencyBuilder;
  17. use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
  18. use Symfony\Bundle\MakerBundle\FileManager;
  19. use Symfony\Bundle\MakerBundle\Generator;
  20. use Symfony\Bundle\MakerBundle\InputConfiguration;
  21. use Symfony\Bundle\MakerBundle\Security\SecurityConfigUpdater;
  22. use Symfony\Bundle\MakerBundle\Security\UserClassBuilder;
  23. use Symfony\Bundle\MakerBundle\Security\UserClassConfiguration;
  24. use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator;
  25. use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator;
  26. use Symfony\Bundle\MakerBundle\Util\YamlManipulationFailedException;
  27. use Symfony\Bundle\MakerBundle\Validator;
  28. use Symfony\Bundle\SecurityBundle\SecurityBundle;
  29. use Symfony\Component\Console\Command\Command;
  30. use Symfony\Component\Console\Input\InputArgument;
  31. use Symfony\Component\Console\Input\InputInterface;
  32. use Symfony\Component\Console\Input\InputOption;
  33. use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
  34. use Symfony\Component\Security\Core\Exception\UserNotFoundException;
  35. use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
  36. use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
  37. use Symfony\Component\Security\Core\User\UserInterface;
  38. use Symfony\Component\Security\Core\User\UserProviderInterface;
  39. use Symfony\Component\Yaml\Yaml;
  40. /**
  41.  * @author Ryan Weaver <weaverryan@gmail.com>
  42.  *
  43.  * @internal
  44.  */
  45. final class MakeUser extends AbstractMaker
  46. {
  47.     private $fileManager;
  48.     private $userClassBuilder;
  49.     private $configUpdater;
  50.     private $entityClassGenerator;
  51.     private $doctrineHelper;
  52.     public function __construct(FileManager $fileManagerUserClassBuilder $userClassBuilderSecurityConfigUpdater $configUpdaterEntityClassGenerator $entityClassGeneratorDoctrineHelper $doctrineHelper)
  53.     {
  54.         $this->fileManager $fileManager;
  55.         $this->userClassBuilder $userClassBuilder;
  56.         $this->configUpdater $configUpdater;
  57.         $this->entityClassGenerator $entityClassGenerator;
  58.         $this->doctrineHelper $doctrineHelper;
  59.     }
  60.     public static function getCommandName(): string
  61.     {
  62.         return 'make:user';
  63.     }
  64.     public static function getCommandDescription(): string
  65.     {
  66.         return 'Creates a new security user class';
  67.     }
  68.     public function configureCommand(Command $commandInputConfiguration $inputConfig): void
  69.     {
  70.         $command
  71.             ->addArgument('name'InputArgument::OPTIONAL'The name of the security user class (e.g. <fg=yellow>User</>)')
  72.             ->addOption('is-entity'nullInputOption::VALUE_NONE'Do you want to store user data in the database (via Doctrine)?')
  73.             ->addOption('identity-property-name'nullInputOption::VALUE_REQUIRED'Enter a property name that will be the unique "display" name for the user (e.g. <comment>email, username, uuid</comment>)')
  74.             ->addOption('with-password'nullInputOption::VALUE_NONE'Will this app be responsible for checking the password? Choose <comment>No</comment> if the password is actually checked by some other system (e.g. a single sign-on server)')
  75.             ->addOption('use-argon2'nullInputOption::VALUE_NONE'Use the Argon2i password encoder? (deprecated)')
  76.             ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeUser.txt'));
  77.         $inputConfig->setArgumentAsNonInteractive('name');
  78.     }
  79.     public function interact(InputInterface $inputConsoleStyle $ioCommand $command): void
  80.     {
  81.         if (null === $input->getArgument('name')) {
  82.             $name $io->ask(
  83.                 $command->getDefinition()->getArgument('name')->getDescription(),
  84.                 'User'
  85.             );
  86.             $input->setArgument('name'$name);
  87.         }
  88.         $userIsEntity $io->confirm(
  89.             'Do you want to store user data in the database (via Doctrine)?',
  90.             class_exists(DoctrineBundle::class)
  91.         );
  92.         if ($userIsEntity) {
  93.             $dependencies = new DependencyBuilder();
  94.             ORMDependencyBuilder::buildDependencies($dependencies);
  95.             $missingPackagesMessage $dependencies->getMissingPackagesMessage(self::getCommandName(), 'Doctrine must be installed to store user data in the database');
  96.             if ($missingPackagesMessage) {
  97.                 throw new RuntimeCommandException($missingPackagesMessage);
  98.             }
  99.         }
  100.         $input->setOption('is-entity'$userIsEntity);
  101.         $identityFieldName $io->ask('Enter a property name that will be the unique "display" name for the user (e.g. <comment>email, username, uuid</comment>)''email', [Validator::class, 'validatePropertyName']);
  102.         $input->setOption('identity-property-name'$identityFieldName);
  103.         $io->text('Will this app need to hash/check user passwords? Choose <comment>No</comment> if passwords are not needed or will be checked/hashed by some other system (e.g. a single sign-on server).');
  104.         $userWillHavePassword $io->confirm('Does this app need to hash/check user passwords?');
  105.         $input->setOption('with-password'$userWillHavePassword);
  106.     }
  107.     public function generate(InputInterface $inputConsoleStyle $ioGenerator $generator): void
  108.     {
  109.         $userClassConfiguration = new UserClassConfiguration(
  110.             $input->getOption('is-entity'),
  111.             $input->getOption('identity-property-name'),
  112.             $input->getOption('with-password')
  113.         );
  114.         if ($input->getOption('use-argon2')) {
  115.             @trigger_error('The "--use-argon2" option is deprecated since MakerBundle 1.12.'\E_USER_DEPRECATED);
  116.             $userClassConfiguration->useArgon2(true);
  117.         }
  118.         $userClassNameDetails $generator->createClassNameDetails(
  119.             $input->getArgument('name'),
  120.             $userClassConfiguration->isEntity() ? 'Entity\\' 'Security\\'
  121.         );
  122.         // A) Generate the User class
  123.         if ($userClassConfiguration->isEntity()) {
  124.             $classPath $this->entityClassGenerator->generateEntityClass(
  125.                 $userClassNameDetails,
  126.                 false// api resource
  127.                 $userClassConfiguration->hasPassword() // security user
  128.             );
  129.         } else {
  130.             $classPath $generator->generateClass($userClassNameDetails->getFullName(), 'Class.tpl.php');
  131.         }
  132.         // need to write changes early so we can modify the contents below
  133.         $generator->writeChanges();
  134.         $useAttributesForDoctrineMapping $userClassConfiguration->isEntity() && ($this->doctrineHelper->isDoctrineSupportingAttributes()) && $this->doctrineHelper->doesClassUsesAttributes($userClassNameDetails->getFullName());
  135.         // B) Implement UserInterface
  136.         $manipulator = new ClassSourceManipulator(
  137.             $this->fileManager->getFileContents($classPath),
  138.             true,
  139.             !$useAttributesForDoctrineMapping,
  140.             true,
  141.             $useAttributesForDoctrineMapping
  142.         );
  143.         $manipulator->setIo($io);
  144.         $this->userClassBuilder->addUserInterfaceImplementation($manipulator$userClassConfiguration);
  145.         $generator->dumpFile($classPath$manipulator->getSourceCode());
  146.         // C) Generate a custom user provider, if necessary
  147.         if (!$userClassConfiguration->isEntity()) {
  148.             $userClassConfiguration->setUserProviderClass($generator->getRootNamespace().'\\Security\\UserProvider');
  149.             $useStatements = new UseStatementGenerator([
  150.                 UnsupportedUserException::class,
  151.                 UserNotFoundException::class,
  152.                 PasswordAuthenticatedUserInterface::class,
  153.                 PasswordUpgraderInterface::class,
  154.                 UserInterface::class,
  155.                 UserProviderInterface::class,
  156.             ]);
  157.             $customProviderPath $generator->generateClass(
  158.                 $userClassConfiguration->getUserProviderClass(),
  159.                 'security/UserProvider.tpl.php',
  160.                 [
  161.                     'use_statements' => $useStatements,
  162.                     'user_short_name' => $userClassNameDetails->getShortName(),
  163.                 ]
  164.             );
  165.         }
  166.         // D) Update security.yaml
  167.         $securityYamlUpdated false;
  168.         $path 'config/packages/security.yaml';
  169.         if ($this->fileManager->fileExists($path)) {
  170.             try {
  171.                 $newYaml $this->configUpdater->updateForUserClass(
  172.                     $this->fileManager->getFileContents($path),
  173.                     $userClassConfiguration,
  174.                     $userClassNameDetails->getFullName()
  175.                 );
  176.                 $generator->dumpFile($path$newYaml);
  177.                 $securityYamlUpdated true;
  178.             } catch (YamlManipulationFailedException $e) {
  179.             }
  180.         }
  181.         $generator->writeChanges();
  182.         $this->writeSuccessMessage($io);
  183.         $io->text('Next Steps:');
  184.         $nextSteps = [
  185.             sprintf('Review your new <info>%s</info> class.'$userClassNameDetails->getFullName()),
  186.         ];
  187.         if ($userClassConfiguration->isEntity()) {
  188.             $nextSteps[] = sprintf(
  189.                 'Use <comment>make:entity</comment> to add more fields to your <info>%s</info> entity and then run <comment>make:migration</comment>.',
  190.                 $userClassNameDetails->getShortName()
  191.             );
  192.         } else {
  193.             $nextSteps[] = sprintf(
  194.                 'Open <info>%s</info> to finish implementing your user provider.',
  195.                 $this->fileManager->relativizePath($customProviderPath)
  196.             );
  197.         }
  198.         if (!$securityYamlUpdated) {
  199.             $yamlExample $this->configUpdater->updateForUserClass(
  200.                 'security: {}',
  201.                 $userClassConfiguration,
  202.                 $userClassNameDetails->getFullName()
  203.             );
  204.             $nextSteps[] = "Your <info>security.yaml</info> could not be updated automatically. You'll need to add the following config manually:\n\n".$yamlExample;
  205.         }
  206.         $nextSteps[] = 'Create a way to authenticate! See https://symfony.com/doc/current/security.html';
  207.         $nextSteps array_map(function ($step) {
  208.             return sprintf('  - %s'$step);
  209.         }, $nextSteps);
  210.         $io->text($nextSteps);
  211.     }
  212.     public function configureDependencies(DependencyBuilder $dependenciesInputInterface $input null): void
  213.     {
  214.         // checking for SecurityBundle guarantees security.yaml is present
  215.         $dependencies->addClassDependency(
  216.             SecurityBundle::class,
  217.             'security'
  218.         );
  219.         // needed to update the YAML files
  220.         $dependencies->addClassDependency(
  221.             Yaml::class,
  222.             'yaml'
  223.         );
  224.         if (null !== $input && $input->getOption('is-entity')) {
  225.             ORMDependencyBuilder::buildDependencies($dependencies);
  226.         }
  227.     }
  228. }