Dependency Injenction in config.yml - symfony

I have two services and I want pass my parameter from config.yml
my config.yml
parameters:
MyService.class: Acme\UserBundle\Services\sendEmail
MyService.arguments: #mailer
NewUserListener.class: Acme\UserBundle\Event\NewUserListener
NewUserListener.arguments: #MyService
my service.yml inside bundle
services:
MyService:
class: %MyService.class%
arguments: [%MyService.arguments%]
NewUserListener:
class: %NewUserListener.class%
arguments: [%NewUserListener.arguments%]
tags:
- { name: kernel.event_listener, event: new.user, method: sendEmailToUsers }
I got an error
You cannot dump a container with parameters that contain references to
other services
My Questions are:
How can I inject my arguments from config.yml?
Where can i Find the list of "global service" like #mailer ? i don't find in doc

You can't reference a service in a parameter. You should replace %MyService.arguments% with #mailer.
To find all available services, run php app/console container:debug

This a bit more complicated!
First, you have to declare your default services like that (I changed all the names in order to be compliant with the Symfony2's conventions):
# resources/config/services.yml
services:
my_own.service.default.class: Acme\UserBundle\Services\sendEmail
my_own.user_listener.default.class: Acme\UserBundle\Event\NewUserListener
services:
my_own.service.default:
class: %my_own.service.default.class%
arguments: [#mailer]
my_own.user_listener:
class: %my_own.user_listener.class%
arguments: [#my_own.service]
tags:
- { name: kernel.event_listener, event: new.user, method: sendEmailToUsers }
We will define some configuration for your bundle in order to allow to change the used services:
namespace My\OwnBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* This is the class that validates and merges configuration from your app/config files
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
*/
class Configuration implements ConfigurationInterface
{
/**
* {#inheritDoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('my_own');
// Here you should define the parameters that are allowed to
// configure your bundle. See the documentation linked above for
// more information on that topic.
$rootNode
->children()
->scalarNode('service')->defaultValue('my_own.service.default')->end()
->scalarNode('user_listener')->defaultValue('my_own.user_listener.default')->end()
->end();
return $treeBuilder;
}
}
Note that, by default, we use our default services defined above in our bundle.
You now can use the following to change your services (in your app/config.yml) for instance:
# app/config.yml
my_own:
service: my_other.service
user_listener: my_other.user_listener
Of course, you can define the services my_other.service and my_other.user_listener as you want in your bundle or in another bundle.
Now we have to tell how to use this configuration to take the wanted services:
namespace My\OwnBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
/**
* This is the class that loads and manages your bundle configuration
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class MyOwnExtension extends Extension
{
/**
* {#inheritDoc}
*/
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');
$container->setAlias('my_own.service', $config['service']);
$container->setAlias('my_own.user_listener', $config['user_listener']);
}
}
Finally, in the rest of your code you have to use the aliased services my_own.service and my_own.user_listener in your code:
// In one of your controller:
$this->container->get('my_own.service');
/* or directly */ $this->get('my_own.service'); // if your controller is a child of the framework bundle class `Controller`.

Related

Symfony 4 - services from custom bundle not working

I migrate symfony 3 to symfony 4.3 and my custom bundle moved to: src/Bundles/FooBundle.
Strucutre FooBundle:
- DependencyInjection
- Configuration.php
- FooExtension.php
- Event
- Model
- Resources
- config
- services.yml
- Service
- Twig
- Exception
- FooBundle.php
And files
Bundles/FooBundle/Resources/config/servies.yaml
services:
foo:
class: App\Bundles\FooBundle\Service\Foo
arguments:
- '%foo.argument1%'
- '%foo.argument2%'
- '%foo.argument3%'
foo.listener.example:
class: App\Bundles\FooBundle\Event\Listener\ExampleListener
arguments: ['#annotations.reader']
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
foo.filter:
class: App\Bundles\FooBundle\Filter\FilterConverter
tags:
- { name: request.param_converter }
Bundles/FooBundle/DependencyInjection/Configuration.php
<?php
namespace App\Bundles\FooBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
/**
* {#inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('foo');
$treeBuilder->getRootNode()
->children()
->scalarNode('argument1')->isRequired()->end()
->scalarNode('argument2')->isRequired()->end()
->scalarNode('argument3')->isRequired()->end()
->end();
return $treeBuilder;
}
}
Bundles/FooBundle/DependencyInjection/FooExtension.php
<?php
namespace App\Bundles\FooBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
/**
* This is the class that loads and manages your bundle configuration.
*
* #link http://symfony.com/doc/current/cookbook/bundles/extension.html
*/
class FooExtension extends Extension
{
/**
* {#inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('foo.argument1', $config['page_field_name']);
$container->setParameter('foo.argument2', $config['per_page_field_name']);
$container->setParameter('foo.argument3', $config['sort_field_name']);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}
And bundle registration: config/bundles.php
App\Bundles\FooBundle\FooBundle::class => ['all' => true],
configuration package:
config/packages/foo.yam
foo:
argument1: test1
argument2: test2
argument3: test3
Done... Run app and error:
Cannot autowire service "App\Bundles\FooBundle\Service\Foo": argument "$argument1" of method "__construct()" has no type-hint, you should configure its value explicitly.
But, when I add conf in config/services.yaml:
App\Bundles\FooBundle\Service\Foo:
arguments:
- '%foo.argument1%'
- '%foo.argument2%'
- '%foo.argument3%'
That working...
Question: why is the bundle service not working?
I guess the problem is that the default configuration in symfony add autowireing to all classes under the App namespace.
Check the configuration under config/services.yml:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
You can remove this lines:
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
And the problem should disappear.

Symfony 3 dependency injection into behat testing class - Type error: Argument 1 passed to __construct() must be an instance

I have such configuration file:
src/AppBundle/services.yml
#imports:
# - { resource: '../../app/config/config.yml' }
parameters:
#laikinas, tikras yra config.yml
app_url: http://app.guru
services:
UserManagement:
class: Tests\AppBundle\SharedCode\UserManagement\UserManagement
arguments: [%app_url%]
UserRegistrationContext:
class: Tests\AppBundle\features\user_registration\bootstrap\UserRegistrationContext
arguments: ['#UserManagement']
tests/AppBundle/features/user_registration/bootstrap/UserRegistrationContext.php
<?php
namespace Tests\AppBundle\features\user_registration\bootstrap;
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use Tests\AppBundle\SharedCode\UserManagement\UserManagement;
use AppBundle\Controller\UserController;
use Tests\AppBundle\features\BaseContext;
/**
* Defines application features from the specific context.
*
* To run:
* sudo vendor/behat/behat/bin/behat
* tests/AppBundle/features/user_registration/user_registration.feature
* --stop-on-failure
*/
class UserRegistrationContext extends BaseContext implements Context, SnippetAcceptingContext
{
private $userManagement;
/**
* UserRegistrationContext constructor.
*/
public function __construct(UserManagement $userManagement)
{
//$this->userManagement = new UserManagement();
$this->userManagement = $userManagement;
parent::__construct();
}
}
I run behat tests and get an error:
vagrant#php7dev:/shared$ sudo vendor/behat/behat/bin/behat tests/AppBundle/features/user_registration/user_registration.feature
Fatal error: Uncaught Symfony\Component\Debug\Exception\FatalThrowableError: Type error: Argument 1 passed to Tests\AppBundle\features\user_registration\bootstrap\UserRegistrationContext::__construct() must be an instance of Tests\AppBundle\SharedCode\UserManagement\UserManagement, none given in /shared/tests/AppBundle/features/user_registration/bootstrap/UserRegistrationContext.php:30
Stack trace:
#0 [internal function]: Tests\AppBundle\features\user_registration\bootstrap\UserRegistrationContext->__construct()
#1 /shared/vendor/behat/behat/src/Behat/Behat/Context/ContextFactory.php(123): ReflectionClass->newInstance()
#2 /shared/vendor/behat/behat/src/Behat/Behat/Context/ContextFactory.php(80): Behat\Behat\Context\ContextFactory->createInstance(Object(ReflectionClass), Array)
#3 /shared/vendor/behat/behat/src/Behat/Behat/Context/Environment/Handler/ContextEnvironmentHandler.php(104): Behat\Behat\Context\ContextFactory->createContext('Tests\\AppBundle...', Array)
#4 /shared/vendor/behat/behat/src/Behat/Testwork/Environme in /shared/tests/AppBundle/features/user_registration/bootstrap/UserRegistrationContext.php on line 30
We can see that in services.yml I have given the parameter. What is wrong?
For services.yml to be read, as I understand I need exctension class, here it is:
src/AppBundle/DependencyInjection/AppExtension.php
<?php
namespace AppBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
class AppExtension extends Extension
{
/**
* #param array $configs configs
* #param ContainerBuilder $container container
* #return null
*/
public function load(array $configs, ContainerBuilder $container)
{
// ... you'll load the files here later
$loader = new YamlFileLoader(
$container,
new FileLocator(__DIR__ . '/../')
);
$loader->load('services.yml');
}
}
Thank you so much Matteo, so awesome, from his comment alone was able to fix the problem.
It turns out that I had to use Symfony2 Extension and configure dependencies in behat.yml instead of my config. Here is how behat.yml looks now:
default:
autoload:
'': %paths.base%/tests/AppBundle/features/user_registration/bootstrap
formatters:
progress: ~
suites:
app_features:
paths: [ %paths.base%//tests/AppBundle/features ]
contexts:
- Tests\AppBundle\features\user_registration\bootstrap\UserRegistrationContext:
userManagement: '#UserManagement'
- Tests\AppBundle\features\user_login\bootstrap\UserLoginContext:
userManagement: '#UserManagement'
- Tests\AppBundle\features\password_reset\bootstrap\PasswordResetContext:
userManagement: '#UserManagement'
extensions:
Behat\Symfony2Extension: ~
And I even commented out UserManagement from src/AppBundle/services.yml and it finds it somehow, I do not understand how actually.
And here is something written about this, I googled again for symfony3 behat dependency injection after I solved the problem:
http://docs.behat.org/en/v3.0/cookbooks/1.symfony2_integration.html
I remember I saw this page before, but this was not in my head when solving this problem. Maybe because in the example there was Session being injected which is symfony component, while UserManagement class was my created component.
Update:
Done from scratch and will give minimal versions of files how they look:
behat.yml has to be in the root of the project. http://docs.behat.org/en/v3.0/cookbooks/1.symfony2_integration.html
default:
suites:
default:
contexts:
- FeatureContext:
userRepository: "#user_repository"
extensions:
Behat\Symfony2Extension: ~
features/bootstrap/FeatureContext.php
use AppBundle\Repository\UserRepository;
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
/**
* Defines application features from the specific context.
*/
class FeatureContext implements Context, SnippetAcceptingContext
{
private $userRepository;
/**
* Initializes context.
*
* Every scenario gets its own context instance.
* You can also pass arbitrary arguments to the
* context constructor through behat.yml.
*/
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
//other methods
}
Pay attention that there has to be matches of keys - if there is such key:
userRepository: "#user_repository"
then in constructor the variable has to be named
$userRepository

How to debug ParameterNotFoundException in Symfony2

I'm getting the following error after following and adapting this
ParameterNotFoundException: You have requested a non-existent parameter "mynamespace_admin.amazon_s3.aws_key". Did you mean this: "mynamespace_admin.amazon_s3.class"?
In config.yml I have:
mynamespace_admin:
amazon_s3:
aws_key: %amazon_aws_key%
aws_secret_key: %amazon_aws_secret_key%
base_url: %amazon_s3_base_url%
And in my parameters.yml I have:
amazon_aws_key: ###
amazon_aws_secret_key: ###
amazon_s3_base_url: ###
amazon_s3_bucket_name: ###
And in services.yml:
parameters:
mynamespace_admin.amazon_s3.class: AmazonS3
mynamespace_admin.image_uploader.class: mynamespace\Bundle\AdminBundle\Uploader\ImageUploader
mynamespace_admin:
amazon_s3:
class: %mynamespace_admin.amazon_s3.class%
arguments:
- "%mynamespace_admin.amazon_s3.aws_key%"
- "%mynamespace_admin.amazon_s3.aws_secret_key%"
image_uploader:
class: mynamespace_admin.image_uploader.class%
arguments: [image_storage_filesystem]
Can anyone see what I have configured incorrectly or advise on how to debug this? Why can't mynamespace_admin.amazon_s3.aws_key be read from config.yml?
If things are not listed under the parameters key in the service configuration, it is not assumed it is a parameter.
In all other cases (except from the services key), it is assumed that it is the configuration for an extension. In this case, the extension called mynamespace_admin. That extension should parse the settings and maybe put them in the container as parameters, or use them to decide which files to include, etc.
Assume you have a correct Configuration class for the config you have given, your extension will look like this if you want to use the config as parameters:
// ...
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
class MynamespaceAdminExtension extends Extension
{
/**
* {#inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
// ... do some other stuff, like loading service definition files
// loop through the processed config and save them as parameters
foreach ($config['amazon_s3'] as $name => $value) {
$container->setParameter('mynamespace_admin.amazon_s3.'.$name, $value);
}
}
}

Service like for one bundle in symfony 2

I'm trying to create a model class (which will use DBAL), and i'd like to use it like a service in my bundle.
I've tried to create a service with this configuration in my bundle :
services:
X:
class: X
arguments: [#database_connection]
But the fact is i don't want to configure this service in app/config/config.yml because it will only be used in one bundle.
Is there any way to create a specific bundle service, and giving #database_connection parameter to the class ? Or am i forced to configure it for all my app ?
My goal here is only to have distinct class for my controller and my model, without using the Doctrine ORM/Entity, just the DBAL.
Yes, every bundle has his own config files.
# src/Acme/YourBundle/Resources/config/services.yml
services:
X:
class: X
arguments: [#database_connection]
The bundle configuration is loaded trough the DIC. So this file in your bundle is important
// src/Acme/YourBundle/DependencyInjection/AcmeYourBundleExtension.php
namespace Acme\YourBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
/**
* This is the class that loads and manages your bundle configuration
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class AcmeYourExtension extends Extension
{
/**
* {#inheritDoc}
*/
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');
}
}
Generally, you should configure all services in the bundle specific services.yml and not in config.yml. So you can reuse them. But the service is visible for the complete application not only for the bundle. But this should be no problem.

symfony2 DI bundle service unavailable

I've defined a service in the Resources/config/services.yml:
services:
gSm.gate.terminal:
class: Stream\TerminalBundle\StreamTerminal
arguments: [ [], [%terminal_login%, %terminal_password%] ]
And I try to access in inside my controller action:
public function displayAction() {
$terminal = $this->get('gSm.gate.terminal');
return $this->render('StreamTerminalBundle::display.html.twig');
}
Server returns following exception: 500 You have requested a non-existent service "gsm.gate.terminal". The bundle is registered in the appKernel, the bundle uses .yml config files... and I don't know what else can I look at to make this service available... thanks for any help.
EDIT:
My extension class code is:
namespace Stream\TerminalBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
/**
* This is the class that loads and manages your bundle configuration
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class StreamTerminalExtension extends Extension
{
/**
* {#inheritDoc}
*/
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');
}
}
Make sure you're importing the bundle's services.yml. A simplest way is to import it from config.yml. A better and more advanced solution is to write an extension.
To see an example of an extension class, see my bundle's one. If you want to use YAML, just change services.xml to services.yml and XmlFileLoader to YamlFileLoader.
Is your services.yml file being parsed by the Extension class?
By default in a new bundle it's setup to load the xml file.
Can you paste your
Acme\Bundle\YourBundle\DependencyInjection\AcmeYourExtension
class?
I'm not sure you can use uppercase letters in you service names. The exception gives such a hint "gsm.gate.terminal" while your service is "gSm.gate.terminal".

Resources