symfony2 configure service with data from another service - symfony

Is it possible in Symfony2 to configure a service by injecting data from another service? For example, by calling a getter on another service?
In my specific case I am creating a (reusable) service that can handle translatable entity fields. For this I need a list of available locales in the application. I have looked at some other bundles that also work with locales, but they always use a static array from the configuration. For example:
a2lix_translation_form:
locales: [en, fr, nl]
This configuration usually ends up mapping to the service in the form of a constructor parameter or setter via the bundle configuration. For example:
class SomeService {
function __construct(array $locales) { ... }
// or
function setLocales(array $locales) { ... }
}
But in my case the list of available locales is not always static and often comes from the database. I have created a Locale service in my application with a method getLocales that returns an array. But how do I get that array into my service that needs it?
The service I am creating that needs a list of locales is split off into a separate reusable bundle. I don't want to inject the Locale service directly because that service is specific to the application, and not the bundle I am creating. I want users of my bundle to be able to provide a static list of locales, or point towards a service that has all the locales.

I would solve this problem using semantic configuration and config defintions. It works pretty similar to how FOSUserBundle asks for a driver and uses different settings depending on your choice (orm, mongodb, propel).
You could add something like this to your config.yml:
a2lix_locale:
provider: default # database
# ... additional settings which are optional,
# but required by provider, e.g. database settings
Your bundle's Configuration.php would verify that a valid provider was selected and that additional settings are set according to what each provider requires. Again, FOSUserBundle provides a great example for how to do this.
Additionally in your bundle's MyBundleExtension.php in /DependencyInjection you can access the service container and pass for instance the parameter locale to your default service in order for it to use the application's default locale provided in parameters.yml.

Related

Custom normalizer not passed name converter service

I'm working on creating a custom (de)normalizer to handle entities. I have created the normalizer and allowed the service container to autowire/autoconfig. The service is selected correctly during deserialization, but I'm having trouble with the name converter. I want to use the MetadataAwareNameConverter service since I'm using the #SerializedName annotation in my entity. No matter what I do, it is always null in the custom normalizer. I have tried a number of methods of getting the name converter service:
Setting it explicitly in my class constructor
Setting it in the service definition (effectively getting rid of autowire/autoconfig)
Setting MetadataAwareNameConverter as the default in framework.yaml (I discovered it is the default already).
Copied an existing normalizer into my src and renamed it to see if it got the correct name converter (it still didn't work)
Built in normalizers are getting a name converter without issue, it is just my custom normalizer that is having this issue.
Is there anything else I should try? Am I missing a step in setting up my service? Any direction is appreciated.
UPDATE - when I dump the service container, the name converter service is missing from the arguments list
---------------- ----------------------------------------------------------
Option Value
---------------- ----------------------------------------------------------
Service ID App\Normalizer\QNormalizer
Class App\Normalizer\QNormalizer
Tags serializer.normalizer
Public no
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired yes
Autoconfigured yes
Arguments Service(serializer.mapping.class_metadata_factory)
-----THIS IS WHERE THE NAME CONVERTER SHOULD BE----
Service(property_accessor)
Service(property_info)
Service(serializer.mapping.class_discriminator_resolver)
Manually injecting MetadataAwareNameConverter in services.yaml solved problem for me.
App\Serializer\CustomNormalizer:
arguments:
$nameConverter: '#serializer.name_converter.metadata_aware'
I faced same issue.
In my case it was a missconfiguration of services happened because of framework was configured to autoconfigure services (It's default framework configuration).
In result I had my custom normalizer duplicated in list of services.
First one is autoconfigured without priority
Second one is declared by me and having name converter injected:
Service Id
Priority
Class Name
App\Adapter\Symfony\Serializer\Normalizer\TranslationNormalizer
App\Adapter\ApiPlatform\Serializer\Normalizer\ItemNormalizer
api_platform.serializer.normalizer.item
-895
App\Adapter\ApiPlatform\Serializer\Normalizer\ItemNormalizer
Declaration:
api_platform.serializer.normalizer.item:
class: App\Adapter\ApiPlatform\Serializer\Normalizer\ItemNormalizer
arguments:
$nameConverter: '#serializer.name_converter.metadata_aware'
autoconfigure: false
tags:
- {name: serializer.normalizer, priority: -895}
Since autoconfigured normalizer have higher priority in list - it was picked by serializer so my SerializedName annotation wasn't working.
Solution is to disable autoconfiguration for first on service:
App\Adapter\ApiPlatform\Serializer\Normalizer\ItemNormalizer:
autoconfigure: false

Symfony 3.4: How to access bundle configuration in YAML

I'd like to ask if there is a way to access bundle configuration from YAML of that bundle.
Specifically, implementing Symfony\Component\Config\Definition\ConfigurationInterface I define that my bundle needs some configuration. User puts that configuration in their app/config/bundles/my_bundle.yml with all the keys I require for my bundle.
my_bundle:
magic_key: '42'
Now in my bundle I have Resources/config/services.yml in which I configure some services and I need magic_key for one of them.
Since I know magic_key is set (because of ConfigurationInterface) I now am able access that key in a class extending Symfony\Component\HttpKernel\DependencyInjection\Extension, get definition of particular service and set argument for that.
However I'd like to do this in Resources/config/services.yml located in my bundle instead of using and Extension class.
I've read at https://symfony.com/doc/3.4/service_container/expression_language.html that it should be possible using parameter or container functions, but I'm not able to do that.
The reasoning behind that is that I want to have configure my bundle services at single location - the YAML file - as opposed to current situation where it is split between YAML and Extension.php.
Is it indeed possible? What is the right syntax?
you need to add your configuration in parameters in your MyBundleExtension class like this:
public function load(array $configs, ContainerBuilder $container)
{
$container->setParameter('my_bundle', $config);
}
Then you can add "%my_bundle%" in service arguments.

Twig is_granted fails in Behat scenario

I have this Behat setup:
default:
extensions:
Behat\Symfony2Extension: ~
Behat\MinkExtension:
sessions:
default:
symfony2: ~
And this scenarion:
Scenario: Event list for authenticated user
Given I am authenticated
Then I should see pagination control
And I should be able to change list page
I check if the user is authenticated and if so show him pagination control in Twig:
{% if is_granted('IS_AUTHENTICATED_FULLY') %}
...
Related Behat context:
/**
* #Given I am authenticated
*/
public function iAmAuthenticated()
{
$user = new User('test', null, ['ROLE_USER']);
$token = new UsernamePasswordToken($user, null, 'test', $user->getRoles());
$this->getTokenStorage()->setToken($token);
}
/**
* #Then I should see pagination control
*/
public function iShouldSeePaginationControl()
{
$this->assertSession()->elementExists('css', 'ul.pagination');
}
I get true for
$this->kernel
->geContainer()
->get('security.authorization_checker')
->isGranted('IS_AUTHENTICATED_FULLY')
in my iShouldSeePaginationControl() but it is false in rendered content.
What am I missing?
My guess is that you're using a different instance of the container in your behat step and in your template.
AFAIR, the symfony2 driver uses BrowserKit under the hood to navigate through your website. The container which will be used in your web page will then be instanciated by the PHP Engine of your Web server (and not by Behat). If so, it is absolutely impossible to operate modifications in the container at runtime in a step and expect that the web server will be aware of them.
Easy solution would be to actually log in in the behat step (through the web interface) instead of setting the token manually.
Another harder way, if you absolutely want to login programatically, would be to serialize the created token on HDD and register some kind of logic (a kernel.request listener for example) that will check if this file is available and inject the unserialized token in the security context. If you do so, MAKE SURE that you enable this logic in TEST environment only, as it potentially is a security breach.
The problem is you have running 2 instances of Symfony:
One core for Behat, that was initialized.
Second, initialized by apache/nginx that was triggered by Mink connection to the server.
Solution
For that, we had a solution in another project (with Zend).
We created service, that created an additional configuration to authorization:
if a file exists and the project was in DEV mode, then it was loaded in the initialization step.
Then in hook/step we could call service that generates a file like that and after scenario, delete it. This way, you could have any logged user in your project.
Another way is to call steps that will log you into your project via a standard form.

Symfony2: How to get Authentication Listener config values into another service?

I'm working on creating an authentication provider for Symfony 2 that allows users to authenticate with the single sign on protocol called CAS.
My Authentication Listener extends from AbstractAuthenticationListener. One of the config params is check_path, which is the path/route that triggers the authentication listener to authenticate the request.
I need check_path when I construct the URL to the CAS server (so CAS server knows where to return the user to), which is easy, since my custom Entry Point class is passed the configuration array when it's constructed in my security Factory.
The hard part is that I also need check_path outside of the listener, like during authentication inside my Authentication Provider class. I need it because when CAS server sends the user back to the app, it passes a "ticket" parameter that must be validated. To validate it, I send a curl request to CAS server that must contain the ticket as well as the original check_path that was used.
As a wrote this, I realized that I could get the current URL of the page request when I'm inside the Authentication Provider (since it's check_path that triggers it anyway), but that seems off, and I'd rather get the config value directly to re-construct the service URL. It also doesn't help me when I want to use check_path elsewhere, like when constructing a logout URL to the CAS server which also required the check_path.
EDIT: The createAuthProvider method of AbstractFactory is passed both the config and the container, but I cannot modify any of my services in here because they are not yet part of the container. Perhaps if I had a way to add a compiler pass after my services are loaded and somehow having access to the listener config?
Can you pass check_path as parameter to your listener?
If it defined in your config or parameters file you can pass it to your listener like this:
your_authentication_listener:
class: YourBundle\Listener\AuthenticationListener
arguments: ['%check_path%']
tags:
...
(If I understood you correct.)
You can make %check_path%(or a namespaced version of it) a 'normal' parameter:
Inside of DependencyInjection, there are (by default) two classes responsible for defining and loading your bundle's configuration. In there you can also inject your configuration into your service container.
DependencyInjection\Configuration is where you define which configurations are available in your bundle, what type they should be etc.
DependencyInjection\YourBundleNameExtension is where you can load your configuration and also add them to the service container.
If you have not done anything in there yet, your Extension's load()-method should look something like this:
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');
}
$config holds your bundle's configuration in the form of an array, so if we imagine your YAML config file looks like this:
your_bundle_name:
check_path: foo
Your $config will look like that:
array( 'check_path' => 'foo' )
So now, all you have to do is add this configuration to the container. Inside your load()-method simply add something like:
$container->setParameter(
'my_bundle_name.check_path',
$config['check_path']
);
Inside your services.yml you can now use %my_bundle_name.check_path% like every other parameter:
my_bundle_name.security.authentication.provider:
class: MyBundleName\Security\Core\Authentication\Provider\MyAuthenticationProvider
arguments: ['%my_bundle_name.check_path%']
For more details, have a look at Symfony's documentation [1,
2]

Access to Doctrine during Bundle Initialization

I have a Symfony2 bundle which I want to use database table which stores key value configuration parameters. I want to be able to load a query and cache it for a long time and be able to inject the configuration parameters into symfony2 service container.
Right now I am injecting a service which loads the configuration from doctrine, and calling a get($key) method to retrieve the value for the key I want.
I basically want these configuration options to be available from the symfony2 service container parameter bag.
Is there maybe an event I could tie into or some sort of compiler pass I can use with my bundle to achieve this?
I'll do something like that in your service listener
public function onLateKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$mydata= $this->manager->getRepository('YourBundle:YourTable')->getAll();
$parameters['mydata'] = $mydata;
$request->attributes->add($parameters);
}
In your Controller, you can get your parameters :
$this->container->get('request')->attributes->get('mydata');

Resources