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?
Aangezien één servicecontainer meerdere services bevat, is de tweede gedachtegang mooier OOP: een apart object voor de definitie van elke service.

In bijvoorbeeld Symfony is deze configuratie in YAML:

# app/config/config.yml
services:
    my_mailer:
        class:        Acme\HelloBundle\Mailer
        arguments:    [sendmail]

gelijk aan deze configuratie in PHP:

<?php
// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;

$container->setDefinition('my_mailer', new Definition(
    'Acme\HelloBundle\Mailer',
    array('sendmail')
));
?>
Ah oké. Dankjewel.

Ik had al zo'n vermoeden dat het beter OOP zou zijn om voor iedere service een apart object te gebruiken.

Stel nu dat je op een gegeven moment 500 services hebt, dan krijg je dus ook 500 objecten. Werkt dat niet vertragend? Of is dat verwaarloosbaar?
Je neemt alleen kleine servicedefinities op in de servicecontainer, geen complete services. Waar jij een class Service hebt, heeft Symfony daarom een class Definition.

Pas bij de container->get('id') wordt het serviceobject van de gevraagde class gemaakt. Die vorm van "late binding" bindt resources dus pas aan de applicatie wanneer ze nodig zijn.

Je instantieert bijvoorbeeld de mailer-service pas als je gaat mailen. Tot die tijd is het slechts een kleine service-definitie in de servicecontainer.
Correct. Dat begrijp ik ook. De service (het object) wordt pas aangemaakt op het moment dat je het nodig hebt.

Maar ik bedoel wat anders. Ik laad dus de configuratiegegevens uit een bestand. Stel hier staan gegevens in voor 500 services. Wat ik momenteel doe, is eigenlijk klakkeloos deze gegevens (de array) in een class property (ook een array) van de container zetten.

De mooiere OOP oplossing is, zoals jij zelf aangaf, om de gegevens niet klakkeloos in een class property te zetten, maar om van iedere "definition" een apart object te maken.

Het mooie van dependency injection is dat er pas een object wordt aangemaakt, op het moment dat het nodig is. Maar aan de andere kant gaan we nu wel 500 objecten maken om de services te definiëren. Dus aan de ene kant van de weegschaal gaan we heel verstandig besparen (er wordt pas een object gemaakt als we het nodig hebben), maar aan de andere kant van de weegschaal maken we wel op voorhand alvast 500 objecten. Is dat wel handig/slim? En vooral... wordt je website hierdoor niet merkbaar trager? Of is dit verwaarloosbaar?
Dat is inderdaad praktisch verwaarloosbaar.

[hr]

Reactie die ik vanochtend om half 9 schreef, maar toen niet verzond:

Optie 2 is zeker beter. Je hebt te maken met meer dan 1 setting: je hebt het id, de class, zijn argumenten (let op, niet zijn parameters!). Misschien ga je later nog tags toevoegen, etc. Dit is dus niet meer in een mooie manier vast te houden dan in een object en als je nieuwe dingen toevoegt wil je niet alles hoeven aanpassen, dan wil je alleen het Service object aanpassen.

Maar ik zou dit zelf niet in de Container afhandelen. Ik zou dit in de ContainerLoader afhandelen, de container moet alleen Service objects krijgen. De ContainerLoader laad de configuratie, zo heb je een YamlContainerLoader, etc. Deze configuratie zet hij vervolgens ook om in de juiste data, die hij vervolgens aan de container geeft. Abstract genomen:
<?php
$ymlLoader = new YamlFileContainerLoader($filelocator);
$xmlLoader = new XmlFileContainerLoader($filelocator);
$loader = new LoaderChain(array($ymlLoader, $xmlLoader));

$container = new Container();
$loader->load($container);
$compiledContainer = $container->compile();

// werk nu vervolgens met $compiledContainer
?>
Ik vind het altijd mooi om te zien hoe complex jij vaak met dingen werkt :) Het begin volg ik altijd nog, en dan ineens dan komt er iets ingewikkelds :)

In ieder geval dus gaan werken met losse objecten. Oké, duidelijk. Dat was het belangrijkste wat ik wilde weten.

>> Maar ik zou dit zelf niet in de Container afhandelen. Ik zou dit in de ContainerLoader afhandelen, de container moet alleen Service objects krijgen.

Je bedoelt eigenlijk te zeggen dat je de configuratie buiten de container inlaadt en de ingeladen gegevens aan de container geeft? Zoiets?
Het is een drietrapsraket.

In de servicecontainer van de applicatie definieer je bijvoorbeeld alleen de mailer-service. Dat is één definitie.

Mogelijk heeft de mailer in tweede instantie, wanneer hij wordt geïnstantieerd, tientallen anderen objecten nodig voor het mailen. Dat laat je dan echter over aan de class loader van de mailer of een autoloader. Die klassen én de interne afhankelijkheden van de mailer-service blijven buiten de servicecontainer. De interne werking van een service is niet relevant voor applicaties die de service gebruiken.

Wat je inderdaad beter niet kunt doen, is alle 500 services van je platform alvast in de servicecontainer steken. Dat gevaar bestaat als je met slechts één config.yml of config.php werkt. Of met één index.php in de root die zogenaamd alles moet kunnen. Het werkt wel bij kleinschalige webprojecten, maar het schaalt niet lekker.

De servicecontainer gebruik je liever niet op siteniveau, maar op applicatieniveau. Hoeft een applicatie niet te mailen? Dan definieer je dus ook geen mailer-service in de servicecontainer van die applicatie.
>> De servicecontainer gebruik je liever niet op siteniveau, maar op applicatieniveau. Hoeft een applicatie niet te mailen? Dan definieer je dus ook geen mailer-service in de servicecontainer van die applicatie.

Ik snap je punt, maar dat is tegelijkertijd ook wel lastig. In iedere website moet je bijna wel kunnen mailen, weliswaar niet bij iedere pagina-aanroep. Het mailen is (hoe ik het zie) een basis-functionaliteit. Als ik die niet zou opnemen op siteniveau, dan moet ik iedere keer als ik de mailer wil gebruiken, eerst de mailer definition/config gaan inladen. Dat lijkt me eigenlijk ook niet helemaal de bedoeling toch? Wel zou ik me kunnen voorstellen dat je bij een webshop een winkelwagen service nodig hebt, die je bij een normale site niet nodig hebt.

Reageren