welcome back to dyb-tech
This commit is contained in:
+252
@@ -0,0 +1,252 @@
|
||||
<?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 Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
|
||||
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\ORMDependencyBuilder;
|
||||
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
|
||||
use Symfony\Bundle\MakerBundle\FileManager;
|
||||
use Symfony\Bundle\MakerBundle\Generator;
|
||||
use Symfony\Bundle\MakerBundle\InputConfiguration;
|
||||
use Symfony\Bundle\MakerBundle\Security\SecurityConfigUpdater;
|
||||
use Symfony\Bundle\MakerBundle\Security\UserClassBuilder;
|
||||
use Symfony\Bundle\MakerBundle\Security\UserClassConfiguration;
|
||||
use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator;
|
||||
use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator;
|
||||
use Symfony\Bundle\MakerBundle\Util\YamlManipulationFailedException;
|
||||
use Symfony\Bundle\MakerBundle\Validator;
|
||||
use Symfony\Bundle\SecurityBundle\SecurityBundle;
|
||||
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\Security\Core\Exception\UnsupportedUserException;
|
||||
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MakeUser extends AbstractMaker
|
||||
{
|
||||
public function __construct(
|
||||
private FileManager $fileManager,
|
||||
private UserClassBuilder $userClassBuilder,
|
||||
private SecurityConfigUpdater $configUpdater,
|
||||
private EntityClassGenerator $entityClassGenerator,
|
||||
private DoctrineHelper $doctrineHelper,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getCommandName(): string
|
||||
{
|
||||
return 'make:user';
|
||||
}
|
||||
|
||||
public static function getCommandDescription(): string
|
||||
{
|
||||
return 'Create a new security user class';
|
||||
}
|
||||
|
||||
public function configureCommand(Command $command, InputConfiguration $inputConfig): void
|
||||
{
|
||||
$command
|
||||
->addArgument('name', InputArgument::OPTIONAL, 'The name of the security user class (e.g. <fg=yellow>User</>)')
|
||||
->addOption('is-entity', null, InputOption::VALUE_NONE, 'Do you want to store user data in the database (via Doctrine)?')
|
||||
->addOption('identity-property-name', null, InputOption::VALUE_REQUIRED, 'Enter a property name that will be the unique "display" name for the user (e.g. <comment>email, username, uuid</comment>)')
|
||||
->addOption('with-password', null, InputOption::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)')
|
||||
->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeUser.txt'));
|
||||
|
||||
$inputConfig->setArgumentAsNonInteractive('name');
|
||||
}
|
||||
|
||||
public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
|
||||
{
|
||||
if (null === $input->getArgument('name')) {
|
||||
$name = $io->ask(
|
||||
$command->getDefinition()->getArgument('name')->getDescription(),
|
||||
'User'
|
||||
);
|
||||
$input->setArgument('name', $name);
|
||||
}
|
||||
|
||||
$userIsEntity = $io->confirm(
|
||||
'Do you want to store user data in the database (via Doctrine)?',
|
||||
class_exists(DoctrineBundle::class)
|
||||
);
|
||||
if ($userIsEntity) {
|
||||
$dependencies = new DependencyBuilder();
|
||||
ORMDependencyBuilder::buildDependencies($dependencies);
|
||||
|
||||
$missingPackagesMessage = $dependencies->getMissingPackagesMessage(self::getCommandName(), 'Doctrine must be installed to store user data in the database');
|
||||
if ($missingPackagesMessage) {
|
||||
throw new RuntimeCommandException($missingPackagesMessage);
|
||||
}
|
||||
}
|
||||
$input->setOption('is-entity', $userIsEntity);
|
||||
|
||||
$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']);
|
||||
$input->setOption('identity-property-name', $identityFieldName);
|
||||
|
||||
$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).');
|
||||
$userWillHavePassword = $io->confirm('Does this app need to hash/check user passwords?');
|
||||
$input->setOption('with-password', $userWillHavePassword);
|
||||
}
|
||||
|
||||
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
|
||||
{
|
||||
$userClassConfiguration = new UserClassConfiguration(
|
||||
$input->getOption('is-entity'),
|
||||
$input->getOption('identity-property-name'),
|
||||
$input->getOption('with-password')
|
||||
);
|
||||
|
||||
$userClassNameDetails = $generator->createClassNameDetails(
|
||||
$input->getArgument('name'),
|
||||
$userClassConfiguration->isEntity() ? 'Entity\\' : 'Security\\'
|
||||
);
|
||||
|
||||
// A) Generate the User class
|
||||
if ($userClassConfiguration->isEntity()) {
|
||||
$classPath = $this->entityClassGenerator->generateEntityClass(
|
||||
$userClassNameDetails,
|
||||
false, // api resource
|
||||
$userClassConfiguration->hasPassword() // security user
|
||||
);
|
||||
} else {
|
||||
$classPath = $generator->generateClass($userClassNameDetails->getFullName(), 'Class.tpl.php');
|
||||
}
|
||||
// need to write changes early so we can modify the contents below
|
||||
$generator->writeChanges();
|
||||
|
||||
$entityUsesAttributes = ($isEntity = $userClassConfiguration->isEntity()) && $this->doctrineHelper->doesClassUsesAttributes($userClassNameDetails->getFullName());
|
||||
|
||||
if ($isEntity && !$entityUsesAttributes) {
|
||||
throw new \RuntimeException('MakeUser only supports attribute mapping with doctrine entities.');
|
||||
}
|
||||
|
||||
// B) Implement UserInterface
|
||||
$manipulator = new ClassSourceManipulator(
|
||||
sourceCode: $this->fileManager->getFileContents($classPath),
|
||||
overwrite: true,
|
||||
useAttributesForDoctrineMapping: $entityUsesAttributes
|
||||
);
|
||||
|
||||
$manipulator->setIo($io);
|
||||
|
||||
$this->userClassBuilder->addUserInterfaceImplementation($manipulator, $userClassConfiguration);
|
||||
|
||||
$generator->dumpFile($classPath, $manipulator->getSourceCode());
|
||||
|
||||
// C) Generate a custom user provider, if necessary
|
||||
if (!$userClassConfiguration->isEntity()) {
|
||||
$userClassConfiguration->setUserProviderClass($generator->getRootNamespace().'\\Security\\UserProvider');
|
||||
|
||||
$useStatements = new UseStatementGenerator([
|
||||
UnsupportedUserException::class,
|
||||
UserNotFoundException::class,
|
||||
PasswordAuthenticatedUserInterface::class,
|
||||
PasswordUpgraderInterface::class,
|
||||
UserInterface::class,
|
||||
UserProviderInterface::class,
|
||||
]);
|
||||
|
||||
$customProviderPath = $generator->generateClass(
|
||||
$userClassConfiguration->getUserProviderClass(),
|
||||
'security/UserProvider.tpl.php',
|
||||
[
|
||||
'use_statements' => $useStatements,
|
||||
'user_short_name' => $userClassNameDetails->getShortName(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// D) Update security.yaml
|
||||
$securityYamlUpdated = false;
|
||||
$path = 'config/packages/security.yaml';
|
||||
if ($this->fileManager->fileExists($path)) {
|
||||
try {
|
||||
$newYaml = $this->configUpdater->updateForUserClass(
|
||||
$this->fileManager->getFileContents($path),
|
||||
$userClassConfiguration,
|
||||
$userClassNameDetails->getFullName()
|
||||
);
|
||||
$generator->dumpFile($path, $newYaml);
|
||||
$securityYamlUpdated = true;
|
||||
} catch (YamlManipulationFailedException) {
|
||||
}
|
||||
}
|
||||
|
||||
$generator->writeChanges();
|
||||
|
||||
$this->writeSuccessMessage($io);
|
||||
|
||||
$io->text('Next Steps:');
|
||||
$nextSteps = [
|
||||
sprintf('Review your new <info>%s</info> class.', $userClassNameDetails->getFullName()),
|
||||
];
|
||||
if ($userClassConfiguration->isEntity()) {
|
||||
$nextSteps[] = sprintf(
|
||||
'Use <comment>make:entity</comment> to add more fields to your <info>%s</info> entity and then run <comment>make:migration</comment>.',
|
||||
$userClassNameDetails->getShortName()
|
||||
);
|
||||
} else {
|
||||
$nextSteps[] = sprintf(
|
||||
'Open <info>%s</info> to finish implementing your user provider.',
|
||||
$this->fileManager->relativizePath($customProviderPath)
|
||||
);
|
||||
}
|
||||
|
||||
if (!$securityYamlUpdated) {
|
||||
$yamlExample = $this->configUpdater->updateForUserClass(
|
||||
'security: {}',
|
||||
$userClassConfiguration,
|
||||
$userClassNameDetails->getFullName()
|
||||
);
|
||||
$nextSteps[] = "Your <info>security.yaml</info> could not be updated automatically. You'll need to add the following config manually:\n\n".$yamlExample;
|
||||
}
|
||||
|
||||
$nextSteps[] = 'Create a way to authenticate! See https://symfony.com/doc/current/security.html';
|
||||
|
||||
$nextSteps = array_map(static fn ($step) => sprintf(' - %s', $step), $nextSteps);
|
||||
$io->text($nextSteps);
|
||||
}
|
||||
|
||||
public function configureDependencies(DependencyBuilder $dependencies, InputInterface $input = null): void
|
||||
{
|
||||
// checking for SecurityBundle guarantees security.yaml is present
|
||||
$dependencies->addClassDependency(
|
||||
SecurityBundle::class,
|
||||
'security'
|
||||
);
|
||||
|
||||
// needed to update the YAML files
|
||||
$dependencies->addClassDependency(
|
||||
Yaml::class,
|
||||
'yaml'
|
||||
);
|
||||
|
||||
if (null !== $input && $input->getOption('is-entity')) {
|
||||
ORMDependencyBuilder::buildDependencies($dependencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user