Versio

Dependency Injection

Een tutorial over wat Dependency Injection is, hoe je het gebruikt, wat Dependency Injection Containers zijn en hoe de mijne eruit ziet.

Gesponsorde koppelingen

BHosted Hosting al vanaf € 1,- per maand

Controleer nu gratis jouw domeinnaam:

  

Inhoudsopgave

  1. Inleiding
  2. Dependency Injection
  3. Dependency Injection Container
  4. Pcms container in opbouw - 1
  5. Pcms container in opbouw - 2
  6. De Pcms container
  7. Conclusie

 

30 reacties op 'Dependency Injection'

PHP hulp
PHP hulp
0 seconden vanaf nu
 
Gesponsorde koppelingen
Wouter J
Wouter J
5 maanden geleden
 
3 +1 -0 -1
Goede tutorial! Erg goed en duidelijk uitgelegd, genoeg code voorbeelden en comments. Precies zoals ik van je gewent ben!

Ik kijk wel uit naar een Unit testing tutorial.

Ik heb wel een heel klein typfoutje ontdekt:
Pcms (PIM cms :)) Container in opbouw -1
1e codeblok - regel 15 > \ voor InvalidArgumentException moet weg
Pim -
Pim -
5 maanden geleden
 
0 +1 -0 -1
Graag gedaan :)

Die \ hoort daar. In de laatste versie zit de container in de namespace Pcms. Om klasses uit de 'hoofdnamespace' aan te spreken moet er een backslash voor.

En het koekje is voor jou ;)
Kay Kay
Kay Kay
5 maanden geleden
 
0 +1 -0 -1
Waarom krijg ik email dat ik hier op gereageerd zou hebben, bij elke nieuwe reactie?
Jelle -
Jelle -
5 maanden geleden
 
0 +1 -0 -1
Interessante tutorial! Ik heb alleen nog een klein vraagje over het volgende (weet niet zeker of ik het helemaal begrijp):

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
$c
= new Container();
// Stel de parameters in
$c->set('mailer.username', 'foo');
$c->set('mailer.password', 'bar');
$c->set('mailer.class', 'Zend_Mail');

// Stel de transport service in
$c->set('mailer.transport', function($c) {
    return new Zend_Mail_Transport_Smtp('smtp.gmail.com', array(
      'auth'     => 'login',
      'username' => $c->get('mailer.username'),
      'password' => $c->get('mailer.password'),
      'ssl'      => 'ssl',
      'port'     => 465,
    ));
});


// Stel de mailer service in
$c->set('mailer.transport', function($c) {
    $class = $c->get('mailer.class');
    $mailer = new $class();
    $mailer->setDefaultTransport($c->get('mailer.transport'));
    return $mailer;
});


// De mailer roep je dan zo aan:
$mailer = $c->get('mailer');
?>


Als ik het goed begrijp denk ik dat je op regel 20 het volgende wou doen: $c->set('mailer' , func.....}); (aangezien je anders mailer.transport overschrijft)

Verder moet ik zeggen dat het me wel weer mooi aan het denken gezet, kan ik mijn structuur weer een stukje mooier maken :)
Pim -
Pim -
5 maanden geleden
 
0 +1 -0 -1
Je hebt gelijk. Bedankt.
Niels Kieviet
Niels Kieviet
5 maanden geleden
 
0 +1 -0 -1
Leuke / Mooie tut Pim!

Paar kleine dingen. Ik mis in de eerste voorbeelden de 'Method Visibility' ? Daarnaast, is het niet leuk / handig / makkelijk dat je in de container de magic functies __get, __set implementeert?

Over de (nieuwe) tutorials.

Wat dacht je van allebei? :-)
Pim -
Pim -
5 maanden geleden
 
0 +1 -0 -1
Method visibility is opgelost.
Een nadeel van get en set is dat je er geen derde parameter mee kan geven (shared). Ik ben er eigenlijk niet zo'n voorstander van... Ook is het vaak onduidelijk in API docs en voor IDEs. Pimple gebruikt ArrayAccess, dat vind ik nog net iets mooier.

Allebei... Wie weet ;)
Wouter J
Wouter J
5 maanden geleden
 
0 +1 -0 -1
Even kijken of ik het laatste gedeelte begrijp. Een Service Container is dus eigenlijk gewoon een Registery class?
Pim -
Pim -
5 maanden geleden
 
1 +1 -0 -1
Nee. Niet helemaal. Er zijn een paar verschillen.
- De service container is 'lazy loading'. De services worden pas bij een verzoek gemaakt.
- De service container is geen losse verzameling objecten, maar een samenhangend geheel waarbij de services dmv dependency injection aan elkaar hanen.
- De service container kan ook configuratie/parameters bevatten, de meeste registries niet.
- De service container is veel cooler :)
Wouter J
Wouter J
5 maanden geleden
 
0 +1 -0 -1
Pim -:
- De service container is 'lazy loading'. De services worden pas bij een verzoek gemaakt.

Bedoel je hiermee dat een functie pas wordt aangeroepen in Container::get()?
Een registery class stored alleen maar dingen toch, als je er een functie instopt moet je die nog zelf aanroepen na Registery::get()? Of heb ik het nou helemaal mis, ben pas begonnen met design patterns...
Pim -
Pim -
5 maanden geleden
 
1 +1 -0 -1
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
<?php
$mailer
= new Mailer();
$registry->set('mailer', $mailer);
$container->set('mailer', function () {
return new Mailer();
});

?>

Stel nou dat het heel ingewikkeld is om een Mailer object aan te maken. Bij de registry wordt dat gedaan tijden de configuratie en het zal dus altijd gebeuren, of het nou gebruikt wordt of niet.
Bij de container wordt de mailer pas aangemaakt bij de eerste (in het geval van een gedeelde service) aanroep. Als je het niet gebruikt wordt het mailer object dus ook niet aangemaakt.
PHP hulp
PHP hulp
0 seconden vanaf nu
 

Gesponsorde koppelingen
Pim -
Pim -
5 maanden geleden
 
1 +1 -0 -1
Bedenk trouwens dat een service container een vorm van een registry is en een registry een service container kan zijn, als het objecten opbouwt mbv dependency injection.
Kees Schepers
kees Schepers
5 maanden geleden
 
0 +1 -0 -1
Het heeft ook wat weg van het singleton pattern?, hoewel dat in Unit Testing land uit den boze is..
Pim -
Pim -
5 maanden geleden
 
0 +1 -0 -1
Nee, niet echt. De service container doet slechts dependency injection voor je. De services zelf krijgen dus netjes hun dependencies binnen en zo hoef je geen lelijke globals te gebruiken en kunnen de services dus ook goed getest worden.

De container zélf kan natuurlijk een singleton zijn, maar dat hoeft absoluut niet en dat raad ik dus niemand aan. Volgens mij is een goede maatstaf dat allen controllers (in een MVC omgeving) over de container beschikken. De rest krijgt zijn dependencies van de container, zonder weet te hebben ervan.

Of begrijp ik niet helemaal wat je bedoelt?
Daan l
Daan l
5 maanden geleden
 
0 +1 -0 -1
Let wel, dit is alleen een hele simpele variant van een container. Hetgeen wat iets vast houdt en geen dependency injection.

Dependency injection injecteerd de juiste objecten / variabelen in een class zie hier onder

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class Service1
{

    protected $_dataLaag;

    public function __construct(IDataLaag $dataLaag)
    {
        $this->_dataLaag = $dataLaag    
    }
}

interface IDatalaag
{

}

interface IDatabase
{

}

class DataLaag1 implemenets IDataLaag
{
    protected $_database;

    public function __construct(IDatabase $database)
    {
        $this->_database = $database;        
    }
}

class DataLaag2 implemenets IDataLaag
{
    protected $_database;

    public function __construct(IDatabase $database)
    {
        $this->_database = $database;        
    }
}

class FileDatabase implements IDatabase
{

}

class SqlDatabase implemenets IDatebase
{

}

$container->Bind('IDatabase')->To('SqlDatabase');
$container->Bind('IDatalaag')->To('DataLaag1');

$container->Resolve('IDatalaag');  //  datalaag1 + sqldatabase in datalaag1 constructor

$container->Bind('IDatabase')->To('FileDatabase');
$container->Bind('IDatalaag')->To('DataLaag2');

$container->Resolve('IDatalaag');  //  datalaag2 + filedatabase in datalaag2 constructor


Omdat je alles met interfaces aan elkaar hangt heb je geen harde koppeling en kan je elke class los testen.

Ik raad juist wel mensen aan om een singleton voor container te hebben, sterker nog het zou raar zijn als je twee containers hebt voor je applicatie.

Wat deze tutorial mist zijn:
Dependency resolver,
Class resolver,
Uitgebreidere container: hoe injecteer je bijvoorbeeld extra variabelen of stel je in dat een class als singleton moet gedragen.
Kees Schepers
kees Schepers
5 maanden geleden
 
0 +1 -0 -1
Wat ik met het Singelton gebeuren bedoel is dat Singleton ook een beetje lazy-loading is. Het object wordt pas echt aangemaakt als het nodig is. Verder niets. Zend Framework maakt nog geen gebruik van DI als in Service Containers e.d. maar ZF2 geloof ik wel, weet het niet zeker. Maargoed op dit moment doe ik een freelance opdracht waar ze dingen in SF2 gaan bouwen dus wie weet :)
Pim -
Pim -
5 maanden geleden
 
0 +1 -0 -1
@daan
'design by contract' kan je toch gewoon met een strong typed argument bij injectie method afdwingen?
En natuurlijk is deze container niet zo uitgebreid als mogelijk is, maar het is wel, net zoals bijvoorbeeld pimple en al helemaal met die configure methode die ik erbij bedacht heb, een volledig functionerende container.
Ook ondersteund de container gedeelde services, zoals ik dat beschreven heb. Waarom zou je dan nog (lelijke en ontestbare) singletons nodig hebben?
Hetzelfde geldt voor een singleton container. Als je dat gebruikt doe je je testbaarheid bij slordig gebruik al snel te niet en waarom zou je het voor jezelf onmogelijk maken twee containers te draaien (voor een test tussen twee chat clients bijvoorbeeld) als dat helemaal niet nodig is?

Als je me wil helpen de container tut uit te breiden tot sf2 dic formaat, heel graag, maar anders snap ik je kritiek niet echt...
Daan l
Daan l
5 maanden geleden
 
0 +1 -0 -1
@pim,

Wat jij beschrijft is geen dependency injection maar de titel heet wel zo. Als je dat niet snapt weet ik het ook niet meer, je injecteerd namelijk nergens een dependency maar bouwt alleen een simpele container.

Ik werk bijvoorbeeld ook nooit met harde implementaties behalve in mijn domain.

Je houdt bijvoorbeeld bij de container ook niet rekening met dat het geen wat je op vraagt ook weer dependencies heeft. Deze wil je niet, zoals jij doet elke keer zelf injecteren maar gewoon 1 keer instellen.

Sinds wanneer zijn singletons lelijk en onhandig? Een settings object wil je niet twee keer laden, zo zijn er nog vele andere voorbeelden.

Een class als singleton instellen, betekend niet meteen dat deze ontestbaar is. Bij mijn DI container zeg ik gewoon ->AsSingleton(), zo zorg ik er voor dat elke class zich als singleton kan gedragen zolang de DI container maar gebruikt wordt.


In een test gebruik je geen dependency injection dus dat argument gaat niet op maar dat snap je zelf ook wel.

Probeer je te verdiepen in patterns als je daar een tutorial voor schrijft. En kijk ook naar andere talen: java, c#, ruby.

Overigens ben ik geen voorstander van symfony of zend.
Kees Schepers
kees Schepers
5 maanden geleden
 
0 +1 -0 -1
@Daan, ik denk dat jij het niet helemaal snapt? Met testen bedoelen we unit testing. Dan wordt het lastig testen met singletons. Als je dit leest begrijp je wel waarom: http://sebastian-bergmann.de/archives/882-Testing-Code-That-Uses-Singletons.html

Dat je geen voorstander bent van Zend of Symfony verbaast me wel, je schrijft zelf een framework ofzo? Of je bent fan van een ander framework?
Daan l
Daan l
5 maanden geleden
 
0 +1 -0 -1
@kees vergis je niet ik snap heel erg goed waar jullie het over hebben.
Het is hoe je de singleton zelf in regelt he, op de manier zoals hun dat doen gaat dat natuurlijk niet werken dat is niet meer dan logisch lijkt me?

Maar als je het instelt op de DI container heb je dit probleem niet, overigens kan je een singleton ook weer injecteren.

Ik vind de frameworks te groot, ik hou liever van iets kleins wat in een paar regels te snappen is als je de bekende patterns kent bijvoorbeeld: MVC, Ioc (Inversion Of Control, waar DI gebruikt wordt), Domain driven design, Repository pattern etc...

Ik maak wel gebruik van delen van het Zend Framework maar dat is meer hun libraries bijvoorbeeld Soap Server WSDL generator is erg sterk.
Kees Schepers
kees Schepers
5 maanden geleden
 
0 +1 -0 -1
Dat wordt ook uitgelegd in het artikel Daan, dat het met een DI container wel te testen is.

Dat je frameworks groot vindt is een beetje een raar argument, wat maakt diskspace of grote nou uit? En groot als in zwaar zou ook een loos argument zijn omdat alleen dingen geladen worden die worden gebruikt.

Ik weet niet of je aan grote projecten werkt maar in mijn ogen als je dat zonder een goed framework doet snijd je zelf (al-dan-niet later) in je vingers.
Daan l
Daan l
5 maanden geleden
 
0 +1 -0 -1
Kees ik werk alleen maar aan grote projecten met main focus op architectuur. Je hoeft me daar dus niks over te vertellen en nee nog nooit in mn vingers gesneden zolang architectuur goed is heb ik geen zend of symfony nodig.
Kees Schepers
kees Schepers
5 maanden geleden
 
0 +1 -0 -1
Bijzonder, ik werk namelijk ook alleen aan grootschalige projecten en houdt me ook met name bezig met architectuur (speel daarbij ook een consultancy rol) en ik denk dat daar open-source frameworks in meerdere opzichten een essientieel belang vormen voor bedrijven.

Maargoed, ieder zo zijn eigen keuzes ondanks ik me niet in jouw beweging kan vinden.
Pim -
Pim -
5 maanden geleden
 
0 +1 -0 -1
@daan,
Sorry, maar ik snap er niet veel van. Of je nou een framework gebruikt of niet maakt niet zo veel uit, maar wat klopt er nou niet aan mijn tut?
In het eerste hoofdstuk introduceer ik het DI patroon. Dat patroon is simpel: het injecteren van afhankelijkheden ipv ze ter plekke instantiëren. Niet meer, niet minder. Je kan dan met interfaces werken, maar dat is niet de essentie, hooguit een nuttige toevoeging.

Mijn container heeft weinig magie. De injecties doe je gewoon zelf binnen de definitie van de service. Hierdoor moet je meer zelf doen, maar blijft je container zeer simpel, flexibel en kan het elke vorm van di aan. Je schrijft de injectie code immers zelf. De definitie wordt meer 'verbose' (vertaling?), maar hierdoor is het bouwen van de container wel eenvoudig en heel overzichtelijk. Dat was toch precies wat je graag hebt, zo zei je zelf?

En wat er mis is met een static container: je beperkt jezelf. Stel dat je een interactie tussen twee clients wil simuleren en functioneel wil unit testen, bijvoorbeeld in een chat app. Normaliter hebben die elk een eigen request en dus een eigen container. In de test wil je dat dus ook.
Dit lijkt misschien een onwaarschijnlijke situatie, maar het laat wel zien dat statics de testbaarheid kunnen verminderen. En wat is trouwens het verschil tussen een static container en een traditionele global var, waarvan we het allemaal eens zijn dat dat niet mooi is?
En over static services: wat is het verschil met mijn gedeelde services, afgezien van dat de deling niet global is, maar container gebonden, wat zoals ik net zei alleen maar voordelig is?

Je zegt verder dat services geen andere services kunnen aanroepen, maar dat is toch precies wat ik in mijn voorbeeld doe? De mailer.transport service is een dependency die voor meerdere services gebruikt kan worden.

En tot slot: de tutorial, en vooral de eerste twee delen, is gebaseerd op een blogpost van Fabien Potencier, de lead dev van een van de twee grote 'enterprise' frameworks: symfony. Ook is pimple, de container waarop ik de mijne heb gebaseerd, de kern van silex, een niet onbekend micro framework.
Zo erg mis kan ik het dan toch niet hebben?

En als laatste: di wordt niet bij testing gebruikt? Wtf?
Daan l
Daan l
5 maanden geleden
 
0 +1 -0 -1
@pim, dat je er niet veel van snapt dat zijn we beide eens. Het lijkt me verstandig om gewoon eens wat boeken te lezen over patterns. Voordat je uberhaupt zon tutorial post, een DI container heeft geen magie, die heeft de Resolver al is dat ook geen magie want daar stel je ook zelf op in.

Dat je meer vrijheid hebt slaat natuurlijk nergens op. Op jouw manier ga ik voor elke class alle dependencies injecteren. Das leuk voor 1 class maar niet voor 50, laat staan als er iets veranderd.

Het maakt er ook niet overzichtelijker op dat is een ding wat ik met je eens ben, voor 2 classes is leuk maar niet voor 50.

Hier uit blijkt wel dat je de patterns niet goed onder de knie hebt. Je hebt over Unit testen terwijl je intergratie tests bedoelt. Bij unit tests test je alleen de functionaliteit van 1 class niet interactie tussen twee clients.

Uiteraard kan je mailer service gebruikt worden, maar je opzet is gewoon niet geschikt voor grote applicaties.

Bijvoorbeeld meerlaagse dependency moet je zelf regelen terwijl je dit juist met een DI container uit handen wilt geven.

Anders gebruik je gewoon het factory pattern wat je eigenlijk nu doet, je container is 1 grote factory.

Je zult vast niet helemaal mis hebben, maar essenieele dingen kunnen toch beter. Wat ik ook zei kijk ook naar andere talen, PHP is nou niet echt een object georrienteerde taal.

Ik mag hopen dat je een DI container niet gebruikt bij het unit testen anders sla je de plak echt helemaal mis.
Daan l
Daan l
5 maanden geleden
 
0 +1 -0 -1
@kees

Ik maak wel gebruik van opensource dingen, eigenlijk alleen maar. Alleen hou niet van grote frameworks is gewoon persoonlijk voorkeur.
Pim -
Pim -
5 maanden geleden
 
1 +1 -0 -1
Natuurlijk is het zo dat een container complexer kan, maar waarom zou dat noodzakelijk zijn? Nu is de container klein en overzichtelijk. Onthoud, dit is een tutorial, geen script. Ik probeer hier uit te leggen hoe je de simpelst mogelijke container maakt, niet hoe je een full-featured container maakt. Deze container is echter wel degelijk volledig en daarom in mijn ogen zeer geschikt voor een tutorial.
Jij hebt het voortdurend over een bepaalde implementatie van de container, maar wat is het in essentie anders dan een factory die aan DI doet?

En zoals ik zei, parafraseer ik iemand die weet waar hij het over heeft. Stellen dat hij geen idee heeft waar hij het over heeft, is een beetje zwak zonder een hoop argumentatie.
Pim -
Pim -
5 maanden geleden
 
0 +1 -0 -1
Om even wat referentie te gebruiken: lees dit eens. In dit artikel wordt de term 'dependency injection' voor het eerst geïntroduceerd en zijn omschrijving komt toch wel sterk overeen met de manier zoals ik het van Fabien Potencier heb 'gejat'.
De container die Fowler beschrijft lijkt inderdaad min of meer op datgeen jij omschrijft, maar is qua functionaliteit hetzelfde als Pimple of bovenstaande container. Slechts de vorm is anders. Deze containers bouwen elk gebruikmakend van DI services op zoals een factory.
Merijn Venema
Merijn Venema
4 maanden geleden
 
0 +1 -0 -1
Ik vind het een leuk concept, DI. Doet me sterk denken aan Java`s Service containers. Maar ik mis hier wel de daadwerkelijke DI zelf. Dit is meer een container dan dat er daadwerkelijk DI plaatsvindt. Desalniettemin een erg leuke klasse. Ik zou hem persoonlijk als Singleton gebruiken, dat scheelt heel wat rondgooien van overerven en includen. Althans, laat ik het anders uitleggen, ik zou een abstracte klasse maken, vandaaruit de hoofdcontainer welke een singleton is en een losse container klasse welke vrij te gebruiken is. De hoofdcontainer is dan wel hanteerbaar. Ik zou ook containers als return willen zien, sub containers voor bijvoorbeeld mail. Hierdoor heb je tenminste ook onderscheid in alle dependency`s voor services qua mail etc.

Ik heb wel nog 1 opmerking, de set() methode kijkt niet of er al een key bestaat met x waarde, hierdoor kun je sleutels overschrijven wat misschien niet de bedoeling is? Misschien een exception gooien of een warning genereren dat er al een sleutel als $key bestaat?

Los daarvan vond ik het een erg interessante tutorial, doet me goed dat er eindelijk weer eens goede tutorials te vinden zijn hier :)

ps. is niet om te zeiken, DI wordt namelijk prima uitgelegd, heb het artikel van Fabien ook gelezen en je hebt het prima weten te verwoorden naar bruikbare, goed-te-begrijpen Nederlands.
Pim -
Pim -
4 maanden geleden
 
1 +1 -0 -1
Je hebt gelijk in zoverre dat je zelf de DI moet implementeren en dat de container dat niet voor je doet. Daarom is idd service container misschien een betere naam, ookal kan je met de zelf-gespecificeerde factories zeker aan DI doen.

Dat er geen exception wordt gegooid is bewust. Op die manier is het mogelijk core-services aan te passen en dat lijkt me vrij noodzakelijk in een modulaire applicatie.

Maar bedankt voor je feedback, die wordt erg op prijs gesteld :-)

Om te reageren heb je een account nodig en je moet ingelogd zijn.

  • Details
  • Pim -
    Door:
    Pim -
  • 5 maanden geleden
  • 1.328 x bekeken
Get Adobe Flash player