Creare un comando di console

La pagina Console della sezione dei componenti (Il componente Console) spiega come creare un comando di console. Questa ricetta spiega invece le differenze nella creazione di comandi di console con il framework Symfony2.

Registrare comandi automaticamente

Per rendere disponibili automaticamente i comandi in Symfony2, creare una cartella Command nel proprio bundle e creare un file php, con suffisso Command.php, per ciascun comando che si vuole fornire. Per esempio, se si vuole estendere AcmeDemoBundle per mandare un saluto dalla linea di comando, creare GreetCommand.php e aggiungervi il codice seguente:

// src/AppBundle/Command/GreetCommand.php
namespace AppBundle\Command;

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

class GreetCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this
            ->setName('demo:greet')
            ->setDescription('Saluta qualcuno')
            ->addArgument(
                'name',
                InputArgument::OPTIONAL,
                'Chi vuoi salutare?'
            )
            ->addOption(
                'yell',
                null,
                InputOption::VALUE_NONE,
                'Se impostato, urlerà in lettere maiuscole'
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $name = $input->getArgument('name');
        if ($name) {
            $text = 'Ciao '.$name;
        } else {
            $text = 'Ciao';
        }

        if ($input->getOption('yell')) {
            $text = strtoupper($text);
        }

        $output->writeln($text);
    }
}

Ora il comando sarà automaticamente disponibile:

$ php app/console demo:greet Fabien

Registrare comandi nel contenitore di servizi

Proprio come i controllori, i comandi possono essere dichiarati come servizi. Vedere la ricetta dedicata per i dettagli.

Recuperare i servizi dal contenitore

Usando Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand come classe base per il comando (invece della più basica Symfony\Component\Console\Command\Command), si ha accesso al contenitore di servizi. In altre parole, si ha accesso a qualsiasi servizio configurato:

protected function execute(InputInterface $input, OutputInterface $output)
{
    $name = $input->getArgument('name');
    $logger = $this->getContainer()->get('logger');

    $logger->info('Esecuzione del comando per '.$name);
    // ...
}

Tuttavia, a causae degli scope del contenitore, questo codice non funziona per alcuni servizi. Per esempio, se si prova a prednere il servizio request o un altro servizio correlato, si otterrà il seguente errore:

You cannot create a service ("request") of an inactive scope ("request").

Si consideri il seguente esempio, che usa il servizio translator per tradurre dei contenuti usando un comando di console:

protected function execute(InputInterface $input, OutputInterface $output)
{
    $name = $input->getArgument('name');
    $translator = $this->getContainer()->get('translator');
    if ($name) {
        $output->writeln(
            $translator->trans('Hello %name%!', array('%name%' => $name))
        );
    } else {
        $output->writeln($translator->trans('Hello!'));
    }
}

Se si dà un’occhiata alle classi del componente Translator, si vedrà che il servizio request è necessario per ottenere il locale in cui tradurre i contenuti:

// vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php
public function getLocale()
{
    if (null === $this->locale && $this->container->isScopeActive('request')
        && $this->container->has('request')) {
        $this->locale = $this->container->get('request')->getLocale();
    }

    return $this->locale;
}

QUindi, quando si usa il servizio translator dall’interno di un comando, si otterrà, come prima, l’errore “You cannot create a service of an inactive scope”. In questo caso, la soluzione è facile, basta impostare il valore del locale prima di tradurre i contenuti:

protected function execute(InputInterface $input, OutputInterface $output)
{
    $name = $input->getArgument('name');
    $locale = $input->getArgument('locale');

    $translator = $this->getContainer()->get('translator');
    $translator->setLocale($locale);

    if ($name) {
        $output->writeln(
            $translator->trans('Hello %name%!', array('%name%' => $name))
        );
    } else {
        $output->writeln($translator->trans('Hello!'));
    }
}

Tuttavia, per altri servizi la soluzione potrebbe essere più complessa. Per maggiori dettagli, vedere Lavorare con gli scope.

Testare i comandi

Quando si testano i comandi usati come parte di un framework, andrebbe usata Symfony\Bundle\FrameworkBundle\Console\Application al posto di Symfony\Component\Console\Application:

use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Acme\DemoBundle\Command\GreetCommand;

class ListCommandTest extends \PHPUnit_Framework_TestCase
{
    public function testExecute()
    {
        // fare un mock del Kernel o crearne uno, a seconda delle esigenze
        $application = new Application($kernel);
        $application->add(new GreetCommand());

        $command = $application->find('demo:greet');
        $commandTester = new CommandTester($command);
        $commandTester->execute(
            array(
                'name'    => 'Fabien',
                '--yell'  => true,
            )
        );

        $this->assertRegExp('/.../', $commandTester->getDisplay());

        // ...
    }
}

Nota

Nel caso specifico appena visto, il parametro name e l’opzione --yell non sono indispensabili al comando, ma sono mostrate per poter capire come personalizzarli quando si richiama il comando stesso.

Per poter usare il contenitore in modo completo per i test della console, si può estendere il test da Symfony\Bundle\FrameworkBundle\Test\KernelTestCase:

use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use AppBundle\Command\GreetCommand;

class ListCommandTest extends KernelTestCase
{
    public function testExecute()
    {
        $kernel = $this->createKernel();
        $kernel->boot();

        $application = new Application($kernel);
        $application->add(new GreetCommand());

        $command = $application->find('demo:greet');
        $commandTester = new CommandTester($command);
        $commandTester->execute(
            array(
                'name'    => 'Fabien',
                '--yell'  => true,
            )
        );

        $this->assertRegExp('/.../', $commandTester->getDisplay());

        // ...
    }
}

Nuovo nella versione 2.5: Symfony\Bundle\FrameworkBundle\Test\KernelTestCase è stata estratta da Symfony\Bundle\FrameworkBundle\Test\WebTestCase in Symfony 2.5. WebTestCase eredita da KernelTestCase. WebTestCase crea un’istanza di Symfony\Bundle\FrameworkBundle\Client tramite createClient(), mentre KernelTestCase crea un’istanza di Symfony\Component\HttpKernel\KernelInterface tramite createKernel().

Tabella dei contenuti

Argomento precedente

Console

Argomento successivo

Come usare la console

Questa pagina