I need to render a twig template from a command class in symfony2.
namespace IT\bBundle\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 CronCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('send:emails')
->setDescription('Envio programado de emails');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$message = \Swift_Message::newInstance()
->setSubject('bla bla')
->setFrom('x#x.com')
->setTo('x#gmail.com')
->setCharset('UTF-8')
->setContentType('text/html')
->setBody($this->renderView('mainBundle:Email:default.html.twig'));
$this->getContainer()->get('mailer')->send($message);
$output->writeln('Enviado!');
}
}
But when I execute the command php app/console send:emails I get the following error:
Fatal error: Call to undefined method IT\bBundle\Command\CronCommand::renderView()
How can I render the view?
It's because renderView is method of class Controller. Instead of that try:
$this->getContainer()->get('templating')->render(...);
Change
$this->renderView()
to
$this->getContainer()->get('templating')->render()
Maybe, not exactly the question you ask, but for sure - important.
Please, do remember that if you want to send emails via Command call, you need to flushQueue.
$mailer = $container->get('mailer');
$spool = $mailer->getTransport()->getSpool();
$transport = $container->get('swiftmailer.transport.real');
$spool->flushQueue($transport);
Related
I have this code...
namespace AppBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
class NotificationCommand extends ContainerAwareCommand
{
protected function configure()
{
$this->setName('app:notif');
}
public function execute(InputInterface $input, OutputInterface $output)
{
$message = \Swift_Message::newInstance()
->setFrom([
'from#example.com' => 'Example'
])
->setTo('to#example.com')
->setSubject('Subject')
->setBody(
$this->getContainer()->renderView(
'emails/notify.html.twig', [
'foo' => 'bar',
]
),
'text/html'
);
$this->getContainer()->get('mailer')->send($message);
}
}
And I get an error in response
Attempted to call an undefined method named "renderView" of class
"ContainerV5drzta\appDevDebugProjectContainer".
How do I use Swift Mailer in my Command (Symfony 3.4)?
You can dependency inject these services that should solve the problem and it's also the way Symfony is trying to turn towards.
public function __construct(
EngineInterface $templating,
\Swift_Mailer $mailer,
) {
$this->templating = $templating;
$this->mailer = $mailer;
parent::__construct();
}
You can now in your execute() render a template like so:
$message = (new \Swift_Message('My message'))
->setFrom('foo#bar.com')
->setTo('bar#foo.com')
->setBody($this->templating->render('mytemplate.html.twig'), 'text/html');
$this->mailer->send($message);
You can read more about dependency injecting in Symfony here or a more generic article here.
I'm trying to spec Symfony command and I want to have formated output with SymfonyStyle
<?php
namespace Acme\AppBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class SomeCommand extends ContainerAwareCommand
{
//....
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$io->title('Feed import initiated');
}
}
and spec file:
<?php
namespace spec\Acme\AppBundle\Command;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class SomeCommandSpec extends ObjectBehavior
{
//...
function it_fetches_social_feeds(
ContainerInterface $container,
InputInterface $input,
OutputInterface $output,
SymfonyStyle $symfonyStyle
) {
// With options
$input->bind(Argument::any())->shouldBeCalled();
$input->hasArgument('command')->shouldBeCalled();
$input->isInteractive()->shouldBeCalled();
$input->validate()->shouldBeCalled();
$symfonyStyle->title(Argument::any())->shouldBeCalled();
$this->setContainer($container);
$this->run($input, $output);
}
}
but I'm getting this error:
exception [err:Error("__clone method called on non-object")] has been thrown.
0 vendor/symfony/symfony/src/Symfony/Component/Console/Style/SymfonyStyle.php:50
throw new PhpSpec\Exception\ErrorException("__clone method called on ...")
1 vendor/symfony/symfony/src/Symfony/Component/Console/Application.php:866
Symfony\Component\Console\Command\Command->run([obj:Symfony\Component\Console\Input\ArgvInput], [obj:Symfony\Component\Console\Output\ConsoleOutput])
2 vendor/symfony/symfony/src/Symfony/Component/Console/Application.php:193
Symfony\Component\Console\Application->doRunCommand([obj:PhpSpec\Console\Command\RunCommand], [obj:Symfony\Component\Console\Input\ArgvInput], [obj:Symfony\Component\Console\Output\ConsoleOutput])
3 vendor/phpspec/phpspec/src/PhpSpec/Console/Application.php:102
Symfony\Component\Console\Application->doRun([obj:Symfony\Component\Console\Input\ArgvInput], [obj:Symfony\Component\Console\Output\ConsoleOutput])
4 vendor/phpspec/phpspec/bin/phpspec:26
Symfony\Component\Console\Application->run()
5 vendor/phpspec/phpspec/bin/phpspec:28
{closure}("3.2.2")
on line 50 of SymfonyStyle is:
public function __construct(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
/* line 50 */ $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter());
// Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
$this->lineLength = min($this->getTerminalWidth() - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);
parent::__construct($output);
}
and phpspec is complainig about
clone $output->getFormatter()
Am I doing something wrong, or am I missing something?
Update
this is my let method:
function let(SymfonyStyle $symfonyStyle, InputInterface $input, OutputInterface $output)
{
$symfonyStyle->beConstructedWith([$input->getWrappedObject(), $output->getWrappedObject()]);
}
You are missing this
function it_fetches_social_feeds(
ContainerInterface $container,
InputInterface $input,
OutputInterface $output,
SymfonyStyle $symfonyStyle
) {
// .... other code
$prophet = new Prophet();
$formatter = $prophet->prophesize(OutputFormatterInterface::class);
$output->getFormatter()->willReturn($formatter);
// .... more code
}
You can place it where you want but before the call is done.
In that way you have created a Stub that is basically a Double with behavior and without expectations. You can see it as a "proxy" that will intercept method calls and returns what you "teach" it to return.
In your example things get broken as your double OutputInterface would return null as it's not a "real" object.
I also suggest to stub the getVerbosity behavior if you need to do different kind of specs.
BTW you can read more about doubles in phpspec guide and prophecy guide
Looking at an example, SymfonyStyle never seems to be passed in the spec class.
The execute function of the command doesn't pass it either, just the input and output classes (the style class it created as a local variable).
I have a command which executes some actions that depend on the entity passed in parameter.
checkAlertCommand.php:
<?php
namespace MDB\PlatformBundle\Command;
use Symfony\Component\Console\Command\Command;
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 checkAlertCommand extends Command {
protected function configure() {
$this
->setName('platform:checkAlert')
->setDescription('Check the alert in in function of the current advert')
->addArgument(
'postedAdvert'
);
}
protected function execute(InputInterface $input, OutputInterface $output) {
$postedAdvert = $input->getArgument('postedAdvert');
$output->writeln($postedAdvert->getTitre());
}
}
?>
So my questions are:
How to get an entity as argument in the checkAlertCommand.php?
How to call this command from a controller and pass the desired entity as argument?
Thanks.
You can't pass an entity directly to a console command. Instead of you should pass "id" of entity as argument, then use repository and pick up desired entity by its id.
<?php
namespace MDB\PlatformBundle\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 checkAlertCommand extends ContainerAwareCommand {
protected function configure() {
$this
->setName('platform:checkAlert')
->setDescription('Check the alert in in function of the current advert')
->addArgument(
'postedAdvertId'
);
}
protected function execute(InputInterface $input, OutputInterface $output) {
$postedAdvertId = $input->getArgument('postedAdvertId');
$em = $this->getContainer()->get('doctrine')->getManager();
$repo = $em->getRepository('MDBPlatformBundle:PostedAdvert');
$postedAdvert = $repo->find($postedAdvertId);
$output->writeln($postedAdvert->getTitre());
}
}
?>
You should use Process component to run the command inside a controller.
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
use MDB\PlatformBundle\Command\checkAlertCommand;
class MyController extends Controller
{
public function indexAction()
{
// get post $postedAdvertId here
....
$command = new checkAlertCommand();
$command->setContainer($this->container);
$input = new ArrayInput(array('postedAdvertId' => $postedAdvertId));
$output = new NullOutput();
$result = $command->run($input, $output);
...
}
}
Update: Answer to your question
I'm not sure what exactly do you mean "asynchronous", but given example executes the command in synchronous way, so mean the controller will wait till command will be finished and only then will go to next operation. But if you need run it in asynchronous(in background) way,you should use Process component http://symfony.com/doc/current/components/process.html
I'm using sonata Mongodb AdminBundle for my back-end, in my case I need to create a new action for my user model ( sending a mail to him ), I followed the documentation literally
https://sonata-project.org/bundles/admin/master/doc/cookbook/recipe_custom_action.html
but I got a weird error :
Compile Error: Cannot use Sonata\AdminBundle\Controller\CRUDController as Controller because the name is already in use
and this is my CRUDController Code :
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Session\UserBundle\Document\User;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
class CrudController extends Controller
{
public function inscriptionAction()
{
$mail = 'towho#someone.com';
$pinCode = '1klm8';
$sender = 'Mymail#gmail.com';
$dm = $this->get('doctrine_mongodb')->getManager();
$userManager = $this->container->get('fos_user.user_manager');
$user = $userManager->createUser();
$user->setEmail($mail);
$user->setUsername($mail);
$user->setPlainPassword($pinCode);
$user->setEnabled(true);
$userManager->updateUser($user, true);
$message = \Swift_Message::newInstance()
->setSubject('Test de recrutement')
->setFrom($sender)
->setTo($mail)
->setCharset('utf-8')
->setContentType('text/html')
->setBody(
$this->renderView(
'ATSQuizzBundle:Default:SwiftLayout/createUser.html.twig',
array('user' => $user, 'pinCode' => $pinCode)
)
);
$this->get('mailer')->send($message);
$this->addFlash('sonata_flash_success', 'mail sent to the candidate');
return new RedirectResponse($this->admin->generateUrl('list'));
}
}
any one knows the origin of this error please ?
You have already imported the Controller class. And so you need to rename it in the second case. Override your use-block with next:
use Session\UserBundle\Document\User;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
As you see I deleted first occurrence of Controller class that is not used in your code. But if you have some other code in this file that can use "old" Controller I would recommend you to rename last Controller to BaseController and extend your class from BaseController.
TL;DR
I'm creating console Garbage collector, which should be able to get services from container.
It's basic, almost straight from the manual:
<?php
namespace SomeBundle\Console\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand,
Symfony\Component\Console\Input\InputArgument,
Symfony\Component\Console\Input\InputInterface,
Symfony\Component\Console\Input\InputOption,
Symfony\Component\Console\Output\OutputInterface;
class GarbageCollector extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('garbage:collect')
->setDescription('Collect garbage')
->addArgument(
'task',
InputArgument::REQUIRED,
'What task to execute?'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$task = $input->getArgument('task');
//...
$_container = $this->getContainer();
}
}
Then I'm trying to call it from console via application.php:
#!/usr/bin/env php
<?php
// application.php
require_once __DIR__.'/../vendor/autoload.php';
use SomeBundle\Console\Command\GarbageCollector;
use Symfony\Bundle\FrameworkBundle\Console\Application;
$application = new Application();
$application->add(new GarbageCollector);
$application->run();
Which produces fatal error:
Argument 1 passed to
Symfony\Bundle\FrameworkBundle\Console\Application::__construct() must
implement interface Symfony\Component\HttpKernel\Kernel Interface,
none given
Manual says that only thing I need to do is to extend my class with ContainerAwareCommand, but something missing. I've wrote some crap code to pass Kernel to the Application():
#!/usr/bin/env php
<?php
// application.php
require_once __DIR__.'/../vendor/autoload.php';
require_once __DIR__.'/AppKernel.php';
use SomeBundle\Console\Command\GarbageCollector;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
$input = new ArgvInput();
$env = $input->getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev');
$debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(array('--no-debug', '')) && $env !== 'prod';
$kernel = new AppKernel($env, $debug);
$application = new Application($kernel);
$application->add(new GarbageCollector);
$application->run();
And it works, but feels disgusting.
What do I need to make ContainerAwareCommand implementation of console app? Thanks in advance.
Symfony2 provides via its console runner the ability to call your custom commands and it will handle the injection of the service container. All you need to do is create your command in a registered bundle and it will automatically become available to you. You are initializing your command outside of the framework that is why the container is not available to you unless you manually inject it.
You can reference the cookbook here:
http://symfony.com/doc/current/cookbook/console/console_command.html
You can get a list of all the commands available by running:
php app/console
On a side note, why are you creating a garbage collector in/for PHP? Just curious because to my understanding memory is freed for you after script execution has ended.
As by version 4.1 you can inject the container service at the constructor.
namespace App\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class MyCommand extends Command {
protected $container;
public function __construct(ContainerInterface $container) {
$this->container = $container;
parent::__construct();
}
protected function configure() {
// ...
}
protected function execute(InputInterface $input, OutputInterface $output) {
// You must make Some\Service\Class public.
// Documentation at https://symfony.com/doc/current/service_container.html#public-versus-private-services
$some_service = $this->container->get('Some\Service\Class');
}
}