How should Symfony services be wrapped? - symfony

My application requires a cache warmer, but to make things interesting, this cache warmer is dependent upon the Symfony route cache warmer.
Before routes can be loaded, my own code needs to be run, then after loading, I need to perform more actions on the generated route names. For this to work, I need to wrap the built in route warmer with my own implementation.
Replacing the original service is simple, but how do I inject that service into my replacement?
The built in class has the router service injected into the constructor. If I replicated this (call new RouteCacheWarmer) in my own code, it may break 3rd party bundles which have also replaced the service. Aliasing the service to my own, may also break if another bundle does the same.
My aim is to run my own cache warmer, in place of the route cache warmer, but run the existing warmer from within my own. While at the same time playing nice with other bundles that may have modified the built in service.

It turns out that Symfony added this functionality in version 2.5. It is known as service decoration.
bar:
public: false
class: stdClass
decorates: foo
arguments: ["#bar.inner"]
This sets the bar service as an alias to foo while at the same time renames the foo service to bar.inner making it available for injection. When 3rd party bundles replace the foo service, the changes it made should not effect the bar service.

You can use a chained router, so that you leave the Symfony router doing its own thing, and then run your router entirely separately. The Symfony CMF offers such functionality in the RoutingBundle component.

Related

Why is accesing services by classname good?

I've always worked in Symfony 2.2 and 2.8
Now glancing at the blog of the version 3.3 and I can't overcome the new feature of getting services by (fully qualified) class name:
// before Symfony 3.3
$this->get('app.manager.user')->save($user);
// Symfony 3.3
$this->get(UserManager::class)->save($user);
Before Symfony 3.3 the service container was a factory with the knowledge of how to instantiate the service class, with the great benefit of a factory: You can swith the old class to any other class, and as long as they both let's say implement the same interface, maybe you don't even have to touch anything else in your code. But if you ask for a service by class name, you have to refactor your whole code, and change the class name in every occurrence of the service. Either when directly accessing by $container->get() or by using typehint and autowire.
Considering these the old way service aliasing seem much more practical or am I just missing something? I know you still can use it the old way, I'm just wondering in what cases could one benefit from preferring this new method instead the classic one.
One of the main points about the new style of service naming it to avoid asking for services by name directly from the container. By typehinting them all (and having them instead created by the framework), then any service that is not being used at all (and is private, so not get-able), can be removed. Anything that is hinted via the container and does not exist will immediately fail on the container building step as well - it would not be possible to rename a service and forget to also change all the other uses of it.
With so much being injected into the controllers (or a controller action) as well, unit testing the services, and even controllers is also more controllable - there wouldn't be any other things that are being pulled from the container within a test.
As for the transition, because of the container compilation step, Symfony is very good about being able to say if there is anything wrong, or at least deprecated. It's not hard to mark all the current services as public with just a small Yaml snippet at the top of each services.yml file (or anywhere else they are defined).
It will be a while until most of the 3rd party bundles and other supporting libraries will be fully 4.0 compatible, but it was also the case for the breaking changes between 2.8 & 3.0. This is a large part of the reason why 2.8 & now 3.4 are long-term-supported versions - with 3.4 supported to November 2021, giving plenty of time to upgrade away from the deprecations and update the 3rd party bundles.

How can I get the Doctrine entity metadata before compiling Symfony container?

In one Symfony bundle I define a compiler pass to preprocess some configuration. Part of that config is based on Doctrine entities, so I need to get the full metadata information for all application entities.
The compiler pass is executed very late (PassConfig::TYPE_BEFORE_REMOVING). I'm using $container->get('doctrine') like this to get the entity metadata:
$em = $container->get('doctrine')->getManagerForClass($entityClass);
$entityMetadata = $em->getMetadataFactory()->getMetadataFor($entityClass);
However, this is causing random failures for some users because of the use of the doctrine service during the Symfony container compilation.
I'd recommend to change your entities addressing. Mainly - create your models with interfaces and make entities implementing them.
Using resolve_target_entities Doctrine will "convert" them to the particular classes.
An example code is here: https://github.com/Sylius/SyliusResourceBundle/blob/master/DependencyInjection/Compiler/DoctrineTargetEntitiesResolverPass.php
Just make sure your bundle is registered before DoctrineBundle is registered.
Then - in your whole app - instead of AppBundle::Entity addressing, use FQDN of interface bound to an entity earlier.
I've experimented a bit with compilers and services and it's a very bad idea to base on cross-bundle services under compiling container process... Why? It's not reliable - sometimes it will work as you want, sometimes it will fail as you described.
Thank you all for your comments and ideas. I post an answer to explain how I solved this problem.
Trying to use the Doctrine service in a compiler pass was creating more and more problems for our users (and it was creating other minor issues with other services such as Twig). So this was definitely a bad solution for our needs.
So at the end I decided to change everything. We no longer use a compiler pass to process the configuration and instead we use a regular PHP class called during runtime.
In the dev environment, the configuration is processed for each request. It's a bit slower than before, but we prevent any caching issue. In the prod environment we use Doctrine Cache to process the configuration once. Besides, we've create a cache warmer to create the cached configuration before the first request hits the application.

When use Extension class, when use Compiler Passes in Symfony?

What is the point of using compiler passes in Symfony?
When we should use Extension class and when Compiler Passes in Symfony?
They come with services definition.
By creating a compiler pass, you are able to update the arguments passed to services.
It's most often done with tagged services.
Also, It can be used for :
Creating new services that require information about other defined services before being defined.
Swapping or adding arguments to a service that you did not write.
Creating and modifying parameters in the container.
I used a compiler pass to register a Factory that make me able to override the doctrine Repository.
You can see the code for more comprehension of how it works:
https://gist.github.com/chalasr/77be8eee5e3ecd3c06ec
Update
Thank's to #Sruj, I added the part I've forgotten about the Extension
Extension are part of dependency injection too, especially of the configuration.
Its primary role is to load the configuration of services across the bundles of your application.
Instead of load your configuration manually by using imports, you can create an extension that does it for you. All your services configuration are registered from your bundle and shared in your whole application.
When you register a vendor in your app configuration, the service container extension of the vendor is invoked.
See "Importing configuration via container extensions" part of the documentation

session shows null when it uses SlingRepository service, why?

I made one bundle, that deploys in OSGI container and now in active state, and provides some services.
And I have another bundle that uses these services and one more service i.e., SlingRepository, to get admin prevelage to make nodes in Apache Jachrabbit.
Second bundle is just to use the implementation that is written in first bundle.
For that, I code to run the first bundle in Activate annotation of Second bundle.
But when I deploy second bundle , it shows Null for session.
#Reference
session = repository.loginadministrataive(null);
Then , I try another method , in which I make one class in first bundle instead to make another for that, i.e., Activator class that implements BundleActivator interface. But that shows same problem.
Can you tell me how can I solve this problem ?
Can you tell me how can I run first bundle using second bundle ?
Thanks in advance. Lookig for your kind reply.
#Reference on such a line of code is wrong, have a look at Sling examples for more info.
The Slingbucks sample, for example uses #Reference to get a SlingRepository, you could use that as a starting point.
Also, you probably don't need an Activator for that, declarative services annotations should be enough, you'll see that in the sample.

Lazy loading dependencies with symfony DI

Currently I've got a Symfony2 DI container instance ready with a service and all it's dependencies. Lets say for example I have a Car class and it has Engine and Lights as dependencies.
In my current setup both these dependencies are automatically created through setter injection when the Car object is created, but it might very well be that my Car object won't need it's lights this time around thus it doesn't explicitly need to create an instance of this dependency.
Is there a way to achieve this in Symfony DI? Thus only creating an instance of the Lights object when needed? My guess is it'll be some sort of Proxy implementation like Doctrine has but as far as i've seen it doesn't exist in Symfony DI.
Inject the dedendencies that are mandatory through the Constructor via your services.yml, automatically.
If you have optional dependencies inject them through a setter in your Controller when you need them.
$this->container->get('cars')->setLights(new \Namespace\Lights());
Of course your Cars class must be designed like so and you have to direct the injections yourself in your controller, or whereever needed, code.
Question is already answered, but for who needs this functionality, lazy services are implemented in Symfony 2.3.
You need to install the ProxyManager bridge.
You can find official documentation here.
A very interesting question, but I don't think it's possible within Symfony2's Dependency Injection Container. The container is only aware of what you tell it - in this case, you have a dependency that's conditional on a specific use-case. Plus, the registration of services happens early on in the app's life, so I don't see how you could get this to work.
Maybe you should use the Factory pattern. Register a CarFactory as a service, and then when fetching a Car instance, you can specify that it should include a Light dependency.
Can I ask why you want to achieve this? There may be a simpler solution.
It's not a pretty workaround, but you can try injecting the whole DIC, then getting the Light and Engine services when neccessary.
I was thinking about something like this method in the Car class:
protected function getLightService()
{
if (!$this->light) { //so we reuse the first instance
$this->light = $this->dic->get("car.light");
}
return $this->light;
}

Resources