Hallo,

Ik heb weer even een OOP vraag.

Ik heb een container die services bevat. Heel simplistisch gesteld vraag ik aan de container een ID op, en dan krijg ik een (geconfigureerd) object terug. De verdere werking is voor mijn vraag niet echt relevant. Voorbeeldje:

<?php
$mailer = $container->get('mailer');
?>
Wat ik op dit moment doe is (zeer vereenvoudigd!) dit:

Ik heb een configuratie-bestand waar ik de ID's en de (geconfigureerde) objecten in zet:


Foo
  class: Foo
  
Bar
  class: Bar
  parameters:
    foobar: true

Nogmaals, het is allemaal wat versimpeld, maar het gaat om het globale idee.

Ik laad het configuratie-bestand in als een array en sla de gegevens rechtstreeks op in een class property (array) van de container class. En dit is waar mijn vraag vooral over gaat. Ik sla de gegevens dus op in een class property, en als ik een service (object) nodig heb dan wordt deze aan de hand van de gegevens uit de array geconstrueerd.

Schematisch:


$container->addConfig($config); // $config zijn de gegevens uit het config-bestand
$foo = $container->get('foo');

De vraag is of ik de gegevens uit het config-bestand direct in een class property (array) moet zetten, of dat het beter is om van ieder configuratie "item" een apart object te maken en deze aparte objecten op te slaan in een class property?

Ik kan me voorstellen dat het best lastig is om te volgen wat ik bedoel, maar ik hoop dat iemand het begrijpt.

Als ik de vraag heel plat stel, dan is eigenlijk de vraag: kan ik de array met configuratiegegevens in 1 keer in z'n totaliteit in een class property stoppen? Of is het de bedoeling dat ik van ieder element in de array een object maak, en dat ik al die afzonderlijke objecten toevoeg aan de class property in de container?

Nu doe ik zeg maar (weer even heel erg versimpeld) dit:

<?php
class Container {

private $config;

public function addConfig(array $config) {
$this->config = array_merge($this->config, $config);
}

public function get($id) {
$class = $this->config[$id]['class'];
return new $class;
}

}
?>
Is dit een goede manier? Of is eerder dit de bedoeling:

<?php
class Container {

private $config;

public function addConfig(array $config) {
foreach ($config as $id => $service_config) {
$this->config[$id] = new Service($service_config);
}
}

public function get($id) {
$class = $this->config[$id]->getClass();
return new $class;
}

}
?>
Het verschil zit 'm er dus in dat ik in de eerste situatie geen gebruik maak van afzonderlijke objecten (de informatie komt rechtstreeks uit de array), en in de 2e situatie wel.

Welke van deze 2 opties is de juiste OOP gedachtengang? Optie 1 of optie 2?
Jazeker, daarvoor is de servicecontainer ideaal!

Je laadt in de servicecontainer alleen de servicedefinitie:

- dit is de service-id;
- dit is het pad naar de class;
- dit zijn de eventuele parameters.

Pas bij de $container->get('id') tovert de servicecontainer de juiste new class tevoorschijn.

Strikt genomen is dit type service container dus meer een service mapper: de services zitten er niet bij voorbaat automatisch al in. Vandaar ook dat Wouter terecht zegt dat je eigenlijk nog een service loader moet gebruiken om de juiste service container te vullen. Daarmee krijg je een nog striktere scheiding van het "laden" en het "hebben" van een service,
Maar we hebben het toch de hele tijd over een service container?

>> Pas bij de $container->get('id') tovert de servicecontainer de juiste new class tevoorschijn.

Ja, precies! Maar volgens jullie moet ik de "mailer" service pas in de container inladen op het moment dat ik 'm nodig heb.

Dus eerst werkt dit niet:

$container->get('mailer')

Vervolgens laad ik de servicedefinitie van de mailer in de container, en dan werkt dit wel:

$container->get('id')

Dat is toch wat jullie bedoelen te zeggen de hele tijd? Of begrijp ik het verkeerd?
>> Je bedoelt dus dingen die bij elkaar horen/met elkaar te maken hebben zet je in aparte bestanden. En de dingen die je altijd nodig hebt zet je in 1 bestand, en dit bestand laad je in en dit cache je dan toch? De dynamische kun je toch niet mee-cachen? (omdat ze dynamisch zijn)

Nee, je splits alles op in bestanden. Vervolgens laad je die bestanden (dus meerdere, niet 1 default bestand, maar meerdere bestanden) die je altijd nodig hebt. Vervolgens bepaal je dynamisch (aan de hand van configuratie) welke andere bestanden je nog meer nodig hebt.

Merk op dat dit niet wordt gedaan wanneer je de service nodig hebt, maar wanneer je de container maakt. Dus je workflow:

Request 1
1. Container aanmaken
2. Laden van configuratie
3. Aan de hand van de configuratie de services in de container laden (doormiddel van ContainerLoaders)
4. Container compilen (vast zetten)
5. Container cachen
6. Container gebruiken

Request 2
1. Gecachete container ophalen
2. Container gebruiken
>> Ja, precies! Maar volgens jullie moet ik de "mailer" service pas in de container inladen op het moment dat ik 'm nodig heb.

Nee, je laadt de servicedefinitie meteen. Via die definitie lever je pas een service op als daarom wordt gevraagd.

Vergelijk de servicecontainer met een menukaart. We hebben hamburger en cheeseburger op het menu staan. De servicecontainer weet welke burgers we kunnen leveren en waar die precies vandaan komen. Maar we gaan natuurlijk pas een cheeseburger maken als erom wordt gevraagd.

Vandaar dat ik zei dat dit type servicecontainer eigenlijk in de eerste plaats een mapper is. De menukaart-service weet waar de cheeseburger vandaan komt. Maar bij het opstarten bak je niet meteen een hamburger én een cheeseburger omdat er "misschien" iemand een cheeseburger bestelt.
@Wouter:

Ah zo, duidelijk verhaal! Ook met dat lijstje. Helemaal top. Wat houdt "compilen" in? Is dat een algemeen gebruikte term?

@Ward:

>> Nee, je laadt de servicedefinitie meteen. Via die definitie lever je pas een service op als daarom wordt gevraagd.

Dit is duidelijk. Maar uit het verhaal van Wouter, en waarschijnlijk is dat ook wat jij bedoelt, leid ik af dat je dus niet ALLE definities inlaadt in de container, maar alleen díe definities die je per applicatie nodig hebt. Correct?
Correct.

Compile is het omzetten van het ene naar een andere taal. In dit geval heb ik het woord gewoon van symfony gestolen. Tijdens het compilen maak je van een container builder een geslote container die gereed is voor gebruik in je code.
Allright. Thanks voor de input! Hier kan ik wel weer even mee vooruit :)
Los van hoe je zo'n Service Locator/Service Container moet implementeren is het eigenlijk niet echt goede OO code. Het is eerder een anti pattern.

Je classes krijgen een directe dependancy met je ServiceLocator (SL). En al geef je je SL door als parameter, dan is het nog steeds geen goede OO.

Het strookt bijvoorbeeld niet met de "Law of Demeter". De classes die de SL gebruiken moeten niet alleen de API van de SL kennen, maar ook van alle children die de SL heeft. Zelfde geldt voor " Single Responsibility Principle". Ook Unit Tests kunnen een probleem vormen en nog een hele lijst aan andere dingen.

Dus als je echt goed OO wilt doen, zou ik niet aan een SL beginnen.

Dan kan je beter een Inversion of Control (IOC) class maken. Daarmee geef je via interfaces door welke objecten classes/methods nodig hebben. Dan is zo'n class niet afhankelijk van een SL. Zo'n SL geeft namelijk ook toegang tot veel meer classes/services dan wanneer je in een methods parameter aangeeft welke classes hij mag krijgen.

Dus als je nog niets over IoC weet zou ik daar ook eens naar kijken.

Dan valt er vervolgens ook weer heel wat over IoC's te zeggen, Snel voorbeeld:

Stel bijvoorbeeld dat je in een Controller een "new object" aan maakt. Dat object geef je door aan een Model class. Die geeft het op zijn beurt weer door aan een andere class en dat nog een keer of 3. Dan weet je uit eindelijk dat je een "code smell" hebt. Omdat je een object een stuk of 5/6 keer moet doorgeven.

Bij een IoC zou je zo'n code smell al niet opmerken omdat je rechtstreeks een object kan meegeven aan de class die hem nodig heeft. Je hoeft dus niet al die classes langs om een object door te geven.

Zo zijn er nog wel een aantal voorbeelden te noemen. Ik zou niet zo snel aan allerlei termen en patterns hechten en gebruiken omdat het maar overal wordt geroepen. De meeste dingen zijn slechts in specifieke gevallen echt handig en sommige zijn helemaal niet handig, maar komen theoretisch gezien wel heel goed en logisch over. Zoals met Service Locators en IoC's.
D Vivendi, naast het feit dat je een leuke opsomming noemt met design patterns die door een DIC worden gebruikt en anti patterns die je niet met doen met DIC vraag ik me zeer af wat dit te maken heeft met dit topic...
>> Zo'n SL geeft namelijk ook toegang tot veel meer classes/services dan wanneer je in een methods parameter aangeeft welke classes hij mag krijgen.

Hmmm... dan vraag ik me af of we het wel over hetzelfde hebben. Juist via een service container kun je via de constructor precies aangeven welke classes er mogen worden gebruikt, dus ik snap niet helemaal wat je bedoelt te zeggen.

Reageren