Running console command from a Symfony 2 test case - php

Is there a way to run a console command from a Symfony 2 test case? I want to run the doctrine commands for creating and dropping schemas.

This documentation chapter explains how to run commands from different places. Mind, that using exec() for your needs is quite dirty solution...
The right way of executing console command in Symfony2 is as below:
Option one
use Symfony\Bundle\FrameworkBundle\Console\Application as App;
use Symfony\Component\Console\Tester\CommandTester;
class YourTest extends WebTestCase
{
public function setUp()
{
$kernel = $this->createKernel();
$kernel->boot();
$application = new App($kernel);
$application->add(new YourCommand());
$command = $application->find('your:command:name');
$commandTester = new CommandTester($command);
$commandTester->execute(array('command' => $command->getName()));
}
}
Option two
use Symfony\Component\Console\Input\StringInput;
use Symfony\Bundle\FrameworkBundle\Console\Application;
class YourClass extends WebTestCase
{
protected static $application;
public function setUp()
{
self::runCommand('your:command:name');
// you can also specify an environment:
// self::runCommand('your:command:name --env=test');
}
protected static function runCommand($command)
{
$command = sprintf('%s --quiet', $command);
return self::getApplication()->run(new StringInput($command));
}
protected static function getApplication()
{
if (null === self::$application) {
$client = static::createClient();
self::$application = new Application($client->getKernel());
self::$application->setAutoExit(false);
}
return self::$application;
}
}
P.S. Guys, don't shame Symfony2 with calling exec()...

The docs tell you the suggested way to do it. The example code is pasted below:
protected function execute(InputInterface $input, OutputInterface $output)
{
$command = $this->getApplication()->find('demo:greet');
$arguments = array(
'command' => 'demo:greet',
'name' => 'Fabien',
'--yell' => true,
);
$input = new ArrayInput($arguments);
$returnCode = $command->run($input, $output);
// ...
}

Yes, if your directory structure looks like
/symfony
/app
/src
then you would run
phpunit -c app/phpunit.xml.dist
from your unit tests you can run php commands either by using
passthru("php app/console [...]") (http://php.net/manual/en/function.passthru.php)
exec("php app/console [...]") (http://www.php.net/manual/en/function.exec.php)
or by putting the command in back ticks
php app/consode [...]
If you are running the unit tests from a directory other than symofny, you'll have to adjust the relative path to the app directory for it to work.
To run it from the app:
// the document root should be the web folder
$root = $_SERVER['DOCUMENT_ROOT'];
passthru("php $root/../app/console [...]");

The documentation has been updated since my last answer to reflect the proper Symfony 2 way of calling an existing command:
http://symfony.com/doc/current/components/console/introduction.html#calling-an-existing-command

Related

Implement locking for all commands in my Symfony app

I followed this guide: https://symfony.com/doc/current/console/lockable_trait.html and implemented the command lock feature for my one of my commands to see how it works. It worked as described and then I was going to implement it for all of my commands. But the issue is that I have about 50 commands and:
I do not want spent time adding the necessary code to each command
I want to have the centralized management of commands locking. I mean, adding extra option to regular commands so that they will be used by my future management center. For now I will need a pretty simple option protected function isLocked() for a regular command which will help me to manage if a command should have lockable feature.
So, I went to the source of \Symfony\Component\Console\Command\LockableTrait and after some time created the following listener to the event console.command:
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Lock\Lock;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\LockInterface;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\SemaphoreStore;
class LockCommandsListener
{
/**
* #var array<string, Lock>
*/
private $commandLocks = [];
private static function init()
{
if (!class_exists(SemaphoreStore::class)) {
throw new LogicException('To enable the locking feature you must install the symfony/lock component.');
}
}
public function onConsoleCommand(ConsoleCommandEvent $event)
{
static::init();
$name = $event->getCommand()->getName();
$this->ensureLockNotPlaced($name);
$lock = $this->createLock($name);
$this->commandLocks[$name] = $lock;
if (!$lock->acquire()) {
$this->disableCommand($event, $name);
}
}
private function disableCommand(ConsoleCommandEvent $event, string $name)
{
unset($this->commandLocks[$name]);
$event->getOutput()->writeln('The command ' . $name . ' is already running');
$event->disableCommand();
$event->getCommand()->setCode()
}
private function createLock(string $name): LockInterface
{
if (SemaphoreStore::isSupported()) {
$store = new SemaphoreStore();
} else {
$store = new FlockStore();
}
return (new LockFactory($store))->createLock($name);
}
private function ensureLockNotPlaced(string $name)
{
if (isset($this->commandLocks[$name])) {
throw new LogicException('A lock is already in place.');
}
}
}
I made some tests and it kind of worked. But I am not sure this is the right way of doing things.
Another problem is that I can not find the proper exit code when I disabled a command. Should I just disable it? But it seems that exit code would be a great feature here. Specially when it comes to this listener testing (PHPUnit testing).
And I also have with testing itself. How can I run commands in parallel in my test class. For now I have this:
class LockCommandTest extends CommandTest
{
public function testOneCommandCanBeRun()
{
$commandTester = new ApplicationTester($this->application);
$commandTester->run([
'command' => 'app:dummy-command'
]);
$output = $commandTester->getDisplay();
dd($output);
}
}
It will allow only to run my commands one by one. But I would like to run them both so after running the first one, the second will fail (with some exit code).
As for me the best way to make background task is doing it via supervisor, create config file, like:
[program:your_service]
command=/usr/local/bin/php /srv/www/bin/console <your:app:command>
priority=1
numprocs=1
# Each 5 min.
startsecs=300
autostart=true
autorestart=true
process_name=%(program_name)s_%(process_num)02d
user=root
this is the best way to be sure that your command will be ran only in one process

How to run a background process in symfony

I have a symfony commad that needes few minutes to be completed. And i want to run it by a http request. The problem is that the server kills the command process just after sending response, he has a timeout.
i tried to use symfony process asynchronously But still not working ( i get always the timeout problem).
I dont want use the kernel.terminate event, since it is not a best practice. Here is the code in my controller :
$commandProcess=new Process('php bin/console app:doSomeThing');
$commandProcess->setWorkingDirectory('./../');
$commandProcess->start();
$commandProcess->setTimeout(50000000);
Any response will be much appreciated.
I had to desibale the process output and add '&' after the command
Here the answer:
new Process('php bin/console app:dosomthing &');
$commandProcess=new Process('php bin/console app:dosomthing &');
$commandProcess->setWorkingDirectory('./../');
$commandProcess->disableOutput();
$commandProcess->setTimeout(1800);
$commandProcess->start();
I write here the solution that has finally worked for me
First create a service:
use Symfony\Component\Process\Process;
use Symfony\Component\HttpKernel\KernelInterface;
class ProcessService {
public function __construct(
private KernelInterface $appKernel
){ }
public function executeProcessAsync($processToExecute) {
$rootDir = $this->appKernel->getProjectDir();
$process = Process::fromShellCommandline($processToExecute . ' &');
$process->setWorkingDirectory($rootDir);
$process->disableOutput();
$process->setTimeout(0);
$process->start();
}
}
Then we use the service from a controller:
class MyController {
public function __construct(
private ProcessService $procesService
){
public function __invoke(Request $request)
{
// (other stuff ...)
$this->procesService->executeProcessAsync('php bin/console mycommands:command ');
// (other stuff...)
}
}

Testing Symfony2 Console Commands that expect input

I am trying to test my Symfony2 Console command using phpunit.
I'm following the Symfony2 cookbook article about this topic:
http://symfony.com/doc/current/components/console/helpers/questionhelper.html#testing-a-command-that-expects-input
However, if I fail to provide an input (a test fail), then phpunit simply sit there doing nothing waiting for input. Here's an example:
// MyCommand.php
class MyCommand extends Command {
// ... configure()
protected function execute(InputInterface $input, OutputInterface $output) {
$qh = $this->getHelper('question');
$q1 = new ConfirmationQuestion('First question, yes or no?', false);
$qh->ask($input, $output, $q);
$q2 = new ConfirmationQuestion('Second question, yes or no?', false);
$qh->ask($input, $output, $q);
}
}
// MyCommandTest.php
class MyCommandTest extends \PHPUnit_Framework_TestCase {
// ... getInputstream()
public function testExecute() {
$app = new Application();
$app->add(new MyCommand());
$cmd = $app->find('askquestions');
$cmdTester = new CommandTester($cmd);
$helper = $cmd->getHelper('question');
$helper->setInputStream($this->getInputStream('y\\n')); // this should be yy\\n
$cmdTester->execute([
'command' => $cmd->getName(),
]);
}
}
Please notice that I have purposefully made my test incorrect, it is only supplying an answer to question 1. Since I wrote that test, I have since added q2 but I forgot to modify my tests. Being a good programmer though I run phpunit to see if there are problems, but phpunit hangs as it expects input from q2!
How do I make it so my test will disregard any further requests for input, fail if it encounters one, and keep going with other tests?

Symfony command line program output from service

I have a Symfony service that processes a file and does stuff with its information. I call this service from a controller and separately from a command class. The actual service takes a long time to run, and I'd like to show some status output on the command line while it processes the file. What is the best way to accomplish this without adding echo commands in my service?
Edit
This seems to be the solution: http://symfony.com/blog/new-in-symfony-2-4-show-logs-in-console
There are commands like
$output->write('Blah blah blah');
$output->writeLn('Blah blah blah'); // Above with a line break
You can also add colours and progress bars and possibly other stuff that I've never got round to using.
http://symfony.com/doc/current/components/console/introduction.html#coloring-the-output
UPDATE
You could use the EventDisptcher service to update your command on events in your service.
For example...
You command
protected function execute(InputInterface $input, OutputInterface $output)
{
//....
$dispatcher = $this->getContainer->get('event_dispatcher');
$dispatcher->addListener(
'an.event.that.you.have.set.up',
function (GenericEvent $event) use ($output) {
$output->writeLn('<info>This event has happened</info');
}
});
//....
}
Your service
protected $dispatcher;
//....
public function __construct(EventDispatcherInterface $dispatcher, ...)
{
$this->dispatcher = $dispatcher;
//...
}
public function someFunction()
{
//...
$variable = 'something you are using');
$dispatcher->dispatch(
'an.event.that.you.have.set.up',
new GenericEvent($variable)
);
//...
}
Obviously there would be a lot more to both you command your service but this give the basic of how to tie it all together.
An actual use example can be seen here..
Command - https://github.com/Richtermeister/Sylius/blob/subscription-bundle/src/Sylius/Bundle/SubscriptionBundle/Command/ProcessSubscriptionsCommand.php
Service - https://github.com/Richtermeister/Sylius/blob/subscription-bundle/src/Sylius/Bundle/SubscriptionBundle/Processor/SubscriptionProcessor.php
This seems to be the solution: http://symfony.com/blog/new-in-symfony-2-4-show-logs-in-console

Symfony2 command change environment

I want to set up an order allowing me to make clear: cache test mode, then do a drop database, drop scheama, add scheme, add fixtures in test mode.
class BaseCommand extends \Symfony\Component\Console\Command\Command {
//put your code here
protected function configure()
{
$this
->setName('mycommand:test')
->setDescription('Launch test')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$command_first_migration = $this->getApplication()->find('cache:clear');
$arguments_first_migration = array(
'command' => 'cache:clean',
'--env' => 'test'
);
$input_first_migration = new ArrayInput($arguments_first_migration);
try {
$returnCode = $command_first_migration->run($input_first_migration, $output);
} catch (\Doctrine\DBAL\Migrations\MigrationException $ex) {
echo "MigrationExcepion !!!! ";
}
}
}
but I have this result :
clearing the case for the dev environment with debug true
How to pass the test in dev environment?
thank you
You can't set the --env=test since the Kernel and the environment are already created when you run php app/console mycommand:test.
The only way is to specify the env when you run your command :
php app/console mycommand:test --env=test

Categories