Why is accesing services by classname good? - symfony

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.

Related

Change in behaviour for IActionInvokerProviders when using UseStatusCodePagesWithReExecute between core 3.1 and dotnet 6

Context:
Upgrading an existing aspnet application from core 3.1 to dotnet 6.0.
Issue:
We have registered a IActionInvokerProvider in our web app. This simply adds some information to the context route data.
We also use UseStatusCodePagesWithReExecute
app.UseStatusCodePagesWithReExecute("/somecontroller", "?statusCode={0}");
According to the documentation https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.abstractions.iactioninvoker?view=aspnetcore-3.1
An IActionInvoker is created for each request the MVC handles by querying the set of IActionInvokerProvider instances. See IActionInvokerProvider for more information.
When running this in netcoreapp3.1 when we return a NotFound() I can observe that 2 calls are made to our action provider OnProvidersExecuting. One for the request to the resource and one for a call expected UseStatusCodePagesWithReExecute to /somecontroller.
When targeting net6.0 and changing no other code this second call to /somecontroller does not get called only the first . If I call the endpoint /somecontroller?statusCode=404 I it does trigger the invoker. I cannot find a reference to a breaking change anywhere. perhaps I missed it.
Does anyone know what the casue might be?
I have tried altering the ordering of the pipeline.
Tried to repro it in https://github.com/csi-lund/core31tonet6issue
In the version the Action provider never gets called at all
The answer was a missed breaking change and documentation.
https://github.com/dotnet/aspnetcore/issues/45589
We skip the IActionInvoker by default as an optimization for
controllers and pages. This is a really heavyweight way to add route
data to the pipeline. You can set EnableActionInvokers to true to
enable this behavior.
builder.Services.AddControllers(o => {
o.EnableActionInvokers = true; }); In your sample it would be AddMvc (since you're using that).
No change in behaviour without documentation to indicate. (There might
be might have missed it)
Yes, it seems like we missed this one. I'll make sure it gets
documented.
PR: #27773 https://github.com/dotnet/aspnetcore/pull/27773

Change Pimple with Symfony dependency injection

Is there a way to swap Pimple with Symfony dependency injection?
I can use Symfony dependency injection as a standalone package with Silex but i need to swap Pimple with it because i can't access controllers registered with Symfony dependency injection from Silex like:
$app->get('/route', 'testController:indexAction');
No you can't. Silex does not allow to change the container as the Silex main class extends from Pimple itself. If you need the Symfony container you should go with Symfony and not with Silex.
You could hack up a solution in which every call to Pimple is forwarded to DIC (Pimple would be a kind of proxy service only), but I wouldn't go that route.
Keep in mind that with the upcoming Symfony 4 (due on November, but there is a preview already aviable) the framework is going to be more like Silex: instead of having it all and remove the parts you don't use, you'll start small and add components/bundles/libraries into your project (check out Symfony flex).
Finally, as a side note/fun fact, there was a project from igorw (one of the coauthors of Silex) which replaced Pimple with Symfony DIC component, but it was more an academic exercise rather than a ready to use framework (it did work)

Route::controller() in Laravel 5.3

We have completed an end-to-end E-Commerce platform using laravel 5.2.x.
The project was started in January 2016 and we are in the final stages.
Have used Route::controller() method on almost 100+ routes. Changing all these into explicit rules at this stage is really painful task. At the same time we would like to upgrade laravel to 5.3 and use its benefits such as broadcasting.
I just want the controller method back. Is it something that we can extend the router class to 5.2.x's controller method?
Yes, removal of Route::controller in Laravel 5.3 was a big step back, and was a result of misunderstanding of this advanced feature.
To fix this shortcoming, and without increasing needlessly complexity, I created a class called AdvancedRoute, which registers the controller routes. It can be used by simply replacing Route::controller with AdvancedRoute::controller
Full information how to install and use find at the GitHub repo at:
https://github.com/lesichkovm/laravel-advanced-route

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.

How should Symfony services be wrapped?

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.

Resources