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.
Related
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 am new in symfony 4 and I've done the CRUD. I want to enhance my code by creating a function that will lessen it.
Example:
If you have 2 modules like manage event and announcement(ofcourse you will have here add,get all,delete, and update). Instead of having a long code like this.
$fetch_item = $this->getDoctrine()
->getRepository(Event::class)
->findAll();
I want to short it like $fetch = $this->fetch(Event::class); I created a new file in my Service directory.
Service\Crud.php
<?php
namespace App\Service;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
/**
*
*/
class Crud extends AbstractController
{
public function __construct(){
parent::__construct();
}
public function fetch($table)
{
$fetch_item = $this->getDoctrine()
->getRepository($table)
->findAll();
return $fetch_item;
}
}
?>
Controller
//
...
use App\Service\Crud;
...
class EventController extends AbstractController
public function index()
{
// $fetch_item = $this->getDoctrine()
// ->getRepository(Item::class)
// ->findAll();
$fetch = $this->fetch(Item::class);
return $this->render('base.html.twig',array(
'items' => $fetch_item
));
}
Above is my code but it gives me an error "Attempted to call an undefined method named "fetch" of class "App\Controller\ItemController""
Question: How can I create a function that will lessen my code?
There is no reason for the fetch function to be part of a controller (on the contrary there are lots of reasons not to be). What you need is a simple service:
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
class CrudService {
protected $em;
public function __construct(EntityManagerInterface $em){
$this->em = $em;
}
public function fetch($entityClass) {
return $this->em->getRepository($entityClass)->findAll();
}
}
Then in your controller you just have to inject it through autowiring and use it:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Service\CrudService;
use App\Entity\Item;
...
class EventController extends AbstractController {
public function index(CrudService $crudService) {
$items = $crudService->fetch(Item::class);
return $this->render('base.html.twig',array(
'items' => $items
));
}
}
I created some new events like app.client_enter or app.client_leave. Now I want to register a listener to listen on this events. If I add a listener in the same command, it's working.
ClientListener.php
namespace AppBundle\Service;
use AppBundle\Event\ClientEnterEvent;
class ClientListener {
public function onClientEnter(ClientEnterEvent $event) {
echo "It could be working";
}
}
service.yml (update)
services:
app.client_listener:
class: AppBundle\Service\ClientListener
tags:
- { name: kernel.event_listener, event: app.client_enter, method: onClientEnter }
ClientCommand.php
namespace AppBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use AppBundle\Event\ClientEnterEvent;
class ClientCommand extends ContainerAwareCommand {
protected function configure() { ... }
protected function execute(InputInterface $input, OutputInterface $output) {
$dispatcher = new EventDispatcher();
$dispatcher->dispatch('app.client_enter', new ClientEnterEvent("Maxi"));
}
It's name: kernel.event_listener for the tag
Thank you to all. I found the solution, in ContainerAwareCommand you have to use the service of event_dispatcher.
ClientCommand.php
namespace AppBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use AppBundle\Event\ClientEnterEvent;
class ClientCommand extends ContainerAwareCommand {
protected function configure() { ... }
protected function execute(InputInterface $input, OutputInterface $output) {
$dispatcher = $this->getContainer->get('event_dispatcher');
$dispatcher->dispatch('app.client_enter', new ClientEnterEvent("Maxi"));
}
After I used this service, my event trigger the listener.
Just a tip hoẃ to make this even better.
Since great Dependency Injection changes in Symfony 3.3+ you can delegate many error prone code to Symfony.
Simplify Service Registration
# app/config/services.yml
services:
_defaults:
autowire: true
AppBundle\:
resouce: '../../src/AppBundle'
It doesn't work for listeners, because of extra tags, but it does for subscribers - I recommend to use them to prevent any extra redundant config proramming.
Get Arguments The Clean Way - Via Constructor
Using that, you can use constructor injection in your commands out of the box.
use Symfony\Component\EventDispatcher\EventDispatcherInterface
class ClientCommand extends Command
{
/**
* #var EventDispatcherInterface
*/
private $eventDispatcher;
public function __construct(EventDispatcherInterface $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->eventDispatcher->dispatch(...);
}
}
To read more about DI changes, see this post with before/after examples.
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');
}
}
We've built up a set of data fixtures to seed the database with all our reference values. We are also using the DoctrineMigrationsBundle to manage schema updates. We would like to trigger the fixture load within our initial schema migration class so the system gets populated before running any additional schema updates.
I found in the docs that you can make migration classes container aware, but I can't figure out how to jump from that to calling/running the data fixtures. I haven't found any good answers on Stackoverflow or via google. Has anyone done this and can point me in the right direction? (or have suggestions on a better way to manage seed data in conjunction with schema migrations). Thanks.
This is using Symfony Version: 2.4
This is interesting question. I've found the "dirty" solution, but it works well.
namespace Application\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
class Version20140811164659 extends AbstractMigration implements ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function up(Schema $schema)
{
// ... your code here
}
public function postUp(Schema $schema)
{
// here you have to define fixtures dir
$this->loadFixtures('src/Acme/BlogBundle/DataFixtures/ORM');
}
public function down(Schema $schema)
{
// ... your code here
}
public function loadFixtures($dir, $append = true)
{
$kernel = $this->container->get('kernel');
$application = new \Symfony\Bundle\FrameworkBundle\Console\Application($kernel);
$application->setAutoExit(false);
//Loading Fixtures
$options = array('command' => 'doctrine:fixtures:load', "--fixtures" => $dir, "--append" => (boolean) $append);
$application->run(new \Symfony\Component\Console\Input\ArrayInput($options));
}
}
This solution simply running console command php app/console doctrine:fixtures:load --fixtures=src/Acme/BlogBundle/DataFixtures/ORM --append after "up" migration.
Sorry for poore English. If you'll find clear solution, share it ;)
I've made a migration class to address this very problem. The code is essentially inspired from the doctrine:fixtures:load command.
<?php
namespace AppBundle\Migrations;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
abstract class AbstractFixturesAwareMigration extends AbstractMigration implements ContainerAwareInterface
{
private $container;
private $fixtures;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
protected function getContainer()
{
return $this->container;
}
protected function addFixture(FixtureInterface $fixture)
{
if(null === $this->fixtures) {
$this->fixtures = new ContainerAwareLoader($this->getContainer());
}
$this->fixtures->addFixture($fixture);
return $this;
}
protected function executeFixtures($em = null, $append = true, $purgeMode = ORMPurger::PURGE_MODE_DELETE)
{
$em = $this->getContainer()->get('doctrine')->getManager($em);
$purger = new ORMPurger($em);
$purger->setPurgeMode($purgeMode);
$executor = new ORMExecutor($em, $purger);
$executor->execute($this->fixtures->getFixtures(), $append);
$this->fixtures = null;
return $this;
}
}
Usage is pretty straightforward:
<?php
namespace Application\Migrations;
use AppBundle\Migrations\AbstractFixturesAwareMigration
use Doctrine\DBAL\Schema\Schema;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20170726102103 extends AbstractFixturesAwareMigration
{
/**
* #param Schema $schema
*/
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
// [...]
}
public function postUp(Schema $schema)
{
// LoadMyData can be any fixture class
$this->addFixture(new LoadMyData());
$this->executeFixtures();
}
/**
* #param Schema $schema
*/
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
// [...]
}
}