PHP
Typ jonglieren und nicht strenge Vergleichsfragen
Suche…
Was ist Typ Jonglieren?
PHP ist eine locker getippte Sprache. Das bedeutet, dass Operanden in einem Ausdruck standardmäßig nicht vom selben (oder kompatiblen) Typ sein müssen. Sie können beispielsweise eine Zahl an eine Zeichenkette anhängen und erwarten, dass sie funktioniert.
var_dump ("This is example number " . 1);
Die Ausgabe wird sein:
Zeichenfolge (24) "Dies ist Beispielnummer 1"
PHP erreicht dies durch das automatische Konvertieren inkompatibler Variablentypen in Typen, die die Ausführung der angeforderten Operation ermöglichen. Im obigen Fall wird das Ganzzahlliteral 1 in eine Zeichenfolge umgewandelt, sodass es mit dem vorhergehenden Zeichenfolgenliteral verkettet werden kann. Dies wird als Typ Jonglieren bezeichnet. Dies ist eine sehr leistungsfähige Funktion von PHP, aber es ist auch eine Funktion, die Sie zu einer Menge Haare ziehen kann, wenn Sie sich dessen nicht bewusst sind, und sogar zu Sicherheitsproblemen führen.
Folgendes berücksichtigen:
if (1 == $variable) {
// do something
}
Die Absicht scheint zu sein, dass der Programmierer prüft, ob eine Variable den Wert 1 hat. Was passiert jedoch, wenn $ variable stattdessen "anderthalb" hat? Die Antwort könnte Sie überraschen.
$variable = "1 and a half";
var_dump (1 == $variable);
Das Ergebnis ist:
bool (wahr)
Warum ist das passiert? Dies liegt daran, dass PHP erkannt hat, dass die Zeichenfolge "anderthalb" keine ganze Zahl ist. Sie muss jedoch sein, um sie mit der ganzen Zahl 1 zu vergleichen ganze Zahl. Dies geschieht, indem alle Zeichen am Anfang der Zeichenfolge, die in Ganzzahl umgewandelt werden können, übernommen werden. Es stoppt, sobald es auf ein Zeichen trifft, das nicht als Zahl behandelt werden kann. Daher wird "anderthalb" in die Ganzzahl 1 umgewandelt.
Zugegeben, dies ist ein sehr erfundenes Beispiel, aber es dient dazu, das Problem zu veranschaulichen. In den nächsten Beispielen werden einige Fälle behandelt, in denen ich auf Fehler gestoßen bin, die durch das Typjonglieren in der realen Software verursacht wurden.
Lesen aus einer Datei
Beim Lesen aus einer Datei möchten wir wissen können, wann wir das Ende dieser Datei erreicht haben. Wenn fgets()
wissen, dass fgets()
am Ende der Datei false zurückgibt, verwenden wir dies möglicherweise als Bedingung für eine Schleife. Wenn die aus dem letzten Lesevorgang zurückgegebenen Daten jedoch als boolesches false
ausgewertet werden, kann dies dazu führen, dass unsere Dateilese-Schleife vorzeitig beendet wird.
$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);
Wenn die gelesene Datei eine leere Zeile enthält, wird die while
Schleife an diesem Punkt beendet, da die leere Zeichenfolge als boolean false
ausgewertet wird.
Stattdessen können wir explizit nach dem booleschen Wert false
suchen, indem Sie strikte Gleichheitsoperatoren verwenden :
while (($data = fgets($handle)) !== false) {
echo ("Current file line is $data\n");
}
Beachten Sie, dass dies ein erfundenes Beispiel ist. Im wirklichen Leben würden wir die folgende Schleife verwenden:
while (!feof($handle)) {
$data = fgets($handle);
echo ("Current file line is $data\n");
}
Oder ersetzen Sie das Ganze durch:
$filedata = file("/path/to/my/file");
foreach ($filedata as $data) {
echo ("Current file line is $data\n");
}
Überraschungen wechseln
Switch-Anweisungen verwenden einen nicht strengen Vergleich, um Übereinstimmungen zu ermitteln. Dies kann zu bösen Überraschungen führen . Betrachten Sie zum Beispiel die folgende Anweisung:
switch ($name) {
case 'input 1':
$mode = 'output_1';
break;
case 'input 2':
$mode = 'output_2';
break;
default:
$mode = 'unknown';
break;
}
Dies ist eine sehr einfache Anweisung und funktioniert wie erwartet, wenn $name
eine Zeichenfolge ist. Andernfalls können Probleme auftreten. Wenn zum Beispiel $name
eine ganze Zahl von 0
, tritt während des Vergleichs ein Typ-Jonglieren auf. Es ist jedoch der Literalwert in der case-Anweisung, der jongliert wird, nicht die Bedingung in der switch-Anweisung. Die Zeichenfolge "input 1"
wird in eine Ganzzahl 0
konvertiert, die mit dem Eingangswert von Ganzzahl 0
übereinstimmt. Wenn Sie einen Wert von Integer 0
angeben, wird der erste Fall immer ausgeführt.
Es gibt einige Lösungen für dieses Problem:
Explizites Casting
Der Wert kann typisiert in einen String vor dem Vergleich:
switch ((string)$name) {
...
}
Oder eine Funktion, von der bekannt ist, dass sie einen String zurückgibt, kann auch verwendet werden:
switch (strval($name)) {
...
}
Beide Methoden stellen sicher, dass der Wert denselben Typ hat wie der Wert in den case
Anweisungen.
vermeiden switch
Die Verwendung einer if
Anweisung gibt uns die Kontrolle darüber, wie der Vergleich durchgeführt wird, und ermöglicht die Verwendung strenger Vergleichsoperatoren :
if ($name === "input 1") {
$mode = "output_1";
} elseif ($name === "input 2") {
$mode = "output_2";
} else {
$mode = "unknown";
}
Striktes Tippen
Seit PHP 7.0 können einige der schädlichen Auswirkungen des Typjonglierens durch strikte Typisierung gemindert werden. Durch das Einschließen dieser declare
als erste Zeile der Datei erzwingt PHP die Deklaration von Parametertypen und die Deklaration von Rückgabetypen durch TypeError
einer TypeError
Ausnahme.
declare(strict_types=1);
Beispielsweise wird dieser Code bei Verwendung von Parametertypdefinitionen beim Ausführen eine abfangbare Ausnahme vom Typ TypeError
:
<?php
declare(strict_types=1);
function sum(int $a, int $b) {
return $a + $b;
}
echo sum("1", 2);
Dieser Code verwendet ebenfalls eine Rückgabetypdeklaration. Es wird auch eine Ausnahme ausgelöst, wenn versucht wird, etwas anderes als eine Ganzzahl zurückzugeben:
<?php
declare(strict_types=1);
function returner($a): int {
return $a;
}
returner("this is a string");