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().
Related
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
`
I tried to run the fixture below on Symfony 5 using the command php bin/console d:f:l.
I get this error: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'contact_email' cannot be null
The same code logic is working fine for Post entities when creating them manually through the CRUD. Are fixtures not compatible with subscribers (events) or did i make a mistake?
Thank you.
Edit: I'm also using EasyAdmin Bundle 3.
App\DataFixtures.php\AppFixtures.php
<?php
namespace App\DataFixtures;
use App\Entity\User;
use App\Entity\Author;
use Doctrine\Persistence\ObjectManager;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class AppFixtures extends Fixture
{
/** #var User[] */
private $users = [];
/** #var Author[] */
private $authors = [];
/** #var UserPasswordHasherInterface */
private $hasher;
public function __construct(UserPasswordHasherInterface $hasher)
{
$this->hasher = $hasher;
}
public function load(ObjectManager $manager): void
{
$this->createUsers();
foreach($this->users as $user) $manager->persist($user);
$this->createAuthors();
foreach($this->authors as $author) $manager->persist($author);
$manager->flush();
}
public function createUsers(): void
{
$admin = (new User)
->setUsername('admin')
->setEmail('admin#admin.com')
->setRoles(['ROLE_ADMIN'])
->setFirstname('Edouard')
->setLastname('Proust');
$admin->setPassword($this->hasher->hashPassword($admin, 'admin'));
$this->users[] = $admin;
}
public function createAuthors(): void
{
foreach($this->users as $user) {
if(in_array('ROLE_ADMIN', $user->getRoles())) {
$author = (new Author)
->setUser($user)
->setAvatar('#')
->setBio('Bio')
// The line i want to get rid of:
// ->setContactEmail($user->getEmail())
;
$this->authors[] = $author;
}
}
}
}
App\EventListener\AuthorSubscriber.php
<?php
namespace App\EventListener;
use App\Entity\Author;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityPersistedEvent;
class AuthorSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
BeforeEntityPersistedEvent::class => 'setContactEmail',
];
}
public function setContactEmail(BeforeEntityPersistedEvent $event)
{
/** #var Author */
$entity = $event->getEntityInstance();
if($entity instanceof Author) {
if(!$entity->getContactEmail()) {
$user = $entity->getUser();
$contactEmail = $user ? $user->getEmail() : '#';
$entity->setContactEmail($contactEmail);
}
}
}
}
EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityPersistedEvent:class is not proper Symfony event name. You probably should use Doctrine\ORM\Events::prePersist.
Also please check your DoctrineBundle version. If you're using the default services.yaml configuration and DoctrineBundle lower than 2.1, you have to configure services.yaml with:
App\EventListener\AuthorSubscriber:
tags:
- name: 'doctrine.event_subscriber'
You can read something more here: https://symfony.com/doc/current/doctrine/events.html#doctrine-lifecycle-subscribers
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
I want to use custom tags on my services, so I followed the instructions in the documentation: http://symfony.com/doc/2.8/components/dependency_injection/tags.html
I have a RulesHydrator class:
<?php
namespace TestBundle\Thruway;
class RulesHydrator
{
private $container;
private $manualChecks = [];
public function __construct($container)
{
$this->container = $container;
}
public function addManualCheck($service, $rule, $method)
{
echo 'addManualCheck invoked!'.PHP_EOL;
exit;
$this->manualChecks[$rule] = $service;
}
}
Here is the compiler pass:
<?php
namespace TestBundle\Thruway;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
class ThruwayCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->has('thruway.rules_hydrator')) {
return;
}
$definition = $container->findDefinition('thruway.rules_hydrator');
foreach ($container->findTaggedServiceIds('thruway.manual_check') as $id => $tags) {
foreach ($tags as $attributes) {
$definition->addMethodCall('addManualCheck', [new Reference($id), $attributes['rule'], $attributes['method']]);
}
}
}
}
Here is my bundle's class:
<?php
namespace TestBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use TestBundle\Thruway\ThruwayCompilerPass;
class TestBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ThruwayCompilerPass());
}
}
My services.yml file look like this:
services:
thruway.rules_hydrator:
class: TestBundle\Thruway\RulesHydrator
thruway.customer_checker:
class: TestBundle\Thruway\MyChecker
tags:
- { name: thruway.manual_check, rule: some.rule1, method: someMethod1 }
- { name: thruway.manual_check, rule: some.rule2, method: someMethod2 }
The process method is called and the different calls to addMethodCall on my Definition object work fine (the property "calls" of the definition is correctly filled). The problem is that the calls to my method addManualCheck never occur. Any idea why?
The case could be that you did not instantiated the service. By default from what I remember the services are lazy loaded, and until you actually fetch one from the container, or it is injected into an other service, it will not be initialized.
Can you look into your appProdProjectContainer.php inside app/cache/prod for the "TestBundle\Thruway\MyChecker" and post back how it is used?
Also try a fast check by getting the thruway.customer_checker from the container.
A fast command like this could help
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class TestCommand extends ContainerAwareCommand
{
protected function configure()
{
$this->setName('service:test')->setDescription('Test service functionalities');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$service = $this->getContainer()->get('thruway.customer_checker');
}
}
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.