OOP vraagjes
Nog een keer dan.
Een service is een class die OF een andere class nodig heeft OF een class die door een andere class gebruikt kan worden en waarvan de constructor argumenten of altijd hetzelfde zijn of niet aanwezig zijn.
Klopt ie nu? :)
En je hebt natuurlijk ook andere situaties. In symfony kun je services bijv. taggen zodat je die services allemaal kunt opvragen. Hierdoor kun je heel snel loggers, commands, blocks, admin extensies, routers, etc. registreren.
Wouter J op 10/10/2013 01:26:20:
Ja, wellicht met enkele uitzonderingen, maar die kan ik nu nog niet bedenken. Het komt ook een beetje aan op intuïtie.
Oh oké. Het kwartje moet bij mij ook nog even goed vallen. Ik hoop dat dat lukt. Maar uitgaande van wat jij nu zegt... Bijvoorbeeld een User, die heeft als argument een wisselend id. Een User is dus geen service. Correct? En hetzelfde geldt dan bijvoorbeeld voor een Product of een NewsArticle. Toch?
Wouter J op 10/10/2013 01:26:20:
En je hebt natuurlijk ook andere situaties. In symfony kun je services bijv. taggen zodat je die services allemaal kunt opvragen. Hierdoor kun je heel snel loggers, commands, blocks, admin extensies, routers, etc. registreren.
Hoe bedoel je dat je die services kunt opvragen? Als een soort tooltje ofzo om te zien welke services je tot je beschikking hebt?
Wouter J op 10/10/2013 01:09:44:
Klopt perfect. PDF creator is een service omdat hij aan voorwaarde 2 voldoet: classes kunnen hem nodig hebben en hij heeft veen veranderende argumenten in de constructor.
Er is een categorie services waarvoor dit allemaal geldt: de databaseservices. Je weet niet zeker of en wanneer een klasse databaseservices nodig heeft. En je hebt geen argumenten in de constructor nodig. Toch zou ik dat implementeren met $dbh = new Database(); in plaats van een servicecontainer.
Sterker nog, bij dataservices heb je zelf een extra reden om een servicecontainer te gebruiken: connection pooling. Maar dan nog is het voordeel daarvan in de praktijk te gering om een service container te gebruiken.
Exact!
Ward van der Put op 10/10/2013 06:36:12:
Er is een categorie services waarvoor dit allemaal geldt: de databaseservices. Je weet niet zeker of en wanneer een klasse databaseservices nodig heeft. En je hebt geen argumenten in de constructor nodig. Toch zou ik dat implementeren met $dbh = new Database(); in plaats van een servicecontainer.
Waarom zou je van een database geen service maken Ward? Een databaseservice heeft een connectie nodig. Dan is het toch juist handig dat wanneer jij de database nodig hebt, je alleen maar dit hoeft te doen:
$db = $this->services->get('database');
Verder hoef je dan helemaal niks in te stellen in je code. Verander je een keer je wachtwoord, dan hoef je dat alleen in je service-configuratiebestand aan te passen en overal in je code werkt het weer. (Wouter, mee eens?)
Wouter J op 10/10/2013 08:00:30:
>> En hetzelfde geldt dan bijvoorbeeld voor een Product of een NewsArticle. Toch?
Exact!
Exact!
Mooi, dan begin ik het eindelijk te begrijpen :)
Het enige wat me dan wel opvalt is dat je een niet-service-class (zoals een Product of een NewsArticle) niet makkelijk kunt vervangen door een andere class. Dan zul je dus overal de class-naam moeten aanpassen, of de code in de class zelf. Of is er nog een andere manier?
Eerder zei jij trouwens dit:
Wouter J op 09/10/2013 17:00:59:
Een ander mooi voordeel van een service container is dat alle objecten standaard niet opnieuw worden geinitialiseerd. Dat gebeurd 1 keer en daarna niet meer, behalve als je expliciet zegt dat hij elke keer een nieuwe instance moet terug geven. Maar in dat geval is de class 'stateful', en dat is iets wat je moet zien te vermijden.
Kun je uitleggen wat je bedoelt met dat een class stateful is, en waarom je dit moet zien te vermijden? (zijn er ook gevallen waarin een service juist wel stateful moet zijn?)
Gewijzigd op 10/10/2013 08:46:57 door Ozzie PHP
Ozzie PHP op 10/10/2013 08:25:05:
Waarom zou je van een database geen service maken Ward? Een databaseservice heeft een connectie nodig. Dan is het toch juist handig dat wanneer jij de database nodig hebt, je alleen maar dit hoeft te doen:
$db = $this->services->get('database');
Verder hoef je dan helemaal niks in te stellen in je code. Verander je een keer je wachtwoord, dan hoef je dat alleen in je service-configuratiebestand aan te passen en overal in je code werkt het weer. (Wouter, mee eens?)
$db = $this->services->get('database');
Verder hoef je dan helemaal niks in te stellen in je code. Verander je een keer je wachtwoord, dan hoef je dat alleen in je service-configuratiebestand aan te passen en overal in je code werkt het weer. (Wouter, mee eens?)
Het is ook een service, dat staat vast. Maar dat wil nog niet zeggen dat je dus een servicecontainer nodig. Laten we de termen service en servicecontainer niet op één hoop vegen.
Zoals ook blijkt uit je eerdere vragen, heb je twee problemen: (a) wat zijn mijn services en (b) hoe stop ik services in de servicecontainer en haal ik ze er uit?
Ik wil een new Foo() kunnen gebruiken als een black box: ik hoef niet te weten dat Foo de service/klasse Bar nodig heeft. Wat ik al helemaal niet wil, is vóóraf voor een keuze staan: in situatie x heeft Foo de service Bar nodig, in situatie y niet. En wat ik tot slot absoluut niet wil, is dat de service Bar wordt geladen en geconfigureerd wanneer Foo die niet nodig heeft; "wel eens nodig zou kunnen hebben" is geen goed ontwerpbeginsel.
Een servicecontainer heeft in bepaalde situaties absoluut voordelen. Alleen zijn die betrekkelijk gering als je ze naast andere mogelijkheden legt: namespaces, autoloaders, dependency injection, wrappers, aliassen...
Quote:
Zoals ook blijkt uit je eerdere vragen, heb je twee problemen: (a) wat zijn mijn services en (b) hoe stop ik services in de servicecontainer en haal ik ze er uit?
Wat betreft punt a) heb (had) je volledig gelijk. Wat punt b) betreft niet, want ik weet hoe ik de services erin stop en eruit haal.
In jouw voorbeeld heb je het over een new Foo(); Dit betekent dat telkens wanneer jij Foo gebruikt er een nieuwe instantie van die class wordt aangemaakt. Bij een service is dit niet het geval. Je gebruikt door de applicatie heen telkens dezelfde instantie.
Quote:
En wat ik tot slot absoluut niet wil, is dat de service Bar wordt geladen en geconfigureerd wanneer Foo die niet nodig heeft; "wel eens nodig zou kunnen hebben" is geen goed ontwerpbeginsel.
Dit is met services ook juist niet aan de orde. Lees de definitie hieronder...
@Wouter:
Nog even terugkomend op mijn vorige reactie... Als we tegenwoordig programmeren gebruiken we eigenlijk altijd een framework. Procdurele code doen we niet meer aan. Dit betekent dat (correct me if i'm wrong!) eigenlijk alle classes binnen een andere class staan. Want je laadt de core class, en feitelijk gezien speelt alles zich daarbinnen af. Mee een? Zo ja, dan is iedere class dus in feite een class die je binnen een andere class kunt gebruiken, en dus kunnen we de definitie van een service inkorten tot:
"Een service is een class die geen constructor-argumenten heeft, of een class die constructor-argumenten heeft die altijd hetzelfde zijn."
Wat vind je daarvan?
Ozzie PHP op 10/10/2013 09:09:58:
In jouw voorbeeld heb je het over een new Foo(); Dit betekent dat telkens wanneer jij Foo gebruikt er een nieuwe instantie van die class wordt aangemaakt. Bij een service is dit niet het geval. Je gebruikt door de applicatie heen telkens dezelfde instantie.
Dat klopt gedeeltelijk en ik noemde een belangrijk voorbeeld: connection pooling. Als een connection pool nodig hebt, zou ik absoluut een servicecontainer gebruiken. Alleen is en blijft dat een keuze en een ontwerpbeslissing, geen verplichting. Er kleven namelijk ook nadelen aan.
Meer technisch is het niet zo'n issue: bij het verlaten van een methode treedt automatisch de garbage collector in werking. Zou je voor 15 objecten in 15 klassen 15 keer een new Database() nodig hebben, dan zijn er nooit 15 instanties actief. Je aanname is hier deels onjuist: het zijn er in totaal 15, maar nooit 15 tegelijk.
Met andere woorden: stel jezelf ook de vraag of de servicecontainer die je zelf bouwt beter en sneller is dan wat PHP en je databaseserver allemaal al in huis hebben voor het beheer van processen en geheugengebruik.
Eerder zei ik dit:
Quote:
Bijvoorbeeld een User, die heeft als argument een wisselend id. Een User is dus geen service.
Toch vraag ik me af of dit klopt. Ik kan me voorstellen dat ik een User in meerdere classes nodig heb (bijv. om de naam op te vragen e.d.). Daarom lijkt het me handig als het wel een service is. Ik zou er een service van kunnen maken door het ID niet mee te geven aan de constructor, maar in plaats daarvan een setID method te gebruiken. Maar is dit de juiste manier vraag ik me nu dus af, of is een User simpelweg geen service? (En indien het geen service is, hoe krijg ik de User dan in alle classes beschikbaar? Telkens als argument doorgeven?)
De User is geen service. Wel bijv. een SecurityContext class die o.a. de huidige User class vasthoudt
Dankjewel Wouter. Maar stel nu dat ik ergens de gegevens van de User nodig heb, bijvoorbeeld in de header (u bent ingelogd als...) en ergens op de pagina zelf (Welkom...), moet ik dan het user_id opslaan in de sessie en vervolgens 2x een new User object aanmaken? Is dat de bedoeling?
Code (php)
1
2
3
4
5
6
2
3
4
5
6
<?php
$securityContext = $container->get('security.context'); // dit wordt natuurlijk geinjecteert
echo 'U bent ingelogt als '.$securityContext->getUser()->getName();
?>
$securityContext = $container->get('security.context'); // dit wordt natuurlijk geinjecteert
echo 'U bent ingelogt als '.$securityContext->getUser()->getName();
?>
Waarmee injecteer je die security context? Want het User object kun je niet injecteren omdat het geen service is toch?
Code (php)
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
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
<?php
/**
* @Service
*/
class SecurityContext
{
private $user;
public function setUser(UserInterface $this->user);
public function getUser()
{
return $this->user;
}
}
/**
* @Service
*/
class Authentication
{
private $context;
/**
* @Inject(service="security.context")
*/
public function __construct(SecurityContext $this->context);
public function authenticate(Request $request)
{
$user = // ... get the user object
$this->context->setUser($user);
}
}
?>
/**
* @Service
*/
class SecurityContext
{
private $user;
public function setUser(UserInterface $this->user);
public function getUser()
{
return $this->user;
}
}
/**
* @Service
*/
class Authentication
{
private $context;
/**
* @Inject(service="security.context")
*/
public function __construct(SecurityContext $this->context);
public function authenticate(Request $request)
{
$user = // ... get the user object
$this->context->setUser($user);
}
}
?>
Thanks voor het voorbeeld Wouter!