PHP
Afhankelijkheid injectie
Zoeken…
Invoering
Constructor injectie
Objecten zijn vaak afhankelijk van andere objecten. In plaats van de afhankelijkheid in de constructor te maken, moet de afhankelijkheid als parameter in de constructor worden doorgegeven. Dit zorgt ervoor dat er geen nauwe koppeling tussen de objecten is en maakt het mogelijk om de afhankelijkheid van klasse-instantiatie te wijzigen. Dit heeft een aantal voordelen, waaronder het gemakkelijker leesbaar maken van code door de afhankelijkheden expliciet te maken, en het testen eenvoudiger maken omdat de afhankelijkheden gemakkelijker kunnen worden omgeschakeld en bespot.
In het volgende voorbeeld is Component
afhankelijk van een exemplaar van Logger
, maar er wordt er geen gemaakt. In plaats daarvan moet er een als argument aan de constructor worden doorgegeven.
interface Logger {
public function log(string $message);
}
class Component {
private $logger;
public function __construct(Logger $logger) {
$this->logger = $logger;
}
}
Zonder injectie van afhankelijkheid zou de code waarschijnlijk lijken op:
class Component {
private $logger;
public function __construct() {
$this->logger = new FooLogger();
}
}
new
gebruiken om nieuwe objecten in de constructor te maken, geeft aan dat afhankelijkheidsinjectie niet is gebruikt (of onvolledig is gebruikt) en dat de code nauw is gekoppeld. Het is ook een teken dat de code onvolledig is getest of broze tests bevat die onjuiste veronderstellingen maken over de programmastatus.
In het bovenstaande voorbeeld, waar we in plaats daarvan afhankelijkheidsinjectie gebruiken, kunnen we gemakkelijk overschakelen naar een andere logger als dit nodig wordt. We kunnen bijvoorbeeld een Logger-implementatie gebruiken die zich aanmeldt bij een andere locatie, of die een ander logboekformaat gebruikt, of die zich aanmeldt bij de database in plaats van bij een bestand.
Zetter injectie
Afhankelijkheden kunnen ook worden geïnjecteerd door setters.
interface Logger {
public function log($message);
}
class Component {
private $logger;
private $databaseConnection;
public function __construct(DatabaseConnection $databaseConnection) {
$this->databaseConnection = $databaseConnection;
}
public function setLogger(Logger $logger) {
$this->logger = $logger;
}
public function core() {
$this->logSave();
return $this->databaseConnection->save($this);
}
public function logSave() {
if ($this->logger) {
$this->logger->log('saving');
}
}
}
Dit is vooral interessant wanneer de kernfunctionaliteit van de klasse niet afhankelijk is van de afhankelijkheid om te werken.
Hier is de enige benodigde afhankelijkheid de DatabaseConnection
dus deze bevindt zich in de constructor. De afhankelijkheid van Logger
is optioneel en hoeft dus geen deel uit te maken van de constructor, waardoor de klasse gemakkelijker te gebruiken is.
Merk op dat het bij het gebruik van setterinjectie beter is om de functionaliteit uit te breiden in plaats van deze te vervangen. Bij het instellen van een afhankelijkheid is er niets dat bevestigt dat de afhankelijkheid op een bepaald punt niet zal veranderen, wat kan leiden tot onverwachte resultaten. Een FileLogger
kan bijvoorbeeld eerst worden ingesteld en vervolgens een MailLogger
. Dit breekt inkapseling en maakt logboeken moeilijk te vinden, omdat we de afhankelijkheid vervangen .
Om dit te voorkomen, moeten we een afhankelijkheid met setter injectie toe te voegen, als volgt:
interface Logger {
public function log($message);
}
class Component {
private $loggers = array();
private $databaseConnection;
public function __construct(DatabaseConnection $databaseConnection) {
$this->databaseConnection = $databaseConnection;
}
public function addLogger(Logger $logger) {
$this->loggers[] = $logger;
}
public function core() {
$this->logSave();
return $this->databaseConnection->save($this);
}
public function logSave() {
foreach ($this->loggers as $logger) {
$logger->log('saving');
}
}
}
Op deze manier, wanneer we de kernfunctionaliteit gebruiken, zal deze niet breken, zelfs als er geen loggerafhankelijkheid is toegevoegd, en elke logger zal worden gebruikt, hoewel een andere logger had kunnen worden toegevoegd. We breiden de functionaliteit uit in plaats van deze te vervangen .
Container injectie
Dependency Injection (DI) in de context van het gebruik van een Dependency Injection Container (DIC) kan worden gezien als een superset van constructorinjectie. Een DIC analyseert doorgaans de typehints van een klasseconstructeur en lost zijn behoeften op, waarbij de afhankelijkheden die nodig zijn voor de uitvoering van de instantie effectief worden geïnjecteerd.
De exacte implementatie gaat veel verder dan het bereik van dit document, maar in wezen berust een DIC op het gebruik van de handtekening van een klasse ...
namespace Documentation;
class Example
{
private $meaning;
public function __construct(Meaning $meaning)
{
$this->meaning = $meaning;
}
}
... om het automatisch te instantiëren, meestal afhankelijk van een automatisch laadsysteem .
// older PHP versions
$container->make('Documentation\Example');
// since PHP 5.5
$container->make(\Documentation\Example::class);
Als je PHP in versie 5.5 gebruikt en een naam van een klasse wilt krijgen op een manier die hierboven wordt getoond, is de tweede manier de juiste manier. Op die manier kun je snel gebruik van de klas vinden met behulp van moderne IDE's, die je enorm zullen helpen met potentiële refactoring. U wilt niet vertrouwen op normale tekenreeksen.
In dit geval weet de Documentation\Example
dat het een Meaning
heeft en een DIC zou op zijn beurt een Meaning
instantiëren. De concrete implementatie hoeft niet afhankelijk te zijn van de consumerende instantie.
In plaats daarvan stellen we regels in de container in, voorafgaand aan het maken van objecten, die instrueert hoe specifieke types indien nodig moeten worden geïnstantieerd.
Dit heeft een aantal voordelen, zoals een DIC kan
- Deel gemeenschappelijke instanties
- Lever een fabriek om een typeaanduiding op te lossen
- Een interfacehandtekening oplossen
Als we regels definiëren over hoe een specifiek type moet worden beheerd, kunnen we nauwkeurige controle krijgen over welke typen worden gedeeld, geïnstantieerd of gemaakt vanuit een fabriek.