Ik ben nou al een hele tijd bezig met het developen van PHP applicaties.
Zo ben ik nu bijvoorbeeld bezig met een applicatie in Laravel, dat lukt me aardig, zo werk ik nu dus ook met een Laravel package die api data kan ophalen met een Http class. Nu is mij de vraag, het kan zijn dat de api het niet doet. Bijvoorbeeld: "U probeert verbinding te maken naar data die niet bestaat", "Er kan geen verbinding worden gemaakt naar de API" of "Even geduld AUB, te veel requests". Ik kan die bepaalde responses wel in een if/elseif/else statement stoppen of in een switch. Maar een paar mensen raden mij aan om try en catch te gebruiken. Ik kan er alleen geen kaas van maken. Kan iemand mij in Jip en Janneke taal uitleggen wanneer je precies try en catch gebruikt en hoe dat precies met de front end communiceert?
try-catch is voor het afvangen van Exceptions.
Die kunnen worden gegooid door de onderliggende code (API library, cURL, zelf geschreven, etc.) en geven aan dat er een uitzonderlijke situatie is opgetreden, oftewel iets wat niet verwacht word door de code.
Je kan de foutafhandeling dan in de catch omzetten naar een netter bericht voor je eindgebruiker of bijvoorbeeld een database-transactie terugdraaien, etc.
Een niet afgehandelde exception levert óf een error-pagina op (en dat hoort op productie systemen uit te staan) óf een 500 - Internal server error. Niet zo gebruiksvriendelijk dus.
Disclaimer: Onderstaande is gebaseerd op mijn ervaring/mening dus neem het niet aan als absolute waarheid of de beste oplossing.
In dit soort gevallen ben ik geen fan van if/else constructies, zeker als de lijst met fouten langer word.
switch/case is dan naar mijn mening netter, compacter en leesbaarder.
Voor wat betreft het terugmelden van fouten bestaan feitelijk 2 opties:
- Simpele melding dat iets mis is gegaan en de fout loggen.
- De fout beknopt en simpel doorgeven aan de gebruiker, zoals in jouw voorbeeld.
Het gevaar van de 2e strategie is dat je (te veel) je onderliggende structuur blootgeeft, wat een beveiligingsrisico kan zijn.
Ook interesseert het de gebruiker vaak niet wat mis gaat (en/of hij heeft onvoldoende kennis om te beoordelen wat nu te doen). De melding "Er is iets fout gegaan, probeer het later opnieuw" en de fout loggen is vaak al voldoende.
Wil je het écht netjes doen kun je nog automatisch een foutrapport mailen o.i.d.
Er is nog een 3e optie, het "inslikken" van de fout en niets doen. Waarom dat absoluut afgeraden word kun je misschien zelf wel bedenken ;)
Ik dacht zelf namelijk echter dat een Exception (zelf geschreven error regeling) er voor zorgt dat er code fouten komen te staan, dus iets wat een eind gebruiker niet snapt. Daarom zat ik meer met if/else, of nog beter switch in mijn hoofd.
Als je met code fouten een stacktrace of fatal error bedoelt dan ja. Als je een exception gooit en die niet afvangt krijg je dat.
Als je een nette stacktrace wil kun je XDebug installeren, dat maakt het wat vriendelijker te lezen en makkelijker te backtracen.
Op productiesystemen hoor je dat uit te schakelen en geen XDebug te installeren, dat is een security risico en de gebruiker heeft er inderdaad niets aan.
Exceptions hoor je dan ook af te vangen, om te zetten in een nette error en te loggen zodat je kan uitzoeken wat er mis ging.
Of je dat met een if/else of switch/case doet is meer een kwestie van smaak dan wat anders.
?Onbekende gebruiker
01-07-2021 14:01
gewijzigd op 01-07-2021 14:01
Exceptions kunnen handig zijn om op een centrale plek fouten af te handelen. Stel je zit in een database transactie en je wilt op meerdere punten de transactie annuleren als er iets fout gaat,
<?php
// maak verbinding met database
try {
// start transactie
if (!$query1) {throw new Exception(eigen melding);}
if (!$query2) {throw new Exception(nog een);}
// sluit transactie af
} catch (Throwable $e) {
// meld fout via $e->getMessage()
// annuleer transactie
} finally {
// verbreek database verbinding
}
?>
Voordelen:
- je mijdt een hoop if-statements
Nadelen:
- wat ik noem de 'scripting-ziekte', het is de bedoeling dat je je eigen objecttypen aanmaakt door de interface Throwable te extenden. Vervolgens kan je daarop differentiëren bij catch(), maar het is net als met Reflection minder snelle code dan wanneer je objecten voor alleen code gebruikt en variabelen alleen voor data.
- meerwaarde ten opzichte van gewone foutmelding is niet altijd evident:
<?php
try {
if ($ietsfout) {throw new Exception('foutje');}
} catch (Throwable $e) {
trigger_error($e->getMessage());
}
if ($ietsfout) {trigger_error('foutje');} // kortere versie
?>
- voor beide situaties is er een algemene handler: set_exception_handler() en set_error_handler().
Meestal gebruik je PHP toch in CGI-setting met php-fpm, en dan is de code eigenlijk dusdanig rechtlijnig dat ik nog nooit echt behoefte heb gehad aan Exceptions.
Een uitzondering is niet altijd een fout. Exceptions komen pas volledig tot hun recht wanneer je ze gaat gebruiken voor meer dan alleen errors.
Een voorbeeld:
<?php
declare(strict_types=1);
class InvoiceRepository
{
/**
* @param int $invoice_number Invoice number as an integer.
* @return Invoice
* @throws InvalidArgumentException Invalid invoice number.
* @throws NotFoundException Invoice not found.
*/
public function get(int $invoice_number): Invoice
{
// ...
}
}
?>
De methode get() retourneert een Invoice-object voor een factuur, maar kan parallel aan de normale flow van de app met een InvalidArgumentException melden dat het factuurnummer onjuist is en met een NotFoundException dat de opgevraagde factuur niet kon worden gevonden. Met de $message of $code van een Exception kun je daarbij indien nodig ook nog doorgeven wat waarom precies fout gaat.
Zo krijg je code die expressiever is (wat gebeurt er precies) én die je een grotere controle geeft (over wat er precies gebeurt).
?Onbekende gebruiker
02-07-2021 10:37
gewijzigd op 02-07-2021 10:44
Goed voorbeeld, dank!
Alleen vrees ik dat ik niet slim genoeg ben om het voordeel te begrijpen.
Je hebt een functie die iets moet doen, dat is dan situatie A.
Als het niet gaat naar verwachting, krijg je situatie B of C, en dat gaat die code niet zelf oplossen, maar doorgeven.
Dat doorgeven kan op veel manieren, waarvan een Exception er maar 1 is.
Andere manieren zijn: de class Invoice uitbreiden met de status, of een aparte statusvariabele bij functie-aanroep als
<?php
public function get(int $invoice_number, string &$status) : Invoice
?>
Exceptions fungeren als een soort van doorgeefluik voor berichten, waarmee je meerdere berichten kan doorgeven. Als je dat zou willen oplossen met een eigen soort front controller patroon kan dat met de ingebouwde handler() functies van PHP.
Maar ook het debuggen van Exceptions wordt niet meteen simpeler, je moet van elk try{} -blok nagaan of alles wel goed afgehandeld wordt in het bijbehorende catch{} -blok (bij meerdere try{} -blokken).
De enige meerwaarde voor Exceptions die ik zie, is wanneer die berichten een bepaalde gelaagdheid hebben, dat ze op verschillende punten afgehandeld moeten worden in objectgeoriëenteerde code, waarbij je daarvoor zelf geen voorziening wilt of kunt bouwen.
Mijn indruk is dat PHP gewoon mee wil doen met andere programmeertalen, wat verder prima is. Maar voor mij biedt het weinig extra's, het is alsof PHP faciliteert in een veelgebruikt design pattern, ongeacht of het een meerwaarde heeft. Op diezelfde manier zou PHP ook een ingebouwde singleton class kunnen gaan leveren, die je zelf weer kunt extenden, wat een kwestie van smaak zou zijn.
Dus ik had liever een minimalistischer PHP, alleen moet ik niet zeuren want ik krijg vanalles gratis. Toch kijk ik alvast naar C, omdat je met PHP geen code kunt compileren tot programma's. Dit is vermeldenswaardig om aan te geven wat mijn bias is.
>> Alleen vrees ik dat ik niet slim genoeg ben om het voordeel te begrijpen.
Niet zo negatief over jezelf ;-)
Ik ben van huis uit geen programmeur en heb alles zelf (aan)geleerd. Door het lezen van boeken, door te oefenen, door te praten met andere programmeurs, door te doen en vooral door geïnteresseerd te zijn.
'Vroeger' dacht ik dat je dingen maar op 1 manier kon programmeren. Dat iets alleen zou werken als je het op een exact, specifieke manier bouwt. Dus stel: Ad, Ozzie en Ward zouden dezelfde opdracht krijgen (bouw een foto-gallerij) dan zouden wij na afloop exact 100% dezelfde code hebben geschreven, omdat dat de enige manier was waarop het geprogrammeerd kon worden. Dat is dus hoe ik er 'vroeger' over dacht. Inmiddels weet ik wel beter. Programmeren is als het schrijven van een boek. Begin- en eindpunt zijn duidelijk, maar op welke wijze je het verder invult is geheel afhankelijk van de schrijver. Dus stel we zouden alledrie een boek schrijven, dan zou het verhaal ongeveer op hetzelfde neerkomen, maar toch zou het door ieder van ons anders geschreven zijn.
Er zijn hier op het forum leden geweest die glashard beweerden dat iets op een bepaalde manier 'moet'. Je móet het precies op deze exacte manier programmeren, anders 'klopt' het niet. En dat 'kloppen' hield dan vaak in dat er (volgens de interpretatie van het forumlid) niet exact gehouden werd aan de (volgens de interpretatie van het forumlid) regels van het betreffende programmeerprincipe.
Ik kan me goed vinden in wat Ad Fundum zegt over het minimalisme. Omdat bepaalde dingen 'kunnen', hoef je ze nog niet per definitie te gebruiken. Ik schrijf zelf liever wat strakkere, compacte, snelle code die lekker overzichtelijk is en makkelijk te onderhouden. Anderen houden er juist weer van om meer 'wollige' code te gebruiken en tot in de details dicht te timmeren. Iedere mogelijke fout/uitzondering wordt op iedere plek waar mogelijk afgevangen en de code wordt een stuk langer.
Even Ward z'n voorbeeld:
<?php
declare(strict_types=1);
class InvoiceRepository
{
/**
* @param int $invoice_number Invoice number as an integer.
* @return Invoice
* @throws InvalidArgumentException Invalid invoice number.
* @throws NotFoundException Invoice not found.
*/
public function get(int $invoice_number): Invoice
{
// ...
}
}
?>
Ward gooit in deze functie dus 2 exceptions. Dat is geen enkel probleem, maar je kan je afvragen waarom je het doet (ik zeg niet dat het goed of fout is).
In het commentaar lees ik bij InvalidArgumentException: Invalid invoice number.
Dit roept bij mij al een vraag op. Een ongeldig argument kan zijn dat een gebruiker als factuurnummer 'abc' invult, terwijl er alleen cijfers (bijv. 123) zijn toegestaan. Het argument(type) is dus ongeldig. Echter, de omschrijving heeft het over een ongeldig nummer. Wat is een ongeldig nummer? Is 'abc' een ongeldig nummer omdat het niet uit cijfers bestaat, of is 598945343 een ongeldig nummer omdat er maar 100 facturen in het systeem zitten. Of leveren beide gevallen een ongeldig argument op?
Dan de volgende: NotFoundException met als commentaar 'Invoice not found'.
Invoice not found. Hoe kan een factuur niet worden gevonden? Dat kan, lijkt mij, alleen als er een fout factuurnummer wordt opgevraagd (maar daar hadden we al een exception voor), of als iemand handmatig de betreffende factuur (het bestand) van de server heeft verwijderd. Echter heb je in zo'n geval te maken met een onbekwame programmeur, en dat lijkt me niet iets waarvoor je een exception zou moeten hanteren.
Kortom, we hebben 2 exceptions waarvan de toepassing voor mij persoonlijk niet geheel duidelijk is. Daarmee wil ik overigens niet zeggen dat het nut er niet is (Ward kennende heeft hij er namelijk heel goed over nagedacht).
Maar persoonlijk zou ik zo denken:
Iemand (een persoon) of iets (een proces) vraagt een factuur op. In het geval een proces dat doet, dan neem ik aan dat dat proces eerst controleert of het factuurnummer bestaat (haal alle factuurnummers van bedrijf X op). Hier kan eigenlijk niks fout gaan (mits goed geprogrammeerd). Een automatisch proces kan niet ineens letters gaan opvragen in plaats van cijfers. Op het moment dat een persoon een factuurnummer opvraagt, kan het wel fout gaan. Die kan inderdaad 'abc' opvragen in plaats van 123. Echter, dat lijkt mij niet zozeer een fout of uitzondering die zich voordoet in de Invoice class. En daarnaast ... als het goed is, heb je de input reeds gevalideerd voordat je het factuurnummer opvraagt, dus voordat je in die get functie komt, moet er al een controle hebben plaatsgevonden dat $invoice_number een getal is bestaande uit enkel cijfers. Dus de term 'invalid argument' is op dat moment al discutabel (afhankelijk van interpretatie). Het enige wat op dat moment nog 'fout' kan gaan is dat er een nummer wordt opgevraagd dat niet bestaat (nummer 9999 terwijl er maar 100 facturen zijn). Dat zou dan resulteren in een ... NotFoundException???
De vraag is dan ook ... als een gebruiker 9999 invoert in plaats van 99, moet daar dan een exception voor komen die logt dat iemand factuur 9999 heeft opgevraagd die niet bestaat? Ik zou denken van niet, omdat het nutteloze informatie is. Harstikke leuk dat iemand een verkeerd nummer invoert, maar waarom zou je dat loggen? Toon gewoon een foutmelding dat het nummer niet bestaat, zodat men het nogmaals kan proberen.
En goed ... zo kunnen we dus heel lang discussiëren. Wat ik bedoel te zeggen is dat wat voor de een prettig en logisch werkt, voor de ander niet per definitie het geval is. Ik denk dat het vooral belangrijk is dat je code schrijft die je zelf goed snapt en waarbij je je prettig voelt.
En daarnaast ... als het goed is, heb je de input reeds gevalideerd voordat je het factuurnummer opvraagt
Dat ligt er maar net aan, bijvoorbeeld:
Ik schrijf een library die facturen kan opvragen uit een ERP systeem. Die library geef ik vrij via packagist zodat anderen deze kunnen hergebruiken. Wie zegt dat die andere developer zijn validatie op orde heeft?
Als ik dan de API van die library documenteer is het (m.i.) veel gebruiksvriendelijker als ik beschrijf dat die method een Exception gooit als er iets fout is (of meerdere, maar het nut daarvan valt inderdaad te betwisten).
Dat is ook waar ik vaak Exceptions toepas, bibliotheken die ik elders wil hergebruiken. Dan hoef ik alleen de API te kennen en niet wat er binnenin precies gebeurt.
Ozzie PHP op 02/07/2021 11:42:33
En goed ... zo kunnen we dus heel lang discussiëren. Wat ik bedoel te zeggen is dat wat voor de een prettig en logisch werkt, voor de ander niet per definitie het geval is. Ik denk dat het vooral belangrijk is dat je code schrijft die je zelf goed snapt en waarbij je je prettig voelt.
Dat is een waarheid als een koe. "Goede" code is een subjectief begrip en een grijs gebied, minder "goede" code is niet persé fout. Foute code is daarentegen vaak behoorlijk duidelijk fout.
Wat ik bedoel met die validatie, is dat ik die buiten de 'get' functie in de invoice class zou houden. Een functie zou in principe maar 1 verantwoordelijkheid moeten kennen, in dit geval het ophalen van een factuur aan de hand van een nummer. Op het moment dat je die get functie aanroept zou m.i. het meegegeven argument al gevalideerd behoren te zijn. Die validatie zou ik ergens anders (buiten de invoice class) onderbrengen, en op die plek kun je je eventuele foutafhandeling doen.
In jouw geval (als library) snap ik ook wel weer dat het gebruiksvriendelijker is om een dergelijke aanpak te gebruiken zoals jij dat doet. Zoals gezegd hangt veel af van de omstandigheden. heb je zelf wel of niet volledige controle? Zolang je in ieder geval maar begrijpt waarom je iets doet, en niet zomaar iets doet omdat iemand anders dat ooit toevallig ergens heeft gezegd ;)