PHP: Its magic! Warum bestimmte Zeichen in Formularnamen verboten sind.

Lukas [php, Tricks und Tools]

Register Globals ist eine etwas merkwürdige Angelegenheit. Man kann mit dieser Option alle Variablen, die via POST oder GET geschickt werden automatisch in lokal verwendbare Variablen speichern. Ein URL Aufruf von index.php?name=wert hat zur Folge, dass automatisch $name im PHP Script verwendet werden kann. Diese Option hat schon einige Sicherheitslöcher verursacht. An dieser Stelle ist anzumerken, dass nicht die Option an und für sich das Sicherheitsproblem ist, sondern der fahrlässige Umgang der Programmierer damit. Abgesehen davon ist Register Globals seit 5.3 deprecated. php.net meint dazu: "Sich auf dieses Feature zu verlassen ist in keiner Weise empfehlenswert." [1] . So sei es!

Die Sache mit den Variablennamen

Variablennamen müssen einge Bedinungen erfüllen. So darf der Name zum Beispiel kein . keine [ ] und auch keine Leerzeichen enthalten. Logisch, sind das doch alles Operatoren, die Variablen zusammenhängen, für arrays oder sonstwas verwendet werden. Kann ich Variablen so benennen?

$var.name = "test1";
$var name = "test2";
$var[name = "test3";

Nein, natürlich nicht. Ich kann aber Formulare so bennen:

<input name="Adresse.PLZ" type="text">
<input name="Mein Name" type="text">
<input name="Adressen[PLZ]" type="text">

Das Problem ist nun. Beim Konvertieren in die $_POST Variable werden die Sonderzeichen von PHP ersetzt, mit dem Verweis auf die Register Globals [2] . Auch wenn die Option deaktiviert ist, werden Punkt, Klammer und Leerzeichen durch einen _ ersetzt. It's magic!

Die Option alle _ in Punkte zurück zu wandeln ist schlecht. Was wenn mal wirklich ein _ verwendet wird. Der geht dann auch wieder verloren.

Variante php://input

Via php:// lassen sich diverse I/O Streams ansprechen [3] . der input ist ein readonly Stream, der die Raw Daten des Request Body enthält. Dort ist noch gar nichts ersetzt. Allerdings fehlen auch praktische Umwandlungen von Formularen, die als Array definiert sind.

<input name="adressen[1][PLZ]" type="text">
<input name="adressen[1][Ort]" type="text">
 
<input name="adressen[2][PLZ]" type="text">
<input name="adressen[2][Ort]" type="text">

Wird von PHP in ein praktisches $_POST["adressen"] array mit mehreren Einträgen umgwandelt. Das macht php://input leider nicht. Wir haben also eine Funktion geschrieben, die die den Raw Input umwandelt. Das ging gut bis wir ein Formular mit einem Dateiupload verwenden wollten. Mit erneutem Verweis auf die PHP Dokumentation [3] : php://input is not available with enctype="multipart/form-data".

Es muss also eine andere Lösung geben!

Do it with PHP Style!

Die einfachsten Lösungen sind oft die besten. Und wenn PHP sich komisch verhält, wieso sollten wir das nicht genau so machen? Zu Hilfe dabei kommt uns unser hauseigenes PHP Framework namens FADAL. Es gibt eine Environment Klasse mit statischen Methoden, die den Zugriff auf die superglobalen Variablen abstrahiert. In unserem Code verwenden wir nie direkt $_POST, sondern gehen immer über Environment::getPostVar(). Genauso gibt es eine FormBase Klasse (Basisklase für alle Formular Elemente), die wiederum von der DOMObject (Basisklasse für alle HTML Elemente) erbt. Wir erweitern also unsere FormBase wie folgt:

abstract class FormBase extends DOMObject {
 
  /**
   * @param string $type
   * @param string $name
   */
  public function __construct($type, $name) {
    parent::__construct($type);
    if (strpos($name, "!") !== false) {
      throw new ArgumentException("name", _("Ungültiges Zeichen '!' in name gefunden."));
    }
    $this->setAttribute("name", str_replace(".", "!", $name));
  }
  [...]
}

Und die Environment macht dies:

public static function init() {
  foreach ($_POST as $key => $value) {
    self::$post[str_replace("!", ".", $key)] = $value;
  }
  [...]
}
 
public static function getPostVar($name, $nullReturn = null) {
  if (!isset(self::$post[$name])) return $nullReturn;
  //
  return self::$post[$name];
}

Ein ! hat in einem Formularnamen ja nun wirklich nichts verloren. Problem gelöst, PHP Style!

Dank der konsequenten Verwendung des Frameworks muss tatsächlich nur an zwei Orten eine kleine Anpassung vorgenommen werden. Auch das ist ein klein wenig magic!