PHP: Was bedeutet "Undefined variable" bzw. "Undefined array key"?

Diese drei Meldungen sind vermutlich die häufigsten in PHP überhaupt. Sie bedeuten immer dasselbe: Du greifst auf etwas zu, das es nicht gibt. Wichtiger als das Wegbekommen der Meldung ist die Frage, warum es das nicht gibt.

Die drei Varianten

Warning: Undefined variable $name
Warning: Undefined array key "port"
Warning: Undefined property: Config::$host

In PHP 8 sind das Warnings. Vor PHP 8 waren es Notices, deshalb findest du im Netz überwiegend die Schreibweise “Notice: Undefined index”. Am Verhalten hat sich nichts geändert: Der Ausdruck liefert null und dein Skript läuft weiter.

Und das ist eigentlich das Problem. Die Meldung ist nicht der Fehler, sie ist der Hinweis darauf. Der eigentliche Fehler ist, dass du gleich mit null weiterrechnest.

Was man nicht tun sollte

Der erste Reflex ist meistens einer von zwei Wegen — und beide sind falsch.

Nicht die Meldungen abschalten:

<?php
  error_reporting(E_ALL & ~E_WARNING);   // schlechte Idee
?>

Damit sind auch alle anderen Warnungen weg, und die willst du sehen.

Nicht das @ davorstellen:

<?php
  $wert = @$nichtGesetzt;
?>

Das unterdrückt nur die Ausgabe. Der Fehler selbst passiert weiterhin. Und @ ist außerdem langsam, weil PHP den Fehler trotzdem vollständig erzeugt und erst danach verwirft.

Wie wenig @ unterdrückt, sieht man, sobald ein eigener Error-Handler im Spiel ist:

@$nochNichtGesetzt      : NULL (keine Ausgabe erschienen)
eigener Handler sieht   : Undefined variable $auchNichtGesetzt
error_reporting() darin : 4437

Der Handler wird also trotz @ aufgerufen. Wenn du irgendwo set_error_handler() verwendest — und jedes Framework tut das — dann landet der unterdrückte Fehler in deinem Log, es sei denn du prüfst selbst:

<?php
  set_error_handler(function (int $errno, string $errstr) {
    if (!(error_reporting() & $errno)) {
      return false;   // war mit @ unterdrückt
    }
    // hier protokollieren
  });
?>

Beachte dabei die Zahl 4437 von oben. Vor PHP 8 setzte @ den Wert von error_reporting() auf 0, und entsprechend prüft alter Code auf error_reporting() === 0. Seit PHP 8 wird stattdessen die Menge der Fehlerklassen gesetzt, die sich ohnehin nie unterdrücken lassen — und das ergibt 4437, nicht 0. Der alte Test greift damit nicht mehr.

Was man stattdessen tut

Der ehrlichste Weg ist, die Variable zu initialisieren:

<?php
  $summe = 0;
  foreach ($werte as $wert) {
    $summe += $wert;
  }
?>

Bei Daten von außen — $_GET, $_POST, JSON, Datenbank — kannst du das nicht, weil du nicht bestimmst, was ankommt. Dafür gibt es den Null-Koaleszenz- Operator:

<?php
  $port = $konfig['port'] ?? 3306;
?>

Das ist seit PHP 7 der Standardweg und in den allermeisten Fällen richtig.

Der Fall, in dem ?? das Falsche tut

Jetzt kommt der Punkt, der selten erwähnt wird. ?? und isset() prüfen nicht “existiert der Schlüssel”, sondern “existiert er und ist nicht null”.

<?php
  $a = ['schluessel' => null];
?>
isset($a['schluessel'])            : false
array_key_exists('schluessel', $a) : true
$a['schluessel'] ?? 'ersatz'       : 'ersatz'

Der Schlüssel ist da. isset() sagt trotzdem false, und ?? liefert den Ersatzwert.

Meistens ist das genau das, was man will. Manchmal aber nicht — nämlich dann, wenn null in deinen Daten eine eigene Bedeutung hat. Etwa bei einem Datenbankfeld: “NULL” heißt dort “bewusst kein Wert”, und das ist etwas anderes als “Spalte nicht abgefragt”. Oder bei einem JSON von einer API, in dem {"rabatt": null} und ein fehlendes rabatt unterschiedliche Dinge bedeuten.

Für diese Fälle:

<?php
  if (array_key_exists('rabatt', $daten)) {
    // Feld war da, Wert kann null sein
  }
?>

array_key_exists() funktioniert nur bei Arrays, nicht bei Objekten. Für Objekt-Eigenschaften gibt es property_exists().

empty() ist noch großzügiger

empty() wird gern als Ersatz für isset() verwendet. Es prüft aber etwas anderes, nämlich zusätzlich auf “leer”:

empty(0     ) = true    isset = true
empty('0'   ) = true    isset = true
empty(''    ) = true    isset = true
empty([]    ) = true    isset = true
empty(NULL  ) = true    isset = false
empty('text') = false   isset = true

Die Zeile mit '0' ist die, die in der Praxis Schaden anrichtet. Ein Formularfeld mit dem Inhalt 0 — eine Menge, eine ID, eine Postleitzahl in einem Land das damit anfängt — gilt empty() als leer. Und Werte aus $_POST sind immer Strings.

Faustregel: empty() nur dann, wenn du wirklich “nicht gesetzt oder leer” meinst. In allen anderen Fällen isset() oder ??.

Zusammenfassung

  • Die Meldung ist der Hinweis, nicht der Fehler. Der Fehler ist das null, mit dem danach weitergerechnet wird.
  • Variablen initialisieren, wo du es kannst.
  • ?? für Daten von außen.
  • array_key_exists(), wenn null als Wert eine Bedeutung hat.
  • empty() behandelt 0 und '0' als leer — bei Formulardaten gefährlich.
  • @ unterdrückt nur die Ausgabe; ein eigener Error-Handler wird trotzdem aufgerufen.

Hinweis zu Netcup (Werbung)

Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).