Symfony2 initialize service [duplicate] - symfony

This question already has an answer here:
Symfony instantiate a service one time and use it with several users
(1 answer)
Closed 7 years ago.
i use a bundle (wa72/jsonrpc-bundle) as a service. I need to initialize a value just after the service is constructed, for all the application. I don't think i need a listener as i just need to set it once, not before each request.
here's what i need to do :
$wa72_jsonrpc = $this->kernel->getContainer()->get('wa72_jsonrpc.jsonrpccontroller');
$wa72_jsonrpc->setSerializationContext(SerializationContext::create()->enableMaxDepthChecks());
how can i do this for all the application, just 1 time ?

If I'm not mistaking, a Kernel onRequest listener would do it if you want to expose the thing to every controller with no additional work.
Another way to do it, is to setup another Service that explicitly wraps your setup and exposes it to your app as a service
src/AppBundle/Services/jsonrpcService.php:
<?php
namespace AppBundle\Services;
class jsonrpcService
{
public $jsonrpc;
public function __construct($wa72_jsonrpc)
{
$wa72_jsonrpc->setSerializationContext(SerializationContext::create()->enableMaxDepthChecks());
$this->jsonrpc = $wa72_jsonrpc;
}
}
app/config/config.yml
services:
jsonrpc:
class: AppBundle\Services\jsonrpcService
arguments: [#wa72_jsonrpc.jsonrpccontroller]
in any controller just call:
$jsonrpc = $this->get('jsonrpc')->jsonrpc;

Is not so well documented but you should enable it in the bundle section in the config.yml as described here
If you use jms_serializer you can also configure exclusion strategies
(groups, version, or max depth checks) :
# app/config/config.yml
wa72_json_rpc:
functions:
myfunction1:
service: "mybundle.servicename"
method: "methodofservice"
jms_serialization_context:
group: "my_group"
version: "1"
max_depth_checks: true
Of course, seem you need to enable the jms serializer bundle also.
Hope this help

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 DI Component - How to make some services public and accessible by interface classname

Using DI as a standalone component in the small part of the codebase I want to make some services visible outside this part and accessible as by interface class name.
As I know, I should use a container for this.
So when old part of the code (that can't use DI) wants to use service that implementation is configured by this part of the code: it should call $container->get(MyInterface:class).
The problem is that I receive:
The "App\MyInterface" service or alias has been removed or inlined when the container was compiled. You should either make it public or stop using the container directly and use dependency injection instead.
I can't use DI in other parts of the code. So I want to make some of my services public. This is my code:
services.yaml:
# ...
# autowire & autoconfigure: true
# catalogs included
App\MyInterface: '#my.configured.implementation'
my.configured.implementation:
class: App\MyInterfaceImplementation
Builder:
$containerBuilder = new ContainerBuilder();
$loader = new YamlFileLoader($containerBuilder, new FileLocator(__DIR__));
$loader->load('services.yaml');
//Make `MyInterface::class` public so
//$container->get(MyInterface::class) should work.
$containerBuilder->getDefinition(MyInterface::class)->setPublic(true);
$containerBuilder->compile(true);
When it comes to getting a definition I receive:
Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: You have requested a non-existent service "App\MyInterface"
How can I make this works? Or maybe there is better way to use one service by these two modules?

Symfony service only initiated after method call

I noticed that a Symfony service is only initiated (the constructor is executed), when a method in that service is called. This can be important if your service only has a constructor, and no methods.
For example:
class MyService {
public function __construct($someOtherService) {
$someOtherService->setFoo("bar");
}
}
// And of course put this service in services.yml
app.my_service:
class: AppBundle\...\MyService
arguments: [ app.some_other_service ]
In this case, the constructor and thus setFoo("bar") is not called. Why is this? Is it possible to somehow force the service to initiate, without calling a (dummy) method on this service?
I also tried to add "lazy: false" for the app.my_service, but that makes no difference.
I'm using Symfony 2.8.
your services are never instantiated if they are never used. to enforce instantiating service you can hook into any kind of event listeners that suits your event, when you want get service (i.e kernel.request) and pass this service as a dependency to listener. this will trigger service constructor first time the event is triggered during the container lifespan.
but i'd rather suggest you to review the architecture. having service with the constructor only is nonsense
Moreover, you can instantiate services on instantiation of EvendDispatcher (just because it would depend on you service) without firing events
listener sample:
class ServiceInstantiatorListener
{
public function onRequest(KernelEvent $kernel)
{
return; //noop, just make sure it works
}
public function instantiate($service)
{
return $service; // noop again, just call to pass service container argument
}
}
yaml config:
services:
my_app.service_instantiator_listener:
class: My\App\ServiceInstantiatorListener
tags:
- { 'name': 'kernel_events', 'event': 'kernel.request', 'method':'onRequest' }
calls:
- [instantiate, ["#my_app.weird_service_one"]]
- [instantiate, ["#my_app.weird_service_two"]]
Going further you can mark your services with tag and configurate calls dynamically with MyAppBundleExtension compiler passes
http://symfony.com/doc/current/service_container/tags.html#create-a-compiler-pass
I hope there are better ways to force instantiating services (i.e some container internal events), but currently I haven't met the case I need that.
You're describing the behaviour of a lazy-loaded service. Check the config for lazy: true and remove/disable it.
Symfony docs, lazy loaded service:
The actual class will be instantiated as soon as you try to interact with the service (e.g. call one of its methods).
You don't really need to call a dummy method on the service to initiate it.
You can instantiate the service object by using the following statement:
$this->container()->get('app.my_service');

symfony2 configure service with data from another service

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.

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