de OOP manier
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:
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:
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:
Code (php)
1
2
2
$container->addConfig($config); // $config zijn de gegevens uit het config-bestand
$foo = $container->get('foo');
$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:
Code (php)
Is dit een goede manier? Of is eerder dit de bedoeling:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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?
Gewijzigd op 13/02/2014 01:33:30 door Ozzie PHP
In bijvoorbeeld Symfony is deze configuratie in YAML:
Code (php)
1
2
3
4
5
2
3
4
5
# app/config/config.yml
services:
my_mailer:
class: Acme\HelloBundle\Mailer
arguments: [sendmail]
services:
my_mailer:
class: Acme\HelloBundle\Mailer
arguments: [sendmail]
gelijk aan deze configuratie in PHP:
Code (php)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
<?php
// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;
$container->setDefinition('my_mailer', new Definition(
'Acme\HelloBundle\Mailer',
array('sendmail')
));
?>
// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;
$container->setDefinition('my_mailer', new Definition(
'Acme\HelloBundle\Mailer',
array('sendmail')
));
?>
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?
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.
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?
Gewijzigd op 13/02/2014 13:17:08 door Ozzie PHP
Ozzie wat is OOP dan?
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:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
<?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
?>
$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
?>
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?
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.
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.
Ozzie PHP op 13/02/2014 13:18:10:
thx hahaha
Zoiets ja :)
Een loader laad de configuratie, dat is namelijk niet de taak van de container. De loader krijgt vervolgens de container instance en zet daarin de verkregen services.
>> 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.
Doormiddel van configuratie moet je kunnen bepalen of je een service inlaad. Dummy code:
Huh... dit volg ik ff niet :) Heb je het nu over een algemene loader, of is dit specifiek een loader voor de services container? En waarom geef je de container aan de loader ipv de gegevens uit de loader aan de container?
>> Doormiddel van configuratie moet je kunnen bepalen of je een service inlaad. Dummy code:
Dit snap ik ook even niet. Ik zet al mijn configuraties in 1 (YAML) bestand. Je kunt toch niet per service zeggen of ie wel of niet beschikbaar moet zijn?
Een service configuratie ziet er compleet anders uit als een routing configuratie, elke configuratie heeft dus recht op zijn eigen loader.
>> Dit snap ik ook even niet. Ik zet al mijn configuraties in 1 (YAML) bestand. Je kunt toch niet per service zeggen of ie wel of niet beschikbaar moet zijn?
Niet alles in 1 yaml bestand te zetten. Zet alle mailer specifieke services/parameters in mailer.yml, alle services/parameters voor het winkelwagentje in shop.yml, etc. Vervolgens kun je bepalen welke bestanden je wel en welke je niet inlaad.
Oké. Ik denk dat ik het snap. Waarom heb jij in jouw voorbeeld meerdere types loader (yaml en xml)? Je schrijft je services toch in 1 formaat (een yaml bestand)?
>> Niet alles in 1 yaml bestand te zetten. Zet alle mailer specifieke services/parameters in mailer.yml, alle services/parameters voor het winkelwagentje in shop.yml, etc.
Oké, als ik je goed begrijp krijg je dan dus een bestand met "defaults" en andere bestanden met bijv. een mailer, shop e.d. Maar stel nu dat jij gemaild wilt worden als er een exception optreedt... zet je die mailer service dan in een apart bestand, of is die mailer service een onderdeel van de default services?
Een old habit van Symfony2. Symfony2 staat toe om je configuratie in 3 formaten te schrijven: yaml, xml en php. Dat neem ik eigenlijk altijd over in mijn libraries...
>> Oké, als ik je goed begrijp krijg je dan dus een bestand met "defaults" en andere bestanden met bijv. een mailer, shop e.d. Maar stel nu dat jij gemaild wilt worden als er een exception optreedt... zet je die mailer service dan in een apart bestand, of is die mailer service een onderdeel van de default services?
Ik zou geen bestand default services maken. Gewoon alles opsplitten in bestanden en dan de bestanden met services die je altijd nog hebt inladen en vervolgens nog dynamisch. Daarna cache je de container, zodat dit totaal geen snelheidsverlies oplevert.
Exceptions mail je niet, maar log je. De configuratie van de logger bepaalt vervolgens hoe en waar er wordt gelogd: per e-mail bijvoorbeeld, of anders in een logbestand of een database.
Ho... niet zo snel ;)
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)
>> Exceptions mail je niet, maar log je. De configuratie van de logger bepaalt vervolgens hoe en waar er wordt gelogd: per e-mail bijvoorbeeld, of anders in een logbestand of een database.
Correct... ik doelde meer op het feit dat je in alle applicaties een mailer nodig kunt hebben omdat er mogelijk een mail moet worden verstuurd. Mijn vraag: laad je die mailer dan standaard in in je service container? Of ga je die pas inladen op het moment dat er daadwerkelijk iets gemaild moet worden. Anders gezegd, moet ik de mailer telkens "inschakelen" op het moment dat ik 'm nodig heb?
Je draait dan steeds om dezelfde hete brij heen: je gaat alle "mogelijke" services laden omdat ze "mogelijk" nodig zijn. Dat is zoals zogenaamd op survival gaan en dan stiekem je hele huisraad inclusief breedbeeld-tv en combimagnetron in een caravan meesleuren, want "je weet maar nooit wanneer je het nodig hebt".
Probeer concrete applicaties eens wat meer los te denken van je klassen. Klassen kunnen redelijk autonoom van alles en nog wat doen, omdat ze los van een context werken. Applicaties moeten echter gericht concrete taken uitvoeren; dat doen ze door de klassen aan het werk te zetten met een voor hun toepassing relevante configuratie. Vereenvoudigd gezegd is de configuratie dus de lijm tussen redelijk abstracte klassen en het gebruik van die klassen in een concrete applicatie.
Neem bijvoorbeeld opnieuw de mailer-service. Die kan van alles en nog wat mailen, maar niet elke applicatie wil hetzelfde type mail op dezelfde wijze mailen. De specifieke applicatie bepaalt daarom hoe er concreet wordt gemaild, niet een of andere standaardservice die door alle applicaties wordt gedeeld.
Ward, ik begrijp ook wel wat je bedoelt. Per applicatie andere services. Tot zover volg ik je. Maar binnen een applicatie, ga je dan een service, bijv. een mailer, pas laden op het moment dat je die daadwerkelijk nodig hebt? Of, laad je hem sowieso, omdat je weet dat je hem nodig zou kunnen hebben. Snap je het verschil tussen beide situaties?