PHP
Type jongleren en niet-strikte vergelijkingsproblemen
Zoeken…
Wat is Type Jongleren?
PHP is een los getypte taal. Dit betekent dat het standaard geen operanden vereist in een expressie van hetzelfde (of compatibele) type. U kunt bijvoorbeeld een nummer toevoegen aan een string en verwachten dat het werkt.
var_dump ("This is example number " . 1);
De output zal zijn:
string (24) "Dit is voorbeeldnummer 1"
PHP bereikt dit door automatisch incompatibele variabeletypen in types te gieten waarmee de gevraagde bewerking kan plaatsvinden. In het bovenstaande geval wordt het gehele getal 1 in een string gegoten, wat betekent dat het kan worden samengevoegd op de voorgaande letterlijke string. Dit wordt type jongleren genoemd. Dit is een zeer krachtige functie van PHP, maar het is ook een functie die je tot veel haartrekken kan leiden als je je er niet van bewust bent, en zelfs tot beveiligingsproblemen kan leiden.
Stel je de volgende situatie voor:
if (1 == $variable) {
// do something
}
De bedoeling lijkt te zijn dat de programmeur controleert of een variabele een waarde van 1 heeft. Maar wat gebeurt er als $ variabele in plaats daarvan een "anderhalf" heeft? Het antwoord zal je misschien verbazen.
$variable = "1 and a half";
var_dump (1 == $variable);
Het resultaat is:
bool (true)
Waarom is dit gebeurd? Het is omdat PHP zich realiseerde dat de tekenreeks "1 en een half" geen geheel getal is, maar dit moet het zijn om het te vergelijken met geheel getal 1. In plaats van te falen initieert PHP het type jongleren en probeert het de variabele om te zetten in een geheel getal. Dit wordt gedaan door alle tekens aan het begin van de tekenreeks die naar het gehele getal kunnen worden gegoten, te nemen en ze te casten. Het stopt zodra het een personage tegenkomt dat niet als een getal kan worden behandeld. Daarom wordt "1 en een half" gegoten naar geheel getal 1.
Toegegeven, dit is een zeer gekunsteld voorbeeld, maar het dient om het probleem aan te tonen. De volgende voorbeelden zullen enkele gevallen behandelen waarin ik fouten tegenkwam die werden veroorzaakt door jongleren met typen die in echte software gebeurde.
Uit een bestand lezen
Bij het lezen van een bestand willen we weten wanneer we het einde van dat bestand hebben bereikt. Wetende dat fgets()
false retourneert aan het einde van het bestand, kunnen we dit gebruiken als voorwaarde voor een lus. Als de gegevens die bij de laatste read zijn geretourneerd echter toevallig iets zijn dat als boolean false
wordt geëvalueerd, kan dit ervoor zorgen dat onze leeslus voor bestanden voortijdig wordt beëindigd.
$handle = fopen ("/path/to/my/file", "r");
if ($handle === false) {
throw new Exception ("Failed to open file for reading");
}
while ($data = fgets($handle)) {
echo ("Current file line is $data\n");
}
fclose ($handle);
Als het te lezen bestand een lege regel bevat, wordt de while
lus op dat punt beëindigd, omdat de lege tekenreeks als boolean false
geëvalueerd.
In plaats daarvan kunnen we expliciet controleren op de booleaanse false
waarde met behulp van strikte gelijkheidsexploitanten :
while (($data = fgets($handle)) !== false) {
echo ("Current file line is $data\n");
}
Merk op dat dit een gekunsteld voorbeeld is; in het echte leven zouden we de volgende lus gebruiken:
while (!feof($handle)) {
$data = fgets($handle);
echo ("Current file line is $data\n");
}
Of vervang het hele ding door:
$filedata = file("/path/to/my/file");
foreach ($filedata as $data) {
echo ("Current file line is $data\n");
}
Verander verrassingen
Switch-verklaringen gebruiken een niet-strikte vergelijking om overeenkomsten te bepalen. Dit kan tot vervelende verrassingen leiden . Overweeg bijvoorbeeld de volgende verklaring:
switch ($name) {
case 'input 1':
$mode = 'output_1';
break;
case 'input 2':
$mode = 'output_2';
break;
default:
$mode = 'unknown';
break;
}
Dit is een zeer eenvoudige verklaring en werkt zoals verwacht wanneer $name
een string is, maar kan anders problemen veroorzaken. Als $name
bijvoorbeeld geheel getal 0
, gebeurt er tijdens het vergelijken een type-jongleren. Het is echter de letterlijke waarde in de case-instructie die jongleert, niet de voorwaarde in de switch-instructie. De string "input 1"
wordt geconverteerd naar geheel getal 0
dat overeenkomt met de invoerwaarde van geheel getal 0
. Het resultaat hiervan is dat als u een waarde van geheel getal 0
opgeeft, het eerste geval altijd wordt uitgevoerd.
Er zijn een paar oplossingen voor dit probleem:
Expliciete casting
De waarde kan voorafgaand aan vergelijking worden typecast naar een string:
switch ((string)$name) {
...
}
Of een functie waarvan bekend is dat deze een string retourneert, kan ook worden gebruikt:
switch (strval($name)) {
...
}
Beide methoden zorgen ervoor dat de waarde van hetzelfde type is als de waarde in de case
statements.
Vermijd switch
Het gebruik van een if
instructie geeft ons controle over hoe de vergelijking wordt uitgevoerd, waardoor we strikte vergelijkingsoperatoren kunnen gebruiken :
if ($name === "input 1") {
$mode = "output_1";
} elseif ($name === "input 2") {
$mode = "output_2";
} else {
$mode = "unknown";
}
Strikt typen
Sinds PHP 7.0 kunnen sommige van de schadelijke effecten van jongleren met typen worden beperkt door strikt te typen . Door deze declare
instructie op te nemen als de eerste regel van het bestand, zal PHP parametertype-verklaringen afdwingen en type-declaraties retourneren door een TypeError
uitzondering te TypeError
.
declare(strict_types=1);
Deze code zal bijvoorbeeld, met gebruik van parametertype-definities, een vangbare uitzondering van het type TypeError
bij uitvoering:
<?php
declare(strict_types=1);
function sum(int $a, int $b) {
return $a + $b;
}
echo sum("1", 2);
Evenzo gebruikt deze code een aangifte van het retourtype; het zal ook een uitzondering genereren als het iets anders dan een geheel getal probeert te retourneren:
<?php
declare(strict_types=1);
function returner($a): int {
return $a;
}
returner("this is a string");