A bundle for Symfony.
The S.O.L.I.D. principles are a quintet of design guidelines aimed at enhancing the clarity, flexibility, and maintainability of software designs. Among these is the Open-Closed Principle, which advocates for the use of interfaces over concrete implementations.
In Symfony, the usual practice is to depend on concrete service implementations when injecting services, as opposed to utilizing an interface. Unfortunately, this approach compromises our code's flexibility and occasionally complicates testing.
Fortunately, this limitation can be overcome in Symfony by manually introducing aliases in the container for each class, a method that enhances flexibility and testing capabilities.
To rectify this issue and augment this practice, this bundle will declare new aliases in the Symfony container. Consequently, when a discovered service implements interfaces, aliases are automatically generated. For example, if a service implements three interfaces, the container will automatically create three new corresponding aliases.
These aliases are automatically created using the Fully Qualified Domain Name (FQDN) of the service's class when the discovered services implement one or several interfaces.
Leveraging aliases allows the injection of services using interfaces and named parameters instead of specific implementations, thereby addressing the often neglected Open-Closed Principle. In this way, our code becomes more adherent to S.O.L.I.D. principles, specifically the Open-Closed Principle, and hence more robust and easier to manage.
The following examples are showing an existing situation, as you can see, we do not respect that principle and we inject an concrete implementation directly:
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Repository\UserRepository;
final class MyTestController
{
// Here we inject a concrete implementation of a Doctrine repository.
public function __invoke(UserRepository $userRepository): Response
{
// Do stuff.
}
}When the bundle is enabled, you can inject the repository using an interface, using a specific parameter name.
<?php
declare(strict_types=1);
namespace App\Controller;
use Doctrine\Persistence\ObjectRepository;
final class MyTestController
{
// Here we inject the UserRepository (which implements ObjectRepository)
// using the variable which has been created from the UserRepository class name.
public function __invoke(ObjectRepository $userRepository): Response
{
// Do stuff.
}
}We can even do better by injecting it in the constructor. Then we can use the interface when injecting, and we can use the implementation in the property. Best of both world.
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Repository\UserRepository;
use Doctrine\Persistence\ObjectRepository;
final class MyTestController
{
private UserRepository $userRepository;
public function __construct(ObjectRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function __invoke(): Response
{
// Do stuff.
}
}composer require loophp/service-alias-autoregister-bundleSee the next section to learn how to enable it in your project.
The bundle can be enabled by just adding a specific tag: autoregister.alias
services:
App\:
resource: "../src/*"
exclude: "../src/{DependencyInjection,Entity,Tests,Kernel.php}"
tags:
- { name: autoregister.alias }services:
_instanceof:
Doctrine\Persistence\ObjectRepository:
tags:
- { name: autoregister.alias }Once it is done, do the following command to verify:
bin/console debug:container --tag=autoregister.aliasAnother example: find all the new aliases for Doctrine repositories:
bin/console debug:container ObjectRepositoryYou can configure this bundle by creating a configuration file in your application.
service_alias_auto_register:
whitelist: ~
blacklist:
- Countable
- Psr\Log\LoggerAwareInterface
- Symfony\Contracts\Service\ServiceSubscriberInterfaceThe configuration keys that are available:
whitelist: Let you configure a list of interface to use. Empty the list to whitelist them all.blacklist: Let you configure a list of interface to ignore. Default is empty array. It takes precedence on thewhitelist.
Feel free to contribute by sending Github pull requests.
If you can't contribute to the code, you can also sponsor me on Github.
See CHANGELOG.md for a changelog based on git commits.
For more detailed changelogs, please check the release changelogs.