mitmario.dev

Vergleichen

PHP Sandbox 4 Min Lesezeit 4 BeispieleLektion 6 von 7

Zwei Werte vergleichen klingt nach der einfachsten Sache der Welt. In PHP ist es die Stelle, an der die meisten Anfängerfehler entstehen, und es ist die einzige Eigenheit der Sprache, die es bis in Sicherheitsberichte geschafft hat.

Der Grund ist eine Entscheidung aus den neunziger Jahren: PHP wollte hilfsbereit sein und Werte, die nur ähnlich aussehen, als gleich durchgehen lassen.

Zwei Gleichheitszeichen, oder drei

Gleich, oder nur ähnlich genug
<?php

$eingabe = "5";
$erwartet = 5;

echo 'mit ==  : ', var_export($eingabe == $erwartet, true), "\n";
echo 'mit === : ', var_export($eingabe === $erwartet, true), "\n";

var_dump($eingabe);
var_dump($erwartet);

== fragt: Sind diese Werte gleich, wenn ich sie vorher aneinander angleiche? === fragt: Sind sie dasselbe, Wert und Sorte?

Im Beispiel steht in $eingabe der Text "5" und in $erwartet die Zahl 5. Mit == gilt das als gleich, mit === nicht. Die beiden var_dump darunter zeigen, warum: string(1) "5" gegen int(5), das sind zwei verschiedene Dinge mit demselben Aussehen. Ohne Datei geht dieselbe Frage im Terminal: php -r 'var_dump("5" == 5, "5" === 5);' antwortet mit bool(true) und bool(false).

Das ist übrigens der Grund, warum du in diesem Kurs schon so oft var_dump gesehen hast. Ein echo zeigt dir bei beiden Werten eine 5 und verschweigt genau die Information, um die es hier geht.

Neu in diesem Beispiel ist var_export. Es zeigt denselben Wert wie var_dump, gibt ihn aber zurück, statt ihn auszugeben, wenn du true als zweites Argument mitgibst. Aus einem Wahrheitswert wird damit der Text true oder false, und den kannst du mitten in eine Zeile setzen. Dasselbe Verhältnis wie bei printf und sprintf aus Lektion 3.4: das eine schreibt, das andere liefert.

Was aufgeräumt wurde, und was geblieben ist

Was PHP 8 aufgeräumt hat, und was geblieben ist
<?php

echo '0 == "abc"      ergibt ', var_export(0 == "abc", true), "\n";
echo '"1" == "01"     ergibt ', var_export("1" == "01", true), "\n";
echo '"10" == "1e1"   ergibt ', var_export("10" == "1e1", true), "\n";
echo '"1" == " 1"     ergibt ', var_export("1" == " 1", true), "\n";
echo 'null == false   ergibt ', var_export(null == false, true), "\n";
echo '"" == null      ergibt ', var_export("" == null, true), "\n";
echo '0.1 + 0.2 == 0.3 ergibt ', var_export(0.1 + 0.2 == 0.3, true), "\n";

Bis PHP 7 galt 0 == "abc" als wahr. PHP machte aus dem Text eine Zahl, was bei "abc" eine Null ergab, und verglich dann zwei Nullen. Damit war jede Prüfung „ist diese Nummer null” für beliebigen Text offen. Seit PHP 8 ist dieser Fall erledigt: Vergleicht man eine Zahl mit Text, der keine Zahl ist, wird stattdessen die Zahl zu Text gemacht, und "0" ist nun einmal nicht "abc".

Geblieben ist alles, was sich in beide Richtungen als Zahl lesen lässt, und das sind mehr Fälle, als man denkt. "1" und "01" gelten als gleich. "10" und "1e1" ebenfalls, denn 1e1 ist die wissenschaftliche Schreibweise für zehn. Und "1" ist gleich " 1" mit führendem Leerzeichen, was bei einer Eingabe aus einem Formular schneller vorkommt, als dir lieb ist.

Dazu die beiden Fälle mit null, die aus Lektion 2.5 hinüberreichen: null == false und "" == null sind beide wahr. Mit === ist keiner davon wahr, und das ist auch die ehrlichere Antwort, denn „hier ist nichts” und „hier ist ein leerer Text” sind zwei verschiedene Zustände.

Die letzte Zeile schließt einen Faden aus Lektion 2.3. 0.1 + 0.2 == 0.3 ist falsch, obwohl echo bei beiden 0.3 anzeigt. Deshalb der Satz von damals: Prüf zwei gebrochene Zahlen nie auf Gleichheit. Bei Geld rechnest du in Cent, und dann vergleichst du ganze Zahlen, bei denen die Frage gar nicht auftaucht.

Warum daraus eine Lücke wird

Ein Vergleich, der zu viel durchlässt
<?php

$gespeichert = "0e462097431906509019562988736854";
$geraten = "0e830400451993494058024219903391";

echo "Zwei voellig verschiedene Zeichenketten.\n";
echo "Mit ==  gleich? ", var_export($gespeichert == $geraten, true), "\n";
echo "Mit === gleich? ", var_export($gespeichert === $geraten, true), "\n";

Sieh dir die beiden Zeichenketten im Beispiel an. Sie haben 32 Stellen, und nach dem 0e am Anfang sind es zwei verschiedene Ziffernfolgen. Trotzdem sagt ==, dass sie gleich sind.

Der Grund ist die Regel von oben, und er steckt genau in diesen zwei Zeichen: Beide lassen sich als Zahl lesen, 0e462... heißt „null mal zehn hoch irgendwas”, und das ist null. Also vergleicht PHP zwei Nullen.

So etwas steht nicht nur in Lehrbüchern. Solche Zeichenketten kommen aus Hash-Funktionen, und wer einen gespeicherten Wert mit == gegen eine Eingabe prüft, hat unter Umständen eine Anmeldung gebaut, die sich mit dem falschen Passwort öffnen lässt. Man muss dafür Glück haben und die richtige Sorte Eingabe finden, aber genau danach sucht jemand, der es darauf anlegt.

Die Regel für diesen Kurs, und für alles, was du danach schreibst: Nimm ===. Wenn du == brauchst, weißt du in dem Moment genau warum, und dann schreibst du einen Kommentar dazu. In Abschnitt 15 kommt für Passwörter noch ein eigenes Werkzeug dazu, das auch die Vergleichsdauer gleich hält. Bis dahin sind drei Gleichheitszeichen die Antwort.

Für ungleich gilt dasselbe in Grün: != gleicht an, !== nicht. Nimm !==.

Zwei Werkzeuge nebenbei

Zwei Werkzeuge nebenbei
<?php

echo "3 <=> 5 ergibt ", 3 <=> 5, "\n";
echo "5 <=> 3 ergibt ", 5 <=> 3, "\n";
echo "3 <=> 3 ergibt ", 3 <=> 3, "\n";

echo "Vorgabe statt Warnung: ", $gibtEsNicht ?? "unbekannt", "\n";

$leer = "";
echo "Ein leerer Text ist gesetzt: [", $leer ?? "unbekannt", "]\n";

<=> vergleicht zwei Werte und liefert eine Zahl statt eines Wahrheitswerts: -1, wenn der linke kleiner ist, 1, wenn er größer ist, 0 bei Gleichstand. Alleine wirkt das nutzlos, und in Abschnitt 5 wird daraus die Zeile, mit der du eine Liste nach eigenen Regeln sortierst.

?? hat mit Vergleichen streng genommen nichts zu tun, gehört aber hierher, weil es dieselbe Sorte Verwirrung auflöst. $wert ?? "unbekannt" heißt: nimm $wert, außer es gibt ihn nicht oder er ist null, dann nimm das andere. Anders als ein echo auf eine unbekannte Variable erzeugt das keine Warnung, und das macht es zum Standardgriff für alles, was von außen kommt.

Wichtig dabei: ?? sieht nur auf „nicht vorhanden oder null”. Ein leerer Text kommt durch, wie die letzte Zeile im Beispiel zeigt. Wer auch den abfangen will, braucht die Werkzeuge aus Abschnitt 4.

Zum Mitnehmen

Zwei Gleichheitszeichen fragen, ob zwei Werte ähnlich genug sind. Drei fragen, ob sie dasselbe sind. Der Unterschied hat schon echte Anmeldungen geöffnet, und deshalb hat er hier eine eigene Lektion.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.