Since the first Rector version, we used Symfony container to inject the services. It worked very well. The new PHP 8.0 came with attributes, and Symfony started to use them extensively.
We're downgrading Rector down to PHP 7.2, and this forced us to lock with unmaintained Symfony 6.1. We needed a hacky patch to make Rector config work...
This made us think: Is there a better way?
The container started to cost more maintenance than the features it provided, so we tried an experimental switch to Laravel. To our surprise, this helped us to make our tests run 7x faster.
How is this possible? Apart from the 3 changes we mentioned in the article regarding downgrading and scoping - the size matters.
During downgrade and scoping, all the /vendor
is shipped with the Rector project. That means every dependency is scoped and downgraded - line by line.
What about features? Rector is a CLI app, and it needs a simple dependency injection container:
If two packages are compatible, the more lines mean only more features we will never use.
Downgrading is a challenging process and sometimes it's a dead end - e.g., downgrading PHP code that uses PHP 8.0 attributes and doesn't provide a fallback feature on PHP 7.2 cannot be automated. Fewer lines mean fewer pitfalls to worry about.
Let's compare container package sizes without knowing the package name.
Guess which is which ↓
Filesystem count
Directories ......................................... 14
Files .............................................. 186
Lines of code count / relative
Code ................................... 17 694 / 79 %
Comments ................................ 4 698 / 21 %
Total .................................. 22 392 / 100 %
Then the other:
Filesystem count
Directories .......................................... 0
Files ................................................ 6
Lines of code count / relative
Code .................................... 1 091 / 56.1 %
Comments .................................. 855 / 43.9 %
Total ................................... 1 946 / 100 %
That means one is 16 x size greater than the other.
Results revealed:
We also used symfony/http-kernel for the kernel test case to run, but we skipped transitional packages for the sake of comparison simplicity.
Rector 0.18 is the first release with a Laravel container. Now, we're testing in the wild.
If you've used bare RectorConfig
class to set up your configuration, no upgrade is needed.
Few changes are required if you've used Symfony internal methods or maintain a custom Rector extension.
The way services are registered:
use Rector\Config\RectorConfig;
return static function (RectorConfig $rectorConfig): void {
- $services = $rectorConfig->services();
- $services->set(App\SomeService::class);
+ $rectorConfig->singleton(App\SomeService::class, function () {
+ return new SomeService('some parameter');
+ });
};
Tag service:
use Rector\Config\RectorConfig;
return static function (RectorConfig $rectorConfig): void {
- $services = $rectorConfig->services();
- $services->set(App\SomeService::class)
- ->tag(SomeInterface::class);
+ $rectorConfig->singleton(SomeService::class);
+ $rectorConfig->tag(SomeService::class, SomeInterface::class);
};
You can drop the $services->defaults()
calls ultimately, as this is now included in the core:
- $services = $rectorConfig->services();
- $services->defaults()
- ->public()
- ->autowire()
- ->autoctonfigure();
Also, PSR-4 autodiscovery is not needed anymore, as services are created for you:
- $services->load('Rector\\', __DIR__ . '/../packages')
- ->exclude([...]);
That's it! Find more in Laravel documentation and pull-request with container switch in Rector.
Is there something missing? Let us know to update this post. Thank you!
Happy coding!