Many of these resources include features that are nice to have for convenience sake, some of them are BC breaks that you have to fix, and some of them are must-have features which can rapidly decrease your technical debt.
Today we will demonstrate some real-life applications of these resources and how to migrate a PHP e-commerce platform from Symfony 2.8 to 3.4.
If there’s one single feature that makes all the hassle of upgrading worth it, it’s this one. Since you probably already use composer, it’s likely that you have PSR-4 autoload in the composer.json
It tells you to load App\SomeController class in app/SomeController.php file.
How is this related to service registration? Well, imagine you have to autoload classes like this:
1 class = 1 line in autoload. That would be crazy, right?
But if you use Symfony 3.2 or earlier versions, you might have no other option but to apply this type of approach to service registration:
PSR-4 Service Autodiscover just ported the PSR-4 approach to services.yml config. To make things simpler than the approach above, you can instead write it like this:
Do you need to add a service? Just place it according to the same way you’d want composer to find it — respect PSR-4. And that’s it. No configuration, no classes that are missing in your container, no dead-classes that are not used. But make no mistake, it’s still configurable (parameters, decorator, method calls, tags, etc.) like before.
Let’s say we want to use PSR-4 Service Autodiscovery and autoconfigure commands:
That makes sense, right? And it works. But we decided we wanted to make use of a Symfony 3.4 feature — lazy commands. Such a method allows the command dependency tree to be constructed in such a way that only one service per call is created, as opposed to all services being created, thereby resulting in faster performance.
Commands are lazy-loaded — yay! But we started to question other aspects of the feature:
Should we use PSR-4 all the time?
Are lazy-commands more important if you have a lazy-developer who wants to skip per-service configuration?
How much faster are lazy-commands over non-lazy in our application, and is it worth it to use this pattern?
How do we measure its effectiveness?
How do we explain/tutor new team members in its use?
What would you pick — a PSR-4 lazy-developer, or lazy-commands that give a slight performance boost to your application?
We are so lazy we chose both.
As one wise man said: “There Are No Solutions, Only Trade-Offs”, so we decided to go for it. If you follow the Symfony/demo package — which is the best showcase for new Symfony features before they’re even released — you might notice a pull-request with an upgrade to Symfony 3.4 with the following trick:
And then go back one step to the clean config:
your commands are lazy loaded
configuration is as simple as it can be
it’s consistent with the registration of all other services
you can move onto other applications
your new team-mates can now focus on other problems
Symfony is moving more and more towards adopting a standardized method of coding. Symfony Flex simplifies and unites package registration among other things, and thanks to the fact that there is now a singular way to work with services. The result is that the codebase is getting slimmer because there are fewer cases to cover, and developer experience overall is improving.
This also extends to performance: Fabien has reported that “according to those benchmarks, a “hello world” page on Symfony 4.0 is almost twice as fast as the same code using Symfony 3.4”. If you’ve managed to get through the last 2 steps above, then you’re already well on your way towards having a more productive experience with Symfony.
What is the next step?
From many services locators, inject helpers, and string types…
Symfony 3.4 now makes this super easy for services, repositories, commands and partially controllers (see point 4 bellow), which makes it easier to adopt SOLID patterns:
Class type over string name of service
Interface type over class type
Constructor dependency over container injection
From 8 possible paths to a single one! This makes it easy for even junior coders to pick it up. To see how it’s done take a look at our PR, or for a more advanced walkthrough use Rector to do it for you.
4. Simpler Controller Without Container?
We haven’t quite gotten to this step yet, but we think you should know about it. In a nutshell:
$this->get(‘…’) doesn’t work anymore in Controller, so how can you get dependencies without the container in controllers?
Also, controllers are similar to commands, in that they should be lazy because they’re only delegators. Imagine you have a controller with 10 actions. Each action has uses 2–3 other services. The benefit of using a lazy $this->get(‘…’) approach is that you only get the ‘tools’ or services you intend to use, as opposed to having to run all your services through a constructor.
In the ideal world, you’d use Invokable Controllers, with one __invoke() action and whatever dependencies the controller needs. This is a trend that became popular with EventListeners as well, but even though we would potentially like to see our preexisting 30 controllers expand to 150 this is currently not feasible.
This is a fast, useful way to incorporate lazy dependencies, but it’s important to note — only attempt this with controllers. You don’t want to use this pattern for your entire application, because it can be extremely difficult to decouple.
Let’s take a break from all these injections and look at a much more simpler code — in Forms.
Simple PhpStorm Find & Replace will do.
The following changes are not as groundbreaking, but we refer to them as “Butterfly Effects” — they seem like tiny changes, but they can suck up a considerable amount of time in terms of coming up with solutions.