public function getFoo() {
if ($some_condition === true) {
return $this->foo;
}
}
?>
Nu geeft deze functie bijv. altijd een array terug als de conditie waar is. Daarom geef ik een return type aan.
<?php
public function getFoo(): array {
if ($some_condition === true) {
return $this->foo;
}
}
?>
Dat gaat goed zolang er aan de voorwaarde wordt voldaan en een array wordt teruggegeven.
Echter, het kan ook zijn dat er geen array wordt teruggegeven. Ik had verwacht dat er dan impliciet NULL werd teruggegeven. Dus dan zou je denken om het return type als volgt aan te passen:
<?php
public function getFoo(): ?array {
if ($some_condition === true) {
return $this->foo;
}
}
?>
Maar helaas blijkt dit niet te werken. Omdat er niks wordt gereturned, leidt het tot een fatal error. Ik kan natuurlijk expliciet return null toevoegen, maar vroeg me af er wellicht een andere oplossing is.
En dan nog een vraag 2:
Als iets er niet is, denk aan bijvoorbeeld:
- je voert een SELECT query succesvol uit, maar deze levert geen records op
- je haalt een geb.datum op van een user, maar die blijkt leeg / niet te zijn ingevuld
- je haalt een winkelmand op, maar er zitten geen producten in (omdat niks besteld of sessie verlopen)
wat return je dan? Is het dan gebruikelijk om NULL te returnen (als in 'leeg') of false (als in 'mislukt').
Thanks Ivo. Dat had ik al geprobeerd, maar dat werkt niet. Void kun je wel 'stand alone' gebruiken, maar niet in combinatie met iets anders. Enkel void werkt dus, maar array|void werkt niet.
?Onbekende gebruiker
14-12-2022 19:52
gewijzigd op 14-12-2022 20:09
Even los van de vraag dat je een apart return NULL; statement moet ingeven in de functie, is het veelvoorkomende design toe aan verbetering.
Het probleem er van is dat je bij dat soort functies elke keer iets als dit zou moeten doen:
Het maakt ook duidelijk wat de intentie is.
En het kost iets meer programmeerwerk om het uit elkaar te trekken, en bij gedeelde implementatie het kost een extra functie call per keer dat je de functie gebruikt. Aan de andere kant is deze code voor de CPU beter te optimaliseren, omdat je een if-constructie vermijdt. Al zal dat effect in de meeste gevallen niet direct merkbaar zijn.
[size=xsmall]Toevoeging op 14/12/2022 20:06:45:[/size]
Ozzie PHP op 14/12/2022 12:18:16
En dan nog een vraag 2:
Als iets er niet is, denk aan bijvoorbeeld:
- je voert een SELECT query succesvol uit, maar deze levert geen records op
- je haalt een geb.datum op van een user, maar die blijkt leeg / niet te zijn ingevuld
- je haalt een winkelmand op, maar er zitten geen producten in (omdat niks besteld of sessie verlopen)
wat return je dan? Is het dan gebruikelijk om NULL te returnen (als in 'leeg') of false (als in 'mislukt').
Dat is een hele goede vraag. De implementatie van NULL wordt ook wel 'The Billion Dollar Mistake' genoemd. Dat ligt niet aan het concept dat een waarde kan missen, maar aan de implementatie van de programmeertaal (sorry als dit begint te klinken als een stokpaardje, ik kan het niet helpen). In Rust hebben ze een betere implementatie via de Option enum: een eigen datastructuur voor wanneer er wel en niet een waarde is, waardoor je zelf niet zonder meer een NULL-waarde in het geheugen hebt, waardoor je programma daar niet op kan crashen.
In reactie op de voorbeelden:
- query uitvoer
In mijn PHP implementatie geeft mijn zelfgemaakte query_params() functie altijd een array terug met daarin de rijen. Een lege array geeft aan dat er geen uitvoer is. Fouten worden gelogd via syslog. Sommige fouten laat ik automatisch terugkomen in de GUI, bijvoorbeeld als opslaan niet lukt door een beperking, dan wordt het commentaar van de beperking als foutmelding gebruikt. NULL gebruik ik niet.
- geboortedatum
Een string is een string, lege strings betekent geen geboortedatum. Wederom geen NULL-waarden.
- winkelmand met producten
Zelfde als met queries: altijd een array teruggeven, desnoods leeg. Zie ook mijn reactie in deze post.
Thanks voor het meedenken. Het returnen van een lege array zou inderdaad een optie zijn. Dan return ik toch nog een array. Helemaal zo verkeerd nog niet. De code wordt alleen iets langer. Bijv. in het voorbeeld van ophalen van een array uit een database:
<?php
public function getRows(): array {
$result = [];
if ("er is data gevonden") {
$result = $this->db->getRows();
}
return $result;
}
?>
Dat zou kunnen. Of nog korter:
<?php
return "er is data gevonden" ? $this->db->getRows() : [];
Afgaande op de opvallende syntaxis gebruikt Ozzie WordPress en in WordPress is array|null of ?array gebruikelijk. Nou is dat wat PHP betreft niet per se een standaard die navolging verdient (ook vanwege de afwijkende syntaxis), maar wel een standaard om rekening mee te houden wanneer je toch in die context aan het werk bent.
Vanwege de eenvoudige conditie zou ik het dan zo schrijven:
<?php
public function getFoo(): ?array
{
return ($some_condition === true) ? $this->foo : null;
}
?>
Hoewel je naïeve getters en setters beter niet kunt gebruiken, gedragen getters die optioneel null retourneren bij het ontbreken van een waarde zich zo meer als properties. Je kunt properties daardoor makkelijker vervangen door getters en vice versa, omdat hun gedrag vergelijkbaar is.
<?php
declare(strict_types=1);
class Person
{
public ?string $name = null;
public function getName(): ?string
{
return $this->name;
}
}
?>
Interessanter wordt het wanneer de geretourneerde null wordt veroorzaakt door een fout of onverwachte systeemtoestand. Databasefuncties van PHP retourneren dan doorgaans false in plaats van null. Die syntaxis mag je in PHP 8.2 ook gebruiken:
<?php
public function getFoo(): array|false
{
return ($some_condition === true) ? $this->foo : false;
}
?>
Als je wilt weten wát er fout gaat of waarom de getter geen resultaat retourneert, heb je niet voldoende aan false alleen. En dat brengt ons bij een andere standaard: de PSR-11 ContainerInterface. In plaats van null of een lege array gebruikt deze standaard bijvoorbeeld een NotFoundException of, strikt genomen, een exception die de NotFoundExceptionInterface implementeert.
>> Afgaande op de opvallende syntaxis gebruikt Ozzie WordPress
Nee, dat is een onjuiste aanname. Het was gewoon een algemene vraag over return types in combinatie met null.
Je geeft bij een method een return type aan waarbij je aangeeft dat het return type ook null kan zijn. Ik heb altijd begrepen dat een functie die niks return impliciet null returnt. Mijn gedachte was, ik haal gegevens op, ik doe een controle of er iets is opgehaald. Zo ja, dan return ik het. Zo nee, dan gebeurt er niks. Als er dan geen gegevens zijn gevonden, dan wordt er niks gereturned en wordt er dus impliciet null gereturned. Ik had dus verwacht dat ik dat met een return type ?array zou kunnen bewerkstelligen. Echter werkt dat niet, omdat je dus blijkbaar expliciet null moet returnen. Dat is ook wat ik terugzie in jouw verkorte (ternary) return.
Jouw tweede voorbeeld is geinig met die parameter null initialisatie. Die zal ik onthouden.
De meest interessante vraag (voor mij) is nog steeds, wat return je als iets er niet is / leeg is.
Ik denk dat we het er wel over eens zijn dat false duidt op een mislukte actie of het aangeven van een toestand (niet waar). Die doet wat mij betreft in deze discussie dus niet mee.
Mijn vraag gaat expliciet over wat je doet als je iets ophaalt dat geen resultaat teruggeeft omdat de waarde(s) leeg zijn. Er gaat in dit geval dus niks fout, maar bijv. je SELECT query levert geen resultaat op, de geb.datum die je ophaalt is niet ingevuld door de gebruiker of de class property die je ophaalt bevat een lege string.
Dus eigenlijk situaties als deze (pseudo-code enkel voor het idee):
<?php
$records = $db->get_records($sql); // geen rijen gevonden
$day_of_birth = $user->get_day_of_birth(); // geen geb.datum ingevuld
$last_name = $this->last_name; // achternaam niet ingevuld
?>
MIJN VRAAG :
Bij het ophalen van database records verwacht je bijv. een array. Echter, er worden geen resultaten gevonden. Return je een lege array of return je null?
Je kunt 2 kanten op. Zoals Ad Fundum zegt, kun je een lege array returnen. Wat mij betreft een mooie en prima oplossing. Je kunt een controle doen met empty() om te zien of er iets in de array zit en vervolgens wel of geen verdere actie ondernemen. Je geeft ook altijd hetzelfde type (in dit voorbeeld een array) terug, en ook dat is netjes en zorgt voor een voorspelbaar resultaat.
Tegelijkertijd kun je ook null returnen. Daarmee geef je "leeg" aan. Dat wat je wilde hebben is er niet. Het resultaat is leeg / bestaat niet ... is null. Ook dat is goed te verdedigen. Controle kan plaatsvinden met is_null.
Het voordeel van de eerste methode is dat je altijd eenzelfde datatype terugkrijgt. Het nadeel van de tweede methode is dat je niet weet welk datatype je terugkrijgt. Of een array of null. Dat laatste kan je ook van de andere kant bekijken. Je kunt dan altijd en overal controleren met is_null en je hoeft niet te controleren of het wel of niet een lege array is, of bijv. een lege string of een int. Je kunt dan gewoon altijd is_null gebruiken. Is het er niet? Dan is het altijd null.
Belangrijk lijkt het me om in ieder geval consistent te zijn, maar als je deze twee opties bekijkt, heeft een van beide dan de voorkeur en zo ja waarom? Kies je ervoor om een lege waarde van hetzelfde datatype te returnen, of return je in alle gevallen null?
?Onbekende gebruiker
16-12-2022 20:15
gewijzigd op 16-12-2022 20:30
@Ward: dank voor de bijdrage en de link naar PSR-11. Ik heb grote aversie tegen de PHP PSR's, deze in het bijzonder vanwege de vermeende standaardisatie van de dependency hell waar we het eerder over hadden. En ik heb nog nooit eerder het nut van Exceptions gezien. In mijn ogen is het laten falen van code een keuze, door wel of niet return codes te behandelen. Maar dat is een bijna religieuze discussie die afleidt van de vraag van Ozzie, dus ik laat het daar bij.
@Ozzie:
Returnwaarden van functies zijn in PHP ontzettend vaag helaas. Zo lees je in de documentatie van __construct() dat het 'void' returnt. Als je dat in PHP code stopt krijg je een fatal (!) error: "Fatal error: Method c::__construct() cannot declare a return type". Geen idee waarom, want bij andere magic functions kan het weer wel:
<?php
class c implements Serializable
{
function __unserialize(array $data): void { /* ... */ }
...
}
?>
De reden dat ik het aanhaal is dat PHP zelf ook nog een weg te gaan heeft. Zo kan je niet een functie maken die 'niets' returnt, een return type als array|void kan gewoon niet: "Fatal error: Void can only be used as a standalone type". Je moet dus wel iets anders returnen. Kennelijk maakt PHP voor het gemak (not) wel weer onderscheid tussen niets (void) en NULL. Handig? Nee. Je blijft dit soort verrassingen tegenkomen, zelfs met de nieuwe return type 'never'. Een functie kan niet returnen (ofzo), maar wel Exceptions opgooien. Logisch? Ik vind van niet.
PHP is een van-alles-aan-elkaar-plak-taal, in die context is er niets mis met het checken met is_null() of er iets uit een functie komt. Maar die check alleen zegt niets over het resultaat, omdat er.. niets is. Je kunt zo niet weten of het de bedoeling was dat er niets uit kwam. Bijvoorbeeld: stuur een keep-alive signaal naar socket X en het maakt voor je programma niet uit of dat is gelukt, maar je wilt de respons wel loggen. Je checkt met is_null() voor het loggen, maar eigenlijk kan je makkelijker de logging class meegeven aan de functie, of als globale static class beschikbaar maken.
Gezien dat NULL echt alles kan betekenen in PHP (behalve niets) vind ik het geen goede vervanger voor een missende waarde. Je zou zelf a la Rust een Option class kunnen maken. Maar mijn oplossing is deze:
Bedenk van te voren welke situaties er kunnen ontstaan, en hoe die afgehandeld moeten worden. Laat dat zoveel mogelijk aan de functie over, die belast is met de taak. Bijvoorbeeld voor de database:
<?php
class db
{
protected $link, $logger, $messager;
function __construct($link, $logger, $messager) { ... }
function query($sql, $params) : array
{
$data = $this->link->query_params($sql, $params);
if $this->link->last_errno > 0 {
if constraint_error {
$messager->write($this->get_constraint_error());
} else {
$logger->write($this->link->last_error);
}
return [];
}
return $data;
}
}
?>
Op deze manier hoef je jezelf na het aanroepen van $db->query() niet meer druk te maken over eventuele fouten, alleen over de rijen data die er al dan niet uit komen.