Inmiddels al weer aardig wat topics gelezen en opgezocht van de beginner tot de expert. Het is alleen altijd lastig om de voor jou beste methode uit te kiezen. Ik zit momenteel namelijk met 2 problemen.
Een daarvan is de alom bekende foutafhandeling of liever gezegd het opvangen van uitzonderingen. Ik heb inmiddels begrepen dat ik in de meeste gevallen Exceptions kan gebruiken en dat ik er verstandig aan doe om meerdere catch blokken te gebruiken om zo gebruikersfouten en systeemfouten te kunnen onderscheiden.
Momenteel heb ik het zo gedaan dat mijn autoloader binnen een try blok staat. Dat houd dus in dat mocht er in een van de classes wat fout gaan, dan wordt het in ieder geval opgevangen.
Mijn punt, zodra er dus iets fout gaat stop hij al het andere wat er binnen het try blok gebeurt, zo dus ook de knoppen die ik bijvoorbeeld weer laat geven onder een error.
In een situatie als een CMS dat er geen gebruikers gevonden kunnen worden, maar ik wil er nog wel een kunnen toevoegen. Dan moet niet ineens die knop weg zijn. Zou dit inhouden dat ik dan gewoon mijn try en catch blokken anders moet doen? Of kan ik aan de hand van levels wellicht zeggen dat hij de rest gewoon uit moet voeren? Of geen exception gebruiken, maar iets totaal anders, iets van mijzelf? En de exceptions laten voor de pure fouten die alles moeten stoppen.
Mijn andere probleem ligt bij het zo min mogelijk herhalen van code. Het liefst schrijf ik het maar 1 keer. Althans dat is mijn doel.
Maar neem bijvoorbeeld mijn UserMapper, die is praktisch hetzelfde als mijn PageMapper behalve dat er een andere factory method inzit is hij gelijk. Is er geen methode om dat over te erven zonder dat mijn flexibiliteit naar de maan gaat? Denk bijvoorbeeld aan een abstracte classe?
Ozzie, je hebt ook nog iets als backtraces... Die laten precies zien uit welke method het komt en uit welke method dat komt en wie die method heeft aangeroepen, etc.
En als je in dit geval een zeer belangrijk configuratie bestand wilt laden en je krijgt een FileNotFoundException, dan ga je die natuurlijk niet catchen. Als je echter de cache niet kan laden dan catch je hem wel, om vervolgens de cache opnieuw aan te maken. Daarvoor hoef je niet te weten waar die exception vandaan komt, je moet alleen de execution context weten (en die weet je).
>> Ozzie, je hebt ook nog iets als backtraces... Die laten precies zien uit welke method het komt en uit welke method dat komt en wie die method heeft aangeroepen, etc.
Snap ik. Dat is om achteraf actie te ondernemen.
>> En als je in dit geval een zeer belangrijk configuratie bestand wilt laden en je krijgt een FileNotFoundException, dan ga je die natuurlijk niet catchen.
Dat is dus wat ik bedoel. Hoe weet je om welke exception het gaat? Als je een wat groter try/catch blok hebt dan kunnen ze misschien allebei voorkomen, maar je vangt af op type. Ze worden dus beide opgevangen. Maar nogmaals, het is dus een kwestie van persoonlijke voorkeur en werkwijze.
Nee, want de 1 vang je als het goed al veel eerder op dan de globale try..catch.
<?php
class ConfigLoader
{
public function load(...)
{
try {
return $this->cacher->getConfig(...);
} catch (FileNotFoundException $e) {
// waarschijnlijk nog geen cache, laten we die aanmaken
$config = $this->doLoad(...);
// ai, als deze (-^) een FileNotFoundException gooit vangen
// we die niet op, er is iets belangrijks: config bestand
// is niet aanwezig!
Het voorbeeld wat ik hier geef is wellicht niet helemaal reƫel, maar het gaat me even om de achterliggende gedachte hoe je met een dergelijke situatie moet omgaan als je geen gebruik maakt van WAAR exceptions maar wel van WAT exceptions.
Stel we willen een of ander cache bestand laden:
<?php
try {
$foo = new Foo();
$cache = $foo->getFooCache();
} catch (FileNotFoundException $e) {
// file niet gevonden, data inladen
}
?>
We hebben hier dus een WAT exception. WAT kan er fout gaan? Een file wordt niet gevonden.
Hoe waarschijnlijk het onderstaande scenario is, laten we het daar even niet over hebben. Het gaat mij erom hoe je hier mee zou moeten omgaan.
Wat je wil bereiken is dus dat als de cache data niet wordt gevonden, je deze alsnog gaat inladen.
Stel nu dat de Foo class gebruik maakt van een configuratiebestand om te bepalen waar hij het cache-bestand moet zoeken. Oeps, om een of andere reden bestaat het configuratiebestand niet meer en de filesystem class gooit een FileNotFoundException. Deze exception is zo ernstig, dat we deze helemaal naar boven willen laten opborrelen. Maar o jee, hij wordt opgevangen door het catch-blok hierboven, terwijl dat niet de bedoeling is.
>> Dat plaatsen we dan niet in een try en catch blok.
Maar dat lijkt me dus een lastige. Je roept een functie aan en die functie moet ergens een path vandaan halen om te kijken waar het bestand moet worden opgehaald. Het config bestand wordt niet gevonden, en die exception wordt dus wel opgevangen. Nou kun je zeggen dat het niet handig geprogrammeerd is (ben ik met je eens) maar de situatie kan zich wel voordoen.
Of een ander voorbeeld. Het bestand van een class kan niet worden ingeladen. De autoloader gooit een FileNotFoundException. Deze zou dan ook weer door dit try/catch blok worden opgevangen wat niet de bedoeling is.
Voor de goede orde... het gaat er mij niet om wie gelijk heeft. Ik probeer uit te vinden of het voor mijzelf een optie is om ook met WAT exceptions te werken ipv WAAR exceptions, omdat ik in het 1e geval minder exception classes hoef te maken. Alleen ik wil graag weten of ik dan op een goede manier alle situaties kan afhandelen.
Ik persoonlijk ga beginnen met de WAT exception, als ik het zo lees bied het mij voldoende functionaliteit en kan ik er vrij simpel mee starten. De eerste heb ik al automatisch en dat is de PDOException. Met dat principe verder borduren lijkt mij een verstandige keuze.
Bij deze ga ik nu dus beginnen met het bouwen van Try en Catch blokken binnen mijn DataMappers e.d. Zo heb ik het eerste al gewonnen namelijk de PDOException en voor in de autoloader een FileNotFound Exception. Langzaamaan verder borduren op de mogelijk exceptions die ik krijg maakt straks een redelijk compleet systeem lijkt mij.
Edit
Nu ik hier dus aan wil beginnen lijkt het mij anderzijds ook heel onlogisch. Bijvoorbeeld in mijn DatabaseStorage, daar waar de Query wordt uitgevoerd. De functie execute retourneert TRUE of FALSE. Dan hoef ik toch geen exception meer te gooien? Bedoel de DataMapper krijgt een FALSE terug dus die retourneert dat gewoon weer naar mijn view pagina. Welke op zijn buurt weet dat er iets is fout gegaan.
Het enigste wat hij niet weet is wat er fout is gegaan, dat zou ik kunnen zien met die exception.
Is het dan geen idee om een soort van DEBUG mode aan en uit te kunnen zetten, of te loggen natuurlijk. Dus dat de exceptionhandler ook nog een FALSE retourneert naar de DatabaseStorage welke dat retourneert naar de DataMapper en uiteindelijk de view. En dan ondertussen de data gewoon opslaat. Zo kan ik per view pagina zelf een melden weergeven wat ik wil bijvoorbeeld Gebruikers niet gevonden of iets dergelijks, maar wordt er wel een goede fout opgeslagen.
>> De autoloader gooit een FileNotFoundException. Deze zou dan ook weer door dit try/catch blok worden opgevangen wat niet de bedoeling is.
Als een autoloader een bestand niet kan vinden gooit ie een ClassNotFoundException, eventueel met de FileNotFoundException als previous exception.
>> Het config bestand wordt niet gevonden, en die exception wordt dus wel opgevangen.
Die wordt niet opgevangen:
<?php
class Router
{
protected function getMatcher()
{
try {
return $this->cache->load('ProjectRouteMatcher.php');
} catch (FileNotFoundException $e) {
// geen cache
$routes = $this->loadRoutes(); // als deze een FileNotFoundException gooit wordt die niet opgevangen
$this->cache->write('ProjectRouteMatcher.php', $this->matcherDumper->dump($routes);
}
}
}
?>
>> Als een autoloader een bestand niet kan vinden gooit ie een ClassNotFoundException, eventueel met de FileNotFoundException als previous exception.
Duidelijk!
>> Die wordt niet opgevangen:
Dit is een ander voorbeeldje. Wat ik bedoel is dat je een method aanroept die een FooBar exception kan gooien. Deze method maakt gebruik van een andere class. Die andere class kan ook een FooBar exception gooien. De eerste FooBar exception wil je wel opvangen, maar die van die andere class niet, want die moet opborrelen naar boven. Hoe voorkom je nu dat die niet wordt opgevangen?