PHP
Typ jonglering och icke-strikta jämförelseproblem
Sök…
Vad är typ jonglering?
PHP är ett löst typspråk. Detta innebär att det som standard inte kräver operander i ett uttryck av samma (eller kompatibla) typer. Till exempel kan du lägga till ett nummer i en sträng och förvänta sig att det ska fungera.
var_dump ("This is example number " . 1);
Utgången kommer att vara:
sträng (24) "Detta är exempel nummer 1"
PHP åstadkommer detta genom att automatiskt kasta inkompatibla variabeltyper i typer som gör att den begärda operationen kan ske. I fallet ovan kommer det att hella bokstäverna 1 i en sträng, vilket innebär att det kan sammankopplas till den föregående strängens bokstav. Detta kallas typ jonglering. Detta är en mycket kraftfull funktion i PHP, men det är också en funktion som kan leda dig till mycket hårdragning om du inte är medveten om det och även kan leda till säkerhetsproblem.
Tänk på följande:
if (1 == $variable) {
// do something
}
Avsikten verkar vara att programmeraren kontrollerar att en variabel har ett värde på 1. Men vad händer om $ variabeln har ett värde på "en och en halv" istället? Svaret kan överraska dig.
$variable = "1 and a half";
var_dump (1 == $variable);
Resultatet är:
bool (true)
Varför har detta hänt? Det beror på att PHP insåg att strängen "en och en halv" inte är ett heltal, men det måste vara för att jämföra det med heltal 1. I stället för att misslyckas, initierar PHP typjuggling och försöker konvertera variabeln till en heltal. Det gör detta genom att ta alla tecken i början av strängen som kan kastas till heltal och casta dem. Det stannar så fort det möter ett tecken som inte kan behandlas som ett nummer. Därför får "en och en halv" roll till heltal 1.
Visst är att detta är ett mycket frivilligt exempel, men det tjänar till att visa problemet. De följande exemplen kommer att täcka några fall där jag har stött på fel orsakade av typjuggling som hände i verklig programvara.
Läser från en fil
När vi läser från en fil vill vi kunna veta när vi har nått slutet på den filen. Genom att veta att fgets()
returnerar falskt i slutet av filen kan vi kanske använda detta som villkor för en slinga. Men om de data som returnerades från den senaste läsningen råkar vara något som utvärderas som booleskt false
, kan det göra att vår fillässlinga slutar för tidigt.
$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);
Om filen är läsning innehåller en tom rad, den while
kommer slingan avslutas på den punkten, eftersom den tomma strängen utvärderas som boolean false
.
I stället kan vi kontrollera om det booleska false
värdet uttryckligen använder strikt jämställdhetsoperatörer :
while (($data = fgets($handle)) !== false) {
echo ("Current file line is $data\n");
}
Observera att detta är ett förfalskat exempel; i det verkliga livet skulle vi använda följande slinga:
while (!feof($handle)) {
$data = fgets($handle);
echo ("Current file line is $data\n");
}
Eller ersätt hela saken med:
$filedata = file("/path/to/my/file");
foreach ($filedata as $data) {
echo ("Current file line is $data\n");
}
Växla överraskningar
Byt uttalanden använder icke-strikt jämförelse för att bestämma matchningar. Detta kan leda till några otäcka överraskningar . Tänk till exempel på följande uttalande:
switch ($name) {
case 'input 1':
$mode = 'output_1';
break;
case 'input 2':
$mode = 'output_2';
break;
default:
$mode = 'unknown';
break;
}
Detta är ett mycket enkelt uttalande och fungerar som förväntat när $name
är en sträng, men kan orsaka problem annars. Till exempel, om $name
är heltal 0
, kommer typ-jonglering att ske under jämförelsen. Det är emellertid det bokstavliga värdet i ärendeklarationen som sjugglas, inte villkoret i switch-uttalandet. Strängen "input 1"
konverteras till heltal 0
som matchar ingångsvärdet för heltal 0
. Resultatet av detta är om du anger ett värde på heltal 0
, det första fallet körs alltid.
Det finns några lösningar på detta problem:
Explicit casting
Värdet kan vara typecast till en sträng innan jämförelse:
switch ((string)$name) {
...
}
Eller en funktion som är känd för att returnera en sträng kan också användas:
switch (strval($name)) {
...
}
Båda dessa metoder säkerställer att värdet är av samma typ som värdet i case
.
Undvik switch
Att använda ett if
uttalande kommer att ge oss kontroll över hur jämförelsen görs, vilket gör att vi kan använda strikta jämförelseoperatörer :
if ($name === "input 1") {
$mode = "output_1";
} elseif ($name === "input 2") {
$mode = "output_2";
} else {
$mode = "unknown";
}
Strikt maskinskrivning
Sedan PHP 7.0 kan vissa av de skadliga effekterna av typjuggling mildras med strikt typning . Genom att inkludera detta declare
uttalande som den första raden i filen, kommer PHP driva parameter typdeklarationer och retur typdeklarationer genom att kasta TypeError
undantag.
declare(strict_types=1);
Till exempel kommer den här koden med parametertypdefinitioner att kasta ett fångbart undantag från typen TypeError
vid körning:
<?php
declare(strict_types=1);
function sum(int $a, int $b) {
return $a + $b;
}
echo sum("1", 2);
På samma sätt använder denna kod en deklaration av returtyp; det kommer också att kasta ett undantag om det försöker att returnera något annat än ett heltal:
<?php
declare(strict_types=1);
function returner($a): int {
return $a;
}
returner("this is a string");