PHP
Kuglarstwo typów i nieskomplikowane problemy z porównaniem
Szukaj…
Co to jest żonglerka typu?
PHP to luźno napisany język. Oznacza to, że domyślnie nie wymaga operandów w wyrażeniu tego samego (lub zgodnego) typu. Na przykład możesz dodać liczbę do łańcucha i oczekiwać, że zadziała.
var_dump ("This is example number " . 1);
Dane wyjściowe będą:
string (24) „To jest przykładowy numer 1”
PHP dokonuje tego poprzez automatyczne rzutowanie niekompatybilnych typów zmiennych na typy, które pozwalają na wykonanie żądanej operacji. W powyższym przypadku rzutuje całkowitą literał 1 na ciąg znaków, co oznacza, że może być konkatenowany z poprzednim ciągiem literału. Jest to określane jako żonglerka typu. Jest to bardzo potężna funkcja PHP, ale jest to również funkcja, która może prowadzić do ciągnięcia włosów, jeśli nie jesteś tego świadomy, a nawet może prowadzić do problemów z bezpieczeństwem.
Rozważ następujące:
if (1 == $variable) {
// do something
}
Wygląda na to, że programista sprawdza, czy zmienna ma wartość 1. Ale co się stanie, jeśli zmienna $ ma zamiast tego wartość „półtora”? Odpowiedź może cię zaskoczyć.
$variable = "1 and a half";
var_dump (1 == $variable);
Wynik to:
bool (prawda)
Dlaczego tak się stało? To dlatego, że PHP zdało sobie sprawę, że ciąg „1 i pół” nie jest liczbą całkowitą, ale musi być, aby porównać go z liczbą całkowitą 1. Zamiast zawieść, PHP inicjuje żonglerkę typu i próbuje przekonwertować zmienną na zmienną liczba całkowita. Robi to, biorąc wszystkie znaki na początku ciągu, które można rzutować na liczby całkowite, i rzutować je. Zatrzymuje się, gdy tylko napotka postać, której nie można traktować jako liczby. Dlatego „1 i pół” jest rzucane na liczbę całkowitą 1.
To prawda, że jest to bardzo wymyślny przykład, ale służy do zademonstrowania problemu. Następne kilka przykładów obejmie niektóre przypadki, w których napotkałem błędy spowodowane przez żonglerkę typu, która zdarzyła się w prawdziwym oprogramowaniu.
Odczytywanie z pliku
Czytając z pliku, chcemy wiedzieć, kiedy osiągnęliśmy koniec tego pliku. Wiedząc, że fgets()
zwraca false na końcu pliku, możemy użyć tego jako warunku dla pętli. Jeśli jednak dane zwrócone z ostatniego odczytu zdarzają się jako wartość logiczna false
, może to spowodować przedwczesne zakończenie naszej pętli odczytu pliku.
$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);
Jeśli plik odczytywany zawiera pustej linii, while
pętla zostanie zakończona w tym punkcie, ponieważ puste ocenia ciąg jako logiczną false
.
Zamiast tego możemy jawnie sprawdzić wartość logiczną false
, używając ścisłych operatorów równości :
while (($data = fgets($handle)) !== false) {
echo ("Current file line is $data\n");
}
Zauważ, że jest to wymyślony przykład; w prawdziwym życiu użylibyśmy następującej pętli:
while (!feof($handle)) {
$data = fgets($handle);
echo ("Current file line is $data\n");
}
Lub zastąp całość:
$filedata = file("/path/to/my/file");
foreach ($filedata as $data) {
echo ("Current file line is $data\n");
}
Przełącz niespodzianki
Instrukcje Switch używają nieścisłego porównania w celu ustalenia zgodności. Może to prowadzić do nieprzyjemnych niespodzianek . Weźmy na przykład następujące zdanie:
switch ($name) {
case 'input 1':
$mode = 'output_1';
break;
case 'input 2':
$mode = 'output_2';
break;
default:
$mode = 'unknown';
break;
}
Jest to bardzo prosta instrukcja i działa zgodnie z oczekiwaniami, gdy $name
jest łańcuchem, ale w przeciwnym razie może powodować problemy. Na przykład, jeśli $name
jest liczbą całkowitą 0
, wówczas podczas porównania nastąpi żonglerka typu. Jednak jest to dosłowna wartość w instrukcji case, która jest żonglowana, a nie warunek w instrukcji switch. Ciąg "input 1"
jest konwertowany na liczbę całkowitą 0
która odpowiada wartości wejściowej liczby całkowitej 0
. Wynik jest taki, że jeśli podasz wartość całkowitą 0
, pierwszy przypadek jest zawsze wykonywany.
Istnieje kilka rozwiązań tego problemu:
Jawne odlewanie
Wartość może być rzutem typu na ciąg przed porównaniem:
switch ((string)$name) {
...
}
Lub można również użyć funkcji znanej z zwracania łańcucha:
switch (strval($name)) {
...
}
Obie te metody zapewniają, że wartość jest tego samego typu co wartość w instrukcjach case
.
Unikaj switch
Użycie instrukcji if
zapewni nam kontrolę nad sposobem przeprowadzania porównania, umożliwiając nam stosowanie ścisłych operatorów porównania :
if ($name === "input 1") {
$mode = "output_1";
} elseif ($name === "input 2") {
$mode = "output_2";
} else {
$mode = "unknown";
}
Ścisłe pisanie
Od PHP 7.0 niektóre szkodliwe efekty żonglowania typami można złagodzić za pomocą ścisłego pisania . Uwzględniając tę instrukcję declare
jako pierwszą linię pliku, PHP wymusi deklarację typu parametru i zwróci deklarację typu, TypeError
wyjątek TypeError
.
declare(strict_types=1);
Na przykład ten kod, używając definicji typu parametru, wyrzuci możliwy do złapania wyjątek typu TypeError
po uruchomieniu:
<?php
declare(strict_types=1);
function sum(int $a, int $b) {
return $a + $b;
}
echo sum("1", 2);
Podobnie, ten kod używa deklaracji typu zwrotu; wyrzuci także wyjątek, jeśli spróbuje zwrócić coś innego niż liczba całkowita:
<?php
declare(strict_types=1);
function returner($a): int {
return $a;
}
returner("this is a string");