dependency injection
/home/xxx/private/pad/naar/library/" waarbij de prefix rood is gemaakt.
De inhoud van het YAML configbestand ziet er als volgt uit:
Normaal gesproken (lees: tot een paar weken geleden) zou ik het configbestand vanuit de Container class inlezen en op deze manier de parameters in de Container class instellen. Echter, ik heb hier op het forum geleerd dat ik nu juist een aparte class moet maken die de parameters inlaadt in de Container class (de OOP manier). Dat heb ik dan ook gedaan. De parameters worden nu via een Container_Loader class ingeladen in de Container class. Tot zover gaat het nog allemaal perfect. De parameters met de paden staan nu in de Container class! (De prefix ontbreekt echter nog...)
Nu komt echter het moeilijke deel: ik wil nu eigenlijk een service "paths" gaan instellen, die als ik 'm aanroep een paths object teruggeeft waarvan de properties de volledige (geprefixte) paden zijn.
Uiteindelijk is het de de bedoeling dat ik een (library) pad als volgt kan aanroepen:
echo $this->getContainer()->getService('paths')->private->library;
// geeft als resulaat "/home/xxx/private/pad/naar/library/"
Wat ik nu zou kunnen doen is (schematisch):
- 2 classes maken, een Paths class en een Paths_Loader class
- vanuit m'n Application class (FrontController) set ik een Paths class. Deze Paths class + de parameter paden uit de Container class + de pad prefix geef ik mee aan de __construct functie van de Paths_Loader class. Deze class vult op zijn beurt de Paths class en als dat gebeurd is, stop ik de Paths class als service in de container. Je krijgt dan ongeveer zoiets als dit:
Vraag me niet waarom... maar ik heb het idee dat er ene Wouter J over m'n schouder meekijkt en dat die zegt dat dit niet de juiste manier is! :-)
Volgens mij moet ik namelijk op de een of andere manier in het configuratiebestand een "paths" service instellen, in plaats van in mijn Application class (FrontController). Maar ik heb geen idee hoe! Vooral omdat die (dynamische) prefix nog aan de paden moet worden toegevoegd. Daarom vraag ik me af of ik überhaupt wel een Paths service via hetconfiguratiebestand kan instellen.
Maar toch heb ik het idee dat in dat configuratiebestand op de een of andere manier die service moet worden ingesteld en er een link moet worden gemaakt met de 'paths' parameters.
Maar hoe... hoe... hoe?????? Wie kan mij de juiste weg wijzen?
Jaja, ik probeer dan toch dependency injection te implementeren in m'n framework, maar nu zit ik al gelijk met het volgende probleem / uitdaging. Ik heb een YAML configbestand gemaakt met daarin een paar paden (nog lang niet compleet, maar we moeten ergens beginnen nietwaar). Deze paden zijn nog niet compleet, want ze moeten worden geprefixt met "home/xxx/private/". Deze prefix genereer ik dynamisch bij iedere pagina-aanroep (op basis van $_SERVER['DOCUMENT_ROOT']). Een pad bestaat dus uit een prefix + het gedeelte wat in het configbestand staat. Een pad naar de library is bijvoorbeeld "De inhoud van het YAML configbestand ziet er als volgt uit:
Normaal gesproken (lees: tot een paar weken geleden) zou ik het configbestand vanuit de Container class inlezen en op deze manier de parameters in de Container class instellen. Echter, ik heb hier op het forum geleerd dat ik nu juist een aparte class moet maken die de parameters inlaadt in de Container class (de OOP manier). Dat heb ik dan ook gedaan. De parameters worden nu via een Container_Loader class ingeladen in de Container class. Tot zover gaat het nog allemaal perfect. De parameters met de paden staan nu in de Container class! (De prefix ontbreekt echter nog...)
Nu komt echter het moeilijke deel: ik wil nu eigenlijk een service "paths" gaan instellen, die als ik 'm aanroep een paths object teruggeeft waarvan de properties de volledige (geprefixte) paden zijn.
Uiteindelijk is het de de bedoeling dat ik een (library) pad als volgt kan aanroepen:
echo $this->getContainer()->getService('paths')->private->library;
// geeft als resulaat "/home/xxx/private/pad/naar/library/"
Wat ik nu zou kunnen doen is (schematisch):
- 2 classes maken, een Paths class en een Paths_Loader class
- vanuit m'n Application class (FrontController) set ik een Paths class. Deze Paths class + de parameter paden uit de Container class + de pad prefix geef ik mee aan de __construct functie van de Paths_Loader class. Deze class vult op zijn beurt de Paths class en als dat gebeurd is, stop ik de Paths class als service in de container. Je krijgt dan ongeveer zoiets als dit:
Code (php)
1
2
3
4
5
6
7
2
3
4
5
6
7
<?php
$paths = new Paths();
$parameters = $this->getContainer()->getParameter('paths');
$loader = new Paths_Loader($paths, $parameters , $path_prefix);
$loader->load(); // de Paths class wordt gevuld met de juiste paden
$this->getContainer()->setService('paths', $paths); // we stellen de Paths class in als service
?>
$paths = new Paths();
$parameters = $this->getContainer()->getParameter('paths');
$loader = new Paths_Loader($paths, $parameters , $path_prefix);
$loader->load(); // de Paths class wordt gevuld met de juiste paden
$this->getContainer()->setService('paths', $paths); // we stellen de Paths class in als service
?>
Vraag me niet waarom... maar ik heb het idee dat er ene Wouter J over m'n schouder meekijkt en dat die zegt dat dit niet de juiste manier is! :-)
Volgens mij moet ik namelijk op de een of andere manier in het configuratiebestand een "paths" service instellen, in plaats van in mijn Application class (FrontController). Maar ik heb geen idee hoe! Vooral omdat die (dynamische) prefix nog aan de paden moet worden toegevoegd. Daarom vraag ik me af of ik überhaupt wel een Paths service via hetconfiguratiebestand kan instellen.
Maar toch heb ik het idee dat in dat configuratiebestand op de een of andere manier die service moet worden ingesteld en er een link moet worden gemaakt met de 'paths' parameters.
Maar hoe... hoe... hoe?????? Wie kan mij de juiste weg wijzen?
Gewijzigd op 04/02/2013 16:07:36 door Ozzie PHP
Waarom wil je de path van een service willen ophalen???
Of zijn paden geen service??? En zo nee, wat is dan wél een service?
Het is voor mij nog allemaal vrij nieuw dus wellicht begrijp ik het nog een beetje verkeerd.
Een service is een globale klasse die je in je code kunt gebruiken.
Kun je mij dan uitleggen wat precies een service is?
Toevoeging op 04/02/2013 17:03:58:
Laat ik het anders zeggen... waarom is een paths class geen service?
Maaarrrr.. die ModuleManager is dan ook een service want de Router heeft de ModuleManager nodig.
Code (php)
Zo zie je dat de router de service 'module_manager' nodig heeft. Een ServiceLoader laad die services dan in in je DI container. Maar, de ServiceLoader laad ook eventuele dependencies in.
Dus, stel de ServiceLoader laad de Router in in DI, dan geeft de ServiceLoader de ModuleManager mee als constructor argument voor de router.
Ik weet niet of ik het goed heb uitgelegd want iets goed uitleggen, daar ben ik niet zo goed in :P
Hmmm... een bijzonder verhaal... ik ben het spoor bijster nu. Ik wacht wel even wat Wouter gaat zeggen...
We hebben een Mailer klasse. Deze Mailer klasse stuurt, hoe kan het ook anders, mails. Om een mail te versturen hebben we een transport string nodig. In ons geval is dit 'sendmail'. Stel dat we nu ook nog een NewsLetterManager klasse hebben, deze klasse zorgt dat nieuwsbrieven verzonden worden. Deze heeft onze Mailer klasse nodig om emails te versturen. Je normale instict zou waarschijnlijk zeggen dit te doen:
Code (php)
Dit ziet er wel leuk uit, maar beeld je eens in dat de de newslettermanager op meerdere plaatsen nodig hebt. Dan moet je telkens eerst een instance van Mailer aanmaken en die dan injecteren in NewsLetterManager. Allereerst kost het veel werk voordat we een nieuwsbrief kunnen versturen en als 2e hoeft de Mailer maar 1x aangemaakt te worden.
Oké, dit gaan we verbeteren. Ipv telkens een Mailer klasse aan te maken met een 'sendmail' parameter maken we nu een mailer service. De configuratie zal dan zijn:
Ziet er niet onaardig uit, we kunnen hem dan zo gebruiken:
Code (php)
1
2
3
4
2
3
4
<?php
$mailer = $this->getContainer()->get('mailer');
$newsLetterManager = new NewsLetterManager($mailer);
?>
$mailer = $this->getContainer()->get('mailer');
$newsLetterManager = new NewsLetterManager($mailer);
?>
Nu hebben we de code al iets beter, maar nog niet optimaal. Stel dat ik nu een nieuwe Mailer klasse heb geschreven, dan zal ik graag die class aan willen passen. We doen er dan goed aan die op te slaan in een parameter. Tevens kunnen we bij een ander project misschien wel de voorkeur geven aan phpmail ipv sendmail. We moeten dat dus makkelijk kunnen aanpassen => ook een parameter van maken:
Code (php)
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
parameters:
mailer.class: Mailer
mailer.transport: sendmail
services:
mailer:
class: %mailer.class%
arguments: [%mailer.transport%]
mailer.class: Mailer
mailer.transport: sendmail
services:
mailer:
class: %mailer.class%
arguments: [%mailer.transport%]
Zoals ik ooit al had uitgelegd geven we met % parameters aan.
Nu kunnen we deze configuratie veranderen door een configuratie file aan te maken in onze module en daarin een nieuwe waarde aan mailer.class of mailer.transport te geven.
Maar nu zitten we nog steeds met het probleem dat we telkens een NewsLetterManager klasse moeten maken met een mailer instance erin. We kunnen beter van onze NewsLetterManager ook een service maken:
met @ geven we een service aan
Nu is het ook verstandig om deze klasse in een parameter te zetten, maar dat laat ik even buiten beschouwing. Als we nu een nieuwsbrief willen versturen is het enige wat we hoeven te doen deze newsletter_manager service uit de container te vissen en ->send(...) aan te roepen:
Code (php)
1
2
3
4
2
3
4
<?php
$newsletter = $this->getContainer()->get('newsletter_manager')->send(...);
// ...
?>
$newsletter = $this->getContainer()->get('newsletter_manager')->send(...);
// ...
?>
Nu hebben we in onze applicatie 2 services: Mailer en NewsLetterManager.
Je hebt gelijk dat services altijd in een configuratie bestand oid moeten worden ingesteld en een container zou na het laden daarvan moeten bevriezen, je kan dan niks meer toevoegen/veranderen.
1) Behalve een mailer... wat kunnen dan nog meer services zijn? Ik kan me voorstellen een database? Maar echt veel verder dan dat kom ik niet. Zou jij eens wat ding kunnen opsommen die een service (kunnen) zijn?
2) Als ik dit in een configuratiebestand zet
Met welke code stop je deze service dan in een container (de "set" functie) en met welke code zorg je dat de service op de juiste manier wordt opgehaald/uitgevoerd (de "get" functie)?
Heb je daar wellicht een voorbeeld van? Als je dat hebt, dan kan ik weer een tijdje vooruit denk ik :)
Alvast dank!
Quote:
ik kwam vanmiddag documentatie (in het Engels) van Symfony tegen waar dit ook in stond.
Klopt, ik gebruik veel dingen uit de documentatie om OO principes uit te leggen. Dat krijg je he, als je daar ook aan mee helpt.
Quote:
1) Behalve een mailer... wat kunnen dan nog meer services zijn? Ik kan me voorstellen een database? Maar echt veel verder dan dat kom ik niet. Zou jij eens wat ding kunnen opsommen die een service (kunnen) zijn?
Nou, zo'n beetje elke klasse is een service. Als je geïnteresseerd bent, dit is de lijst van services in een test projectje van me (gemaakt met symfony): http://pastebin.com/Y3f89ve5
Quote:
Met welke code stop je deze service dan in een container (de "set" functie) en met welke code zorg je dat de service op de juiste manier wordt opgehaald/uitgevoerd (de "get" functie)?
Hiervoor heb je een service loader nodig. Deze zet de data die hij krijgt om in een factory.
Ik snap die lijst niet in die link... heb jij die lijst zelf gemaakt? Ik zie er zelfs een firewall tussen staan. Gebruik jij al die bestanden in een project???
ServiceLoader zou ik dan moeten gaan schrijven, daar gaat behoorlijk wat tijd in zitten.
Gewijzigd op 04/02/2013 20:41:46 door Wouter J
Bestaat er geen standaardfunctie voor zo'n service loader? Gebruik jij zo'n stuctuur zelf dan niet?
Ik ben al blij dat ik snap wat het bovenstaande inhoudt. Echter, als ik het niet in m'n container gestopt krijg, dan kan ik het ook niet gebruiken...
Quote:
Maar wanneer is iets nou een service...? (...) Wanneer is iets een service? (...) Wat zijn de kenmerken van een service?
Een service is een klasse die je op een andere plek gebruikt/kan gebruiken. Zodra een klasse een andere klasse injecteert of zodra een klasse geïnjecteerd kan worden is het een service.
Quote:
Bestaat er geen standaardfunctie voor zo'n service loader? Gebruik jij zo'n stuctuur zelf dan niet?
Je zou die van Symfony kunnen pakken (kijk eens naar het DependencyInjection component). Ik gebruik zo'n structuur zelf ook, als ik met symfony2 werk, maar dan doet Symfony het allemaal lekker voor me (voordeeltje van andermans framework gebruiken). En in mijn eigen projecten, zonder Symfony2, gebruik ik vaak Pimple en ServiceProvider klassen (zie bijv. deze ServiceProvider)
"Een service is een klasse die je op een andere plek gebruikt/kan gebruiken."
Dit is duidelijk.
"Zodra een klasse een andere klasse injecteert of zodra een klasse geïnjecteerd kan worden is het een service."
Maar dit niet... Bedoel je wanneer een klasse een andere klasse nodig heeft om te kunnen werken? En dat dan beiden classes een service zijn?
Ik heb onlangs een klasse geschreven die al services en hun dependencies inlaad in de DI container.
Dankzij de tips van Wouter (thx!) is deze nu heel erg wat beter, maar je moet het niet serieus nemen.
ServiceLoader:
https://github.com/rvandenberge/Framework/blob/master/vendor/framework/lib/Framework/DependencyInjection/ServiceLoader.php
Config van de services:
https://github.com/rvandenberge/Framework/blob/master/config/services.php
Misschien dat je het door middel van die code wat beter snapt?
Thanks Raoul, ik blijf het gewoon lastig vinden. Dan denk ik dat ik er eindelijk uit ben, en dan toch weer niet... misschien moet ik het maar gewoon op m'n eigen manier doen... en in ieder geval even een nachtje erover slapen... zucht
Als je programmeert moet je het net op je eigen manier doen, dat is net het mooie aan programmeren. Maar, je moet een design pattern wel natuurlijk correct toepassen anders kan je het ook niet DI noemen.
Nee klopt... maar alles is een service... maar ik snap nog steeds niet goed waarom. Maar goed, ik ga maar eerst verder voorlopig, want het kost me teveel tijd nu. Thanks voor de hulp.
nu verder zonder di en later je hele framework omzetten in DI kost meer tijd dan nu uitvogelen hoe DI werkt.
Zodra iets eigenlijk een dependency nodig heeft (zoals de Router als dependency de ModuleManager heeft, als in m'n eerdere voorbeeld) zie ik het als een service. Een service is ook iets dat gebruikt word ALS dependency van andere services.