i have problem with get my class function to which i send files from terminal. It's see that:
terminal: // php src/AppBundle/Provider/CommissionCost.php test.csv
in CommissionCost i want only put data to my function in Parser/CommissionDataParser.php
global $kernel;
$data = $kernel->getContainer()->get('data.parser')->getData($argv[1]);
var_dump($data);
//Uncaught Error: Call to a member function getContainer() on null
// this example i see in stackoverflow in topic about container : )
service:
services:
data.parser:
class: AppBundle\Parser\CommissionDataParser
arguments: ["#doctrine.orm.entity_manager"]
You need to add this in the configure method:
$this->addArgument('arg1', InputArgument::REQUIRED, 'Your first arg');
Then, you can access to it in the execute method by:
$input->getArgument('arg1');
Related
I am using the m6web_guzzle bundle to register several http clients:
m6web_guzzlehttp:
clients:
myclient:
timeout: 3
headers:
"Accept": "application/json"
delay: 0
verify: false
I want to call a method on a service that it dynamically generates. In this case the generated service name is:
#m6web_guzzlehttp.guzzle.handlerstack.myclient
Here is what I do in my service constructor: (the 3rd parameter injected is '#m6web_guzzlehttp.guzzle.handlerstack.myclient')
/**
* #param array $parameters
* #param Client $client
* #param HandlerStack $handlerStack
*/
public function __construct(array $parameters, Client $client, HandlerStack $handlerStack)
{
$this->parameters = $parameters;
$this->client = $client;
$this->handlerStack->push(Middleware::retry([$this, 'retryDecider']));
}
So far, it works well, but how can I transfer the last line (the push call) in my services.yml file? Or another cleaner method to register this retry handler?
So compiler passes were mentioned before. That is one option.
Use factories to create instances
But you can nearly express this also directly in your services definition. I say nearly, because you will need some kind of code as Symfony service definitions cannot (AFAIK) evaluate to a Closure - which is what we need for the Guzzle Middleware.
I wrote up this services.yml as an example:
m6web_guzzlehttp.guzzle.handlerstack.myclient:
class: GuzzleHttp\HandlerStack
factory: ['GuzzleHttp\HandlerStack', create]
retry_decider:
class: MyBundle\RetryDecider
factory: ['MyBundle\RetryDecider', createInstance]
retry_handler:
class: GuzzleHttp\Middleware
factory: ['GuzzleHttp\Middleware', retry]
arguments:
- '#retry_decider'
handlerstack_pushed:
parent: m6web_guzzlehttp.guzzle.handlerstack.myclient
calls:
- [push, ['#retry_handler']]
What is what?
m6web_guzzlehttp.guzzle.handlerstack.myclient - Your dynamic service - remove from example as you have this already created.
retry_decider - Your decider. We return a Closure in the createInstance method. You can add more parameters if you need, just add arguments to your YML.
retry_handler - Here we compose the middleware using our decider
handlerstack_pushed - Here we push() our handler into the stack, using the dynamic service as a parent service.
Et voilĂ - we have the stack that the dynamic service defined, but pushed our retry middleware.
Here is the source for our decider:
<?php
namespace MyBundle;
class RetryDecider {
public static function createInstance() {
return function() {
// do your deciding here
};
}
}
--> You now have the service handlerstack_pushed which is the complete Stack.
Configuring more
Please note that you could add m6web_guzzlehttp.guzzle.handlerstack.myclient to parameters.yml:
parameters:
baseHandlerStackService: m6web_guzzlehttp.guzzle.handlerstack.myclient
Then use that on handlerstack_pushed:
handlerstack_pushed:
parent: "%baseHandlerStackService%"
calls:
- [push, ['#retry_handler']]
Just nicer like that ;-)
In your bundle's Extension.php file, you can override the load method and add:
$definition = $container->getDefinition('m6web_guzzlehttp.guzzle.handlerstack.myclient');
$definition->addMethodCall('push', [Middleware::retry([$this, 'retryDecider'])]);
You can write a compiler pass that grabs the definition in question and adds the method call to it.
i have this code to run schema update command from controller i got help from symfony document
i have this code:
namespace AdminBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class DefaultController extends Controller
{
/**
* #Route("/admin")
*/
public function indexAction(KernelInterface $kernel)
{
$application = new Application($kernal);
$input = new ArrayInput(array(
'command' => 'doctrine:schema:update'
));
$output = new NullOutput();
$application->run($input, $output);
return new Response("");
}
}
it's not work for me i get this error after open this url (http://127.0.0.1:8000/admin):
Controller "AdminBundle\Controller\DefaultController::indexAction()" requires that you provide a value for the "$kernel" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.
how can i do?
Instead of injecting the KernelInterface $kernel directly into your action (I guess, you're not using it as a declared service), call your container directly for asking the kernel:
public function indexAction()
{
$kernel = $this->get('kernel');
$application = new Application($kernel); // btw: you have an typo in here ($kernal vs $kernel)
$input = new ArrayInput(array(
'command' => 'doctrine:schema:update'
));
$output = new NullOutput();
$application->run($input, $output);
// tip: use "204 No Content" to indicate "It's successful, and empty"
return new Response("", 204);
}
While Michael's answer works, it is not the preferred method in Symfony 3.3, which had several changes to dependency injection. Your code will actually work just fine with some changes to your services configuration.
As the documentation states, the Dependency Injection Container changed in Symfony 3.3, and by default your controllers are registered as services:
# app/config/services.yml
services:
# ...
# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
This allows you to autowire the kernel through arguments in your controller action method, like you tried. The reason yours isn't working is because your AdminBundle is likely not set up the way your AppBundle is by default in app/config/services.yml. To truly solve the issue in the way that Symfony 3.3 wants, you should add AdminBundle to your services configuration like so:
# app/config/services.yml
services:
# add this below your AppBundle\Controller definition
AdminBundle\Controller\:
resource: '../../src/AdminBundle/Controller'
public: true
tags: ['controller.service_arguments']
With that, you no longer have to call $this->get('kernel');, and your original code will work as you have it, with KernelInterface as a parameter to your action method.
Furthermore, you can extend the new AbstractController instead of the regular Controller, and then calls to $this->get() will not work anymore, which is the way Symfony is going.
So again while Michael's answer will work just fine, I would advise you to implement the answer I've given simply because Symfony 3.3 prefers that method going forward.
I'm a newbie in Symfony.
I trying to change Monolog output formatter by console argument 'format=json'.
In short, I want to run any console command with the way:
app/console my_command --format=json # xml / txt / my own
...and get output of LoggerInterface in requested format.
For example, I set the default formatter in configuration:
monolog:
handlers:
console:
type: console
channels: [!event, !doctrine]
formatter: json_formatter
services:
json_formatter:
class: Monolog\Formatter\JsonFormatter
When I create the some MyEventListener::onConsoleCommand (as described here), I cannot change the parameters bag because it is already compiled: "Impossible to call set() on a frozen ParameterBag."
Up2: My services config in this case looks like this:
services:
kernel.listener.command_dispatch:
class: My\Listener\MyEventListener
autowire: true
tags:
- { name: kernel.event_listener, event: console.command }
With another way, I can register console option inside initial file:
# app/console
$loader = require __DIR__.'/autoload.php';
# ...
$application->getDefinition()->addOption(
new InputOption(
'formatter',
'f',
InputOption::VALUE_OPTIONAL,
'The logs output formatter',
'json_formatter'
)
);
But I can't find a way to change parameters bag in the Container. Because $application->getKernel()->getContainer() is still empty.
So, how to change Symfony2 parameters from console input?
Alternatively, maybe I can just use some environments parameters? But how I can get an environment variable in YAML configuration?
Thank you.
UP3:
I have achieved the goal with environments variables like this:
SYMFONY__LOG__FORMATTER=json_formatter app/console my_command
monolog:
handlers:
console:
type: console
#...
formatter: '%log.formatter%'
The only point to modify command arguments for every registered command of your application is handling CommandEvents::COMMAND that is triggered before any command has been executed. So you can modify its arguments and read them as described here. Also, at this point you have your container compiled and it is not possible to modify service's definitions at this point. But you can get any service.
So i think you can end up with following handler:
class LogFormatterEventListener
{
private $container;
private $consoleHandler;
public function __construct(ContainerInterface $container, HandlerInterface $consoleHandler)
{
$this->container = $container;
$this->consoleHandler = $consoleHandler;
}
public function onConsoleCommand(ConsoleCommandEvent $event)
{
$inputDefinition = $event->getCommand()->getApplication()->getDefinition();
$inputDefinition->addOption(
new InputOption('logformat', null, InputOption::VALUE_OPTIONAL, 'Format of your logs', null)
);
// merge the application's input definition
$event->getCommand()->mergeApplicationDefinition();
$input = new ArgvInput();
// we use the input definition of the command
$input->bind($event->getCommand()->getDefinition());
$formatter = $input->getOption('logformat');
if ($formatter /** && $this->container->has($formatter) **/) {
$this->consoleHandler->setFormatter(
$this->container->get($formatter);
);
}
}
}
Here is alternative solution (compatible with common):
Configuration:
monolog:
handlers:
console:
type: console
channels: [!event, !doctrine]
formatter: "%log.formatter%"
services:
json_formatter:
class: Monolog\Formatter\JsonFormatter
Command execution:
# colored plain text
app/console my_command
# json
SYMFONY__LOG__FORMATTER=json_formatter app/console my_command
I have a service which takes a driver to do the actual work. The driver itself is within the context of Symfony 2 is just another service.
To illustrate a simplified version:
services:
# The driver services.
my_scope.mailer_driver_smtp:
class: \My\Scope\Service\Driver\SmtpDriver
my_scope.mailer_driver_mock:
class: \My\Scope\Service\Driver\MockDriver
# The actual service.
my_scope.mailer:
class: \My\Scope\Service\Mailer
calls:
- [setDriver, [#my_scope.mailer_driver_smtp]]
As the above illustrates, I can inject any of the two driver services into the Mailer service. The problem is of course that the driver service being injected is hard coded. So, I want to parameterize the #my_scope.mailer_driver_smtp.
I do this by adding an entry to my parameters.yml
my_scope_mailer_driver: my_scope.mailer_driver_smtp
I can then use this in my config.yml and assign the parameter to the semantic exposed configuration [1]:
my_scope:
mailer:
driver: %my_scope_mailer_driver%
In the end, in the Configuration class of my bundle I set a parameter onto the container:
$container->setParameter('my_scope.mailer.driver', $config['mailer']['driver'] );
The value for the container parameter my_scope.mailer.driver now equals the my_scope.mailer_driver_smtp that I set in the parameters.yml, which is, as my understanding of it is correct, just a string.
If I now use the parameter name from the container I get an error complaining that there is no such service. E.g:
services:
my_scope.mailer:
class: \My\Scope\Service\Mailer
calls:
- [setDriver, [#my_scope.mailer.driver]]
The above will result in an error:
[Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException]
The service "my_scope.mailer" has a dependency on a non-existent service "my_scope.mailer.driver"
The question now is, what is the correct syntax to inject this container parameter based service?
[1] http://symfony.com/doc/current/cookbook/bundles/extension.html
This question has a similar answer here
I think the best way to use this kind of definition is to use service aliasing.
This may look like this
Acme\FooBundle\DependencyInjection\AcmeFooExtension
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration;
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader(
$container,
new FileLocator(__DIR__.'/../Resources/config')
);
$loader->load('services.yml');
$alias = $config['mailer']['driver'];
$container->setAlias('my_scope.mailer_driver', $alias);
}
This will alias the service you've defined in my_scope.mailer.driver with my_scope.mailer_driver, which you can use as any other service
services.yml
services:
my_scope.mailer_driver:
alias: my_scope.mailer_driver_smtp # Fallback
my_scope.mailer_driver_smtp:
class: My\Scope\Driver\Smtp
my_scope.mailer_driver_mock:
class: My\Scope\Driver\Mock
my_scope.mailer:
class: My\Scope\Mailer
arguments:
- #my_scope.mailer_driver
With such a design, the service will change whenever you change the my_scope.mailer_driver parameter in your config.yml.
Note that the extension will throw an exception if the service doesn't exist.
With service container expression language you have access to the following two functions in config files:
service - returns a given service (see the example below);
parameter - returns a specific parameter value (syntax is just like service)
So to convert parameter name into a service reference you need something like this:
parameters:
my_scope_mailer_driver: my_scope.mailer_driver_smtp
services:
my_scope.mailer:
class: \My\Scope\Service\Mailer
calls:
- [setDriver, [#=service(parameter('my_scope_mailer_driver'))]]
At first I thought this was just a question of getting the # symbol passed in properly. But I tried assorted combinations and came to the conclusion that you can't pass an actual service as a parameter. Maybe someone else will chime in and show how to do this.
So then I figured is was just a question of using the service definition and passing it a reference. At first I tried this in the usual extension but the container does not yet contain all the service definitions.
So I used a compiler pass: http://symfony.com/doc/current/cookbook/service_container/compiler_passes.html
The Pass class looks like:
namespace Cerad\Bundle\AppCeradBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class Pass1 implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
// Set in the Extension: my_scope.mailer_driver_smtp
$mailerDriverId = $container->getParameter('my_scope.mailer.driver');
$def = $container->getDefinition('my_scope.mailer');
$def->addMethodCall('setDriver', array(new Reference($mailerDriverId)));
}
}
Take the calls section out of the service file and it should work. I suspect there is an easier way but maybe not.
#my_scope.mailer.driver needs to be a service but not defined as service. To retrieve string parameter named as my_scope.mailer.driver you need to wrap it with %: %my_scope.mailer.driver%.
So you need to pass #%my_scope.mailer.driver% as parameter to a service. Yml parser will replace %my_scope.mailer.driver% with the appropriate value of the parameter and only then it will be called as a service.
I was under the impression I could get the request object like in the code below. Something to do with Dependency Injection.
This below is activated as a service and everything seems to be setup correctly except for the first argument which gives this error:
ErrorException: Catchable Fatal Error: Argument 1 passed to....
namespace Acme\Bundle\BundleName\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class RequestListener
{
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
// etc....
I'm guessing the above is not how you do it?
If you want to declare an event listener on the kernel request, you should declare it that way (notice the tags parameters):
services:
acme.demobundle.listener.request:
class: Acme\Bundle\BundleName\EventListener\RequestListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Otherwise, if you want to create just a service, you should declare it that way
services:
acme.demobundle.demo.service:
class: Acme\Bundle\BundleName\Service\DemoService
arguments: [#service_container]
For a service or a listener, I'd advise injecting only the services that are needed.
It's good to know that a service will be initialized on the first call.
A service, a listener and a twig extension can be accessed through the container.
$this->container->get('your.listener.name')
$this->container->get('your.service.name')
$this->container->get('your.extension.name')