11
votes

Is it possible to override symfony2 app/console commands? For example, in the FOS UserBundle I'd like to add a few more fields it asks when it creates a user with its console create user command. Is this possible, or do I need to create my own console command in my own bundle?

4
Helpful stuff on this question. You should mark an answer as correct ;)Darragh Enright

4 Answers

15
votes

The whole process for adding more fields to the command is:

1.In your AcmeDemoBundle class you must set FOSUser as parent:

<?php

namespace Acme\UserBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class AcmeUserBundle extends Bundle
{
    public function getParent()
    {
        return 'FOSUserBundle';
    }
}

2.Once you do that you can recreate the CreateUserCommand in your bundle:

<?php

namespace Acme\UserBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use FOS\UserBundle\Model\User;

/**
 * @author Matthieu Bontemps <[email protected]>
 * @author Thibault Duplessis <[email protected]>
 * @author Luis Cordova <[email protected]>
 */
class CreateUserCommand extends ContainerAwareCommand
{
    /**
     * @see Command
     */
    protected function configure()
    {
        $this
            ->setName('fos:user:create')
            ->setDescription('Create a user.')
            ->setDefinition(array(
                new InputArgument('username', InputArgument::REQUIRED, 'The username'),
                new InputArgument('email', InputArgument::REQUIRED, 'The email'),
                new InputArgument('password', InputArgument::REQUIRED, 'The password'),
                new InputArgument('name', InputArgument::REQUIRED, 'The name'),
                new InputOption('super-admin', null, InputOption::VALUE_NONE, 'Set the user as super admin'),
                new InputOption('inactive', null, InputOption::VALUE_NONE, 'Set the user as inactive'),
            ))
            ->setHelp(<<<EOT
The <info>fos:user:create</info> command creates a user:

  <info>php app/console fos:user:create matthieu</info>

This interactive shell will ask you for an email and then a password.

You can alternatively specify the email and password as the second and third arguments:

  <info>php app/console fos:user:create matthieu [email protected] mypassword</info>

You can create a super admin via the super-admin flag:

  <info>php app/console fos:user:create admin --super-admin</info>

You can create an inactive user (will not be able to log in):

  <info>php app/console fos:user:create thibault --inactive</info>

EOT
            );
    }

    /**
     * @see Command
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $username   = $input->getArgument('username');
        $email      = $input->getArgument('email');
        $password   = $input->getArgument('password');
        $name       = $input->getArgument('name');
        $inactive   = $input->getOption('inactive');
        $superadmin = $input->getOption('super-admin');

        $manipulator = $this->getContainer()->get('acme.util.user_manipulator');
        $manipulator->create($username, $password, $email, $name, !$inactive, $superadmin);

        $output->writeln(sprintf('Created user <comment>%s</comment>', $username));
    }

    /**
     * @see Command
     */
    protected function interact(InputInterface $input, OutputInterface $output)
    {
        if (!$input->getArgument('username')) {
            $username = $this->getHelper('dialog')->askAndValidate(
                $output,
                'Please choose a username:',
                function($username) {
                    if (empty($username)) {
                        throw new \Exception('Username can not be empty');
                    }

                    return $username;
                }
            );
            $input->setArgument('username', $username);
        }

        if (!$input->getArgument('email')) {
            $email = $this->getHelper('dialog')->askAndValidate(
                $output,
                'Please choose an email:',
                function($email) {
                    if (empty($email)) {
                        throw new \Exception('Email can not be empty');
                    }

                    return $email;
                }
            );
            $input->setArgument('email', $email);
        }

        if (!$input->getArgument('password')) {
            $password = $this->getHelper('dialog')->askAndValidate(
                $output,
                'Please choose a password:',
                function($password) {
                    if (empty($password)) {
                        throw new \Exception('Password can not be empty');
                    }

                    return $password;
                }
            );
            $input->setArgument('password', $password);
        }

        if (!$input->getArgument('name')) {
            $name = $this->getHelper('dialog')->askAndValidate(
                $output,
                'Please choose a name:',
                function($name) {
                    if (empty($name)) {
                        throw new \Exception('Name can not be empty');
                    }

                    return $name;
                }
            );
            $input->setArgument('name', $name);
        }
    }
}

Note I have added a new input argument called name and inside the command I am using an acme.util.user_manipulator service instead of the original one os there I am going to process also the user's name.

3.Create your own UserManipulator:

<?php

namespace Acme\UserBundle\Util;

use FOS\UserBundle\Model\UserManagerInterface;

/**
 * Executes some manipulations on the users
 *
 * @author Christophe Coevoet <[email protected]>
 * @author Luis Cordova <[email protected]>
 */
class UserManipulator
{
    /**
     * User manager
     *
     * @var UserManagerInterface
     */
    private $userManager;

    public function __construct(UserManagerInterface $userManager)
    {
        $this->userManager = $userManager;
    }

    /**
     * Creates a user and returns it.
     *
     * @param string  $username
     * @param string  $password
     * @param string  $email
     * @param string  $name
     * @param Boolean $active
     * @param Boolean $superadmin
     *
     * @return \FOS\UserBundle\Model\UserInterface
     */
    public function create($username, $password, $email, $name, $active, $superadmin)
    {
        $user = $this->userManager->createUser();
        $user->setUsername($username);
        $user->setEmail($email);
        $user->setName($name);
        $user->setPlainPassword($password);
        $user->setEnabled((Boolean)$active);
        $user->setSuperAdmin((Boolean)$superadmin);
        $this->userManager->updateUser($user);

        return $user;
    }
}

In this class I only need the create function so the rest of commands like promote, demote.. does not know about your user's new properties so I do not need to create a CompilerPass to override the whole service.

4.Finally, define this new UserManipulator service in the Resources/config directory and add it to the DependencyInjection Extension:

services:
    acme.util.user_manipulator:
        class:      Acme\UserBundle\Util\UserManipulator
        arguments:  [@fos_user.user_manager]

Done!!!

6
votes

Symfony 4 has removed bundle inheritance, but you can still override commands by decorating them. It's cleaner and doesn't require a bundle. Just add this to services.yaml:

services:
    App\Command\MyCustomCommand:
        decorates: command_you_want_to_override

Note that command_you_want_to_override is a service name. For older, pre-Symfony 3.3 commands it will be something in lowercase with dots and underscores (eg. doctrine_migrations.diff_command), for newer ones it will be a class name. You can find this name by inspecting its bundle's service config or by searching through output of debug:container. On Linux or macOS it would be:

php bin/console debug:container | grep Command
5
votes

You can override a bundle's console command(s) if you create (or already have) your own bundle which is a child of that bundle (see Bundle Inheritance). Then by placing a class in your bundle with the same location/name as the original command you effectively override it.

So for example, to override FOS/UserBundle/Command/CreateUserCommand.php, create MyCompany/UserBundle/Command/CreateUserCommand where MyCompanyUserBundle has FOSUserBundle as it's parent.

Your command class could extend the FOS command class in order to re-use (bits of) it. However, having looked at the FOS CreateUserCommand I think you would need to override all the methods to add more input fields in which case there is no benefit to doing this. Of course this also means you could just create your own command in any bundle but in my opinion it's better to keep the customisation of FOSUserBundle in a child bundle.

-1
votes

In Symfony (3.3) you can override console commands by following these links. https://symfony.com/doc/current/console/calling_commands.html and the options at https://symfony.com/doc/current/console/input.html

Code from symfony doc:

use Symfony\Component\Console\Input\ArrayInput;
// ...

protected function execute(InputInterface $input, OutputInterface $output)
{
    $command = $this->getApplication()->find('demo:greet');

    $arguments = array(
        'command' => 'demo:greet',
        'name'    => 'Fabien',
        '--yell'  => true,
    );

    $greetInput = new ArrayInput($arguments);
    $returnCode = $command->run($greetInput, $output);

    // ...
}