4 minutos de lectura

Symfony 7 in Background

Puntos a ver ...

MakerBundle

He he empezado a desgranar el componente Maker de Symfony. El componente no sólo permite la generación de comandos automatizados, sino también podemos personalizar los nuestros propios. Así por ejemplo en el repo: https://github.com/symfony/maker-bundle/tree/main/src/Maker

Puedo revisar como está contruido cada clase, y cuales son sus dependencias. Básicamente, cada Maker dispone de un conjunto de clases y plantillas prefabricadas para construir ficheros .yaml, .xml, y ficheros .php , que contienen clases, repositorios, getters and setters, namespaces. Y aquí la cosa se pone interesante…

Todos los métodos extienden de la clase AbstractMaker que implementa la interfaz:

interface MakerInterface
{
    /**
     * Return the command name for your maker (e.g. make:report).
     */
    public static function getCommandName(): string;

    /**
     * Configure the command: set description, input arguments, options, etc.
     *
     * By default, all arguments will be asked interactively. If you want
     * to avoid that, use the $inputConfig->setArgumentAsNonInteractive() method.
     */
    public function configureCommand(Command $command, InputConfiguration $inputConfig);

    /**
     * Configure any library dependencies that your maker requires.
     */
    public function configureDependencies(DependencyBuilder $dependencies);

    /**
     * If necessary, you can use this method to interactively ask the user for input.
     */
    public function interact(InputInterface $input, ConsoleStyle $io, Command $command);

    /**
     * Called after normal code generation: allows you to do anything.
     */
    public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator);
}

Muy similar a Console, getCommandName configura el nombre del comando que queremos darle.

configureCommand se utiliza para utilizar como en Console, configurando tipo de argumentos y opciones de entrada.

    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');
    }

configureDependencies chequea si el comando dispone de las dependencias o componentes instalados. Si no es así, el comando no se ejecutará.

 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);
        }
    }

interact Permite configurar las preguntas para configurar nuestro comando y registrar las opciones seleccionadas. Son las opciones que el programador escoge para generar el proceso.

 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);

generate Una vez obtenido todos los requerimientos del programador, este método se utiliza para generar las instrucciones y/o operaciones necesarias que debe realizar el comando.

        $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.');
        }

Podemos pasar dependencias a nuestro comando Maker personalizado de la siguiente manera:

    App\Command\GenerateUserCommand:
        arguments: ['@maker.entity_class_generator']