I would like to run a command via a controller. My command run normaly in cli (tests are done)
My controller call the command and I got immediately
[OK] success
from the command return but tests did not run.
my controller
`
<?php
namespace App\Controller\Api;
use OpenApi\Annotations as OA;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Process\Process;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
class UnitTestController extends AbstractController
{
private $kernel;
public function __construct(KernelInterface $kernel)
{
$this->kernel = $kernel;
}
#[Route('/unit_tests', name: 'unit_test', methods: 'GET')]
public function unitTest(): Response
{
$application = new Application($this->kernel);
$application->setAutoExit(false);
$input = new ArrayInput([
'command' => 'mapi:unitTests',
]);
$output = new BufferedOutput();
$application->run($input, $output);
// return the output, don't use if you used NullOutput()
$content = $output->fetch();
dd($content);
}
}
`
and my command
`
<?php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Process\Process;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
#[AsCommand(
name: 'mapi:unitTests',
description: 'lancement des tests unitaires',
)]
class unitTestCommand extends Command
{
/**
* #param InputInterface $input
* #param OutputInterface $output
* #return int
* #throws ClientExceptionInterface
* #throws DecodingExceptionInterface
* #throws RedirectionExceptionInterface
* #throws ServerExceptionInterface
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
ini_set('max_execution_time', 300);
$process = Process::fromShellCommandline('php vendor/bin/phpunit');
$process->run();
$io->success('success');
return Command::SUCCESS;
}
}
edit: no error, unit tests from command are not executed. when I run the command from the cli, everything is ok
`
Related
I'd like to run a command in a separate process from a unit test and obtain its output. I've tested several options like using shell_exec or the ApplicationTester-class. While the command is obviously executed (I checked by inserting some side-effects), its output cannot be obtained when executed from the unit test.
However, if I create a small stand-alone php-file (see below, runDummyCmd.php) that contains a corresponding shell_exec, I obtain the output of the symfony command.
How to reproduce
Dummy-Command:
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class DummyCommand extends Command
{
/**
* {#inheritdoc}
*/
protected function configure()
{
$this
->setName('app:dummycmd')
->setDescription('Can be used for testing commands')
->setHelp('Does a simple output.');
}
/**
* {#inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('Test!');
return 0;
}
}
Test-Case:
class MyTest extends MyKernelTestCase
{
protected $projectDir;
protected function setUp(): void
{
parent::setUp();
$this->projectDir = static::$container->getParameter('kernel.project_dir');
}
public function testGetOutputOfCommand() {
$cmd = 'env APP_ENV=test '. $this->projectDir . '/bin/console app:dummycmd';
dump($cmd);
$output = shell_exec($cmd);
dump($output);
}
}
runDummyCmd.php:
<?php
$cmd = 'env APP_ENV=test bin/console app:dummycmd';
var_dump($cmd);
$output = shell_exec($cmd);
var_dump($output);
?>
Outputs
Output of the unit-test:
"env APP_ENV=test PROJECT_DIR/bin/console app:dummycmd"
null
Output of the execution of runDummyCmd.php on the console:
string(41) "env APP_ENV=test bin/console app:dummycmd"
string(6) "Test!
"
Have you tried the CommandTester class?
$command = $application->find('app:dummycmd');
$commandTester = new CommandTester($command);
$commandTester->execute([
'command' => $command->getName()
]);
$output = $commandTester->getDisplay();
I am using Symfony 4.2.1 and I cannot somehow not use the generate() Method of router:
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Routing\RouterInterface;
class NewAcceptedOrderCommand extends Command
{
/**
* #var RouterInterface $router
*/
private $router;
public function __construct(
RouterInterface $router
) {
$this->router = $router;
parent::__construct();
}
protected function configure()
{
$this
->setName('address:new')
->setDescription('Get adresses by Status ID')
->setHelp('Get adresses by Status ID')
->addArgument('id', InputArgument::REQUIRED, 'Status ID');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$url = $this->router->generate('adresses_index');
// SOME more Code
}
}
I injected all sorts of other services and interfaces before (EntityManagerInterface, my Logservice etc.)
But I am always getting
Unable to generate a URL for the named route "adresses_index" as such
route does not exist.
But this route exists for sure I checked with php bin/console debug:router and also using it in other places. I am using the global approach from here: https://symfony.com/doc/current/console/request_context.html#configuring-the-request-context-globally
I was thinking to secure at the beginning and added the annotation "method" to every action - it has to be unset (ANY). It works fine afterwards.
Dont forget to configure your router
$context = $this->router->getContext();
$context->setHost($this->container->getParameter('router_host'));
$context->setScheme($this->container->getParameter('router_scheme'));
I create a symfony command that executes a function in a controller but when I run the command I get this error message:
[Symfony \ Component \ Debug \ Exception \ FatalThrowableError]
Fatal error: Call to a member function has () on null
My Services.yml
services:
app.site_batch:
class: App\SiteBundle\Controller\BatchController
arguments:
entityManager: "#doctrine.orm.entity_manager"
container: "#service_container"
My Controller
<?php
namespace App\SiteBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use ChrisCom\BDDBundle\Entity\Paiement;
use ChrisCom\BDDBundle\Entity\BBooking;
/**
* #Route("/batch")
*/
class BatchController extends Controller
{
private $log_file;
private $output;
private function openLog($filename) { $this->log_file = fopen($this->get('kernel')->getLogDir() . '/' . $filename, 'a'); }
private function log($line, $message)
{
if ($this->log_file)
{
$msg = sprintf("[%s %s:%03d] %s\n", date('d/m/Y H:i:s'), basename(__FILE__), $line, $message);
fputs($this->log_file, $msg);
$this->output .= $msg;
}
}
private function closeLog() { if ($this->log_file) fclose($this->log_file); }
/**
* #Route("/check_bookings", name="batch_check_bookings")
*/
public function checkBookingsAction($simul)
{
$em = $this->getDoctrine()->getManager();
$rep_book = $em->getRepository('ChrisComBDDBundle:BBooking');
$cur_date = new \DateTime();
$format_cmp = 'YmdHis';
$flush = false;
....
My Command
<?php
namespace App\SiteBundle\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 CheckBookingsCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('cron:checkBookings')
->setDescription('Vérification des réservation')
->addOption(
'simul',
null,
InputOption::VALUE_NONE,
'Si défini, le programme ne fera pas de COMMIT en base de donnée'
);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln("Starting the cron");
// Call the method here
$version = $this->getContainer()->get('app.bdd')->parameterGet('VERSION');
$simul = ($input->getOption('simul'));
if ($simul) $output->writeln("Mode simulation");
$output->writeln($this->getContainer()->get('app.site_batch')->checkBookingsAction($simul));
$output->writeln("Cron completed");
}
}
I searched but without result ...
Thank you for help.
In your BatchController :
public function checkBookingsAction($simul)
{
$em = $this->container->get('doctrine')->getManager();
// ...
}
When you use a controller as a service, you lose the possibility to use $this->getDoctrine().
I get this error
Cannot access empty property
when I try this command abc:check-car
<?php
namespace CarBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
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 AbcCheckCarCommand extends Command
{
protected $carChecker;
protected $manager;
/**
* AbcCheckCarCommand constructor.
* #param $carChecker
* #param $manager
*/
public function __construct($carChecker, $manager)
{
$this->carChecker = $carChecker;
$this->manager = $manager;
parent::__construct();
}
protected function configure()
{
$this
->setName('abc:check-car')
->setDescription('...')
->addArgument('format', InputArgument::OPTIONAL, 'format progress bar')
->addOption('option', null, InputOption::VALUE_NONE, 'Option description')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$carRepository = $this->$manager->getRepository('CarBundle:Car');
$cars = $carRepository->findAll();
$bar = new ProgressBar($output, count($cars));
$argument = $input->getArgument('format');
$bar->setFormat($argument);
$bar->start();
foreach ($cars as $car)
{
$this->carChecker->checkCar($car);
sleep(1);
$bar->advance();
}
$bar->finish();
}
}
change
$carRepository = $this->$manager->getRepository('CarBundle:Car');
to
$carRepository = $this->manager->getRepository('CarBundle:Car');
mind $ before manager
How can I use $this->renderView inside a symfony Command (not inside a controller)? I new about the function "renderView" but what do I have to setup to use it wihtin a command?
Thank you in advance an regards
Your command class must extends the ContainerAwareCommand abstract class and then you can do:
$this->getContainer()->get('templating')->render($view, $parameters);
When it comes to commands that extend ContainerAwareCommand the proper way to obtain the container is by getContainer() unlike in controller shortcut.
In Symfony 4 I could not get $this->getContainer()->get('templating')->render($view, $parameters); to work.
I set the namespace use for Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand and extended ContainerAwareCommand class EmailCommand extends ContainerAwareCommand
I get an exception thrown
[Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException]
You have requested a non-existent service "templating".
For Symfony 4, this is the solution I came up with.
First I installed Twig.
composer require twig
Then created my own twig service.
<?php
# src/Service/Twig.php
namespace App\Service;
use Symfony\Component\HttpKernel\KernelInterface;
class Twig extends \Twig_Environment {
public function __construct(KernelInterface $kernel) {
$loader = new \Twig_Loader_Filesystem($kernel->getProjectDir());
parent::__construct($loader);
}
}
Now my email command looks like this.
<?php
# src/Command/EmailCommand.php
namespace App\Command;
use Symfony\Component\Console\Command\Command,
Symfony\Component\Console\Input\InputInterface,
Symfony\Component\Console\Output\OutputInterface,
App\Service\Twig;
class EmailCommand extends Command {
protected static $defaultName = 'mybot:email';
private $mailer,
$twig;
public function __construct(\Swift_Mailer $mailer, Twig $twig) {
$this->mailer = $mailer;
$this->twig = $twig;
parent::__construct();
}
protected function configure() {
$this->setDescription('Email bot.');
}
protected function execute(InputInterface $input, OutputInterface $output) {
$template = $this->twig->load('templates/email.html.twig');
$message = (new \Swift_Message('Hello Email'))
->setFrom('emailbot#domain.com')
->setTo('someone#somewhere.com')
->setBody(
$template->render(['name' => 'Fabien']),
'text/html'
);
$this->mailer->send($message);
}
}
Yet another one: rely on dependency injection, i.e. inject ContainerInterface
namespace AppBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Psr\Container\ContainerInterface;
class SampleCommand extends Command
{
public function __construct(ContainerInterface $container)
{
$this->templating = $container->get('templating');
parent::__construct();
}
protected function configure()
{
$this->setName('app:my-command')
->setDescription('Do my command using render');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$data = retrieveSomeData();
$csv = $this->templating->render('path/to/sample.csv.twig',
array('data' => $data));
$output->write($csv);
}
private $templating;
}
This relies on Symfony to inject the container, which in turn is used to retrieve either templating or twig or whatever you need for your custom command.