Programmieren mit PHP

Aus

Meine Tipps für PHP

Falls jemand über diese Seite stolpert und mit dem Gedanken spielt PHP zu lernen, dann hier die für mich wichtigsten Tipps:

Register Globals ausschalten

Wenn immer ihr einen Server mit PHP aufsetzt oder Euch ein Hosting für Eure Webpage sucht, achtet darauf, dass register globals auf off sind. Ansonsten bittet den Hoster dies zu tun oder, falls er das nicht tut, verlasst ihn schnell. Falls ihr selber einen Server aufsetzt oder einen Rootserver und damit Zugriff auf die php.ini hat, dann könnt ihr die entsprechende Änderung auch selber durchführen. Öffnet dazu die php.ini. Wenn ihr nicht wisst wo diese liegt, dann legt eine PHP Datei mit folgendem Inhalt an:

<?php
phpinfo();
//irgendwo steht das etwas wie
//Configuration File (php.ini) Path /usr/syno/etc/php.ini
//weiter unten steht etwa
//register_globals Off Off
?>

In dieser angegebenen Datei sucht ihr nach register globals, ändert den Wert auf off und speichert die Datei wieder (bei der Ausgabe von phpinfo() stehen jeweils zweimal die Zustände der Einstellung. Dies kommt daher, dass der erste Wert der Local Einstellung entspricht, der zweite hingegen der Master Vorgabe)

Gefahr von Register Globals

<?php
if($password == 'totalGeheim'){
  $login = true;
  //Code
}
//viel mehr Code
if($login == true){
  echo 'Total geheimer Text';
}
?>

Diesen Code kann man bei register_globlas ON gleich auf zweit Arten umgehen. Entweder man übergibt dem Script eine POST oder GET Variable mit dem Namen password und dem Wert totalGeheim oder man gibt schlicht und eirgreifend einen Wert mit Namen login und Wert 1. Dieser Code hat gleich mehrere Don't dos drin. Zum ersten ist es einfach nötig - egal ob register_globals ON oder OFF - Variabeln zu initialisieren und zum anderen muss man Variabeln v.a. im Umfeld von register_globals ON neben dem Wert auch auf den Typ prüfen. Eine Initialisierung von login mit false und password mit NULL würde jegliche Versuche eines Angreifers via GET oder POST unmöglich machen.

Ausserdem könnte man das Problem entschärfen indem man den === Verlgeichoperator verwenden würde. Da PHP Variabeln, die via GET oder POST hereinkamen immer als String sieht. Das führt dazu, dass der Text nur angezeigt wird, wenn man das Passwort kennt und $login auf den Boolean Wert true gesetzt wurde.

Beim Programmieren ist es sehr wichtig sich nicht auf eine bestimmte Servereinstellung zu verlassen. Sonst kann das dazu führen, dass der Code nicht mehr portabel ist. Vorallem als Entwickler sollte man beachten, dass der Code sowohl in Umgebungen mit register_globals ON als auch OFF laufen kann.

Eigenheiten und "Macken" von PHP

Auto Type Cast und Typisierung

Und mit der Zeit gewöhnt man sich an die Eigenheiten von PHP, die es eben von echten Programmiersprachen unterscheidet. Typische solche "Eigenheiten" sind zum Beispiel das automatische Type Casting oder die nicht strikte Typisierung (strikt oder dynamisch) von Variabeln. In jeder echten Programmiersprache muss man BEVOR man eine Variable benutzt diese deklarieren d.h. bekanntmachen welchen Datentyp die Variable hat (z.B. String, Integer, Float ...).

Folgender Code gäbe in fast jeder anderen Sprache einen schweren Fehler:

<?php
$i = 1;
?>

Dem Parser von PHP ist es egal wenn eine Variable ohne Deklaration verwendet wird.

Der Parser denkt sich: Hhm eine unbekannte Variable i hhhm und der Programmierer will ihr einen Wert von 1 zuweisen. Hhm mal überlegen was könnte der beste Datentyp für 1 sein ????? (Grübel und studier) Ach ja ich hab's ich deklariere i einfach mit dem Datentyp Integer für ganze Zahlen und initialisiere diese mit 1"

Wohingegen ein Compiler denkt: Also echt ich kann doch nicht einer Variable deren Typ ich nicht kenne irgendeinen Wert zuweisen, du Spinner. Wo kämen wir denn da hin ???? Ich muss doch für die Variable Speicher reservieren. Wie denn ohne den Typ zu kennen ?? Nein wirklich so einen Quellcode versuche ich gar nicht erst zu kompilieren. Fehler und tschüss"

Oben habe ich als Beispiel für Eigenheiten von PHP neben dem Type Casting auch die nicht strikte Typisierung erwähnt. In einer richtigen Programmiersprache kann das Ergebnis einer Division zweier Zahlen entweder Integer ODER Float (Fliesskommmazahl) sein.

<?php
//Variable als Integer
$h = 0;

for($i=1;$i<=30;$i++){
  $h = 30/$i;
  var_dump($h);
}
?>

var_dump() ist eine PHP Funktion, welche den Typ und den Inhalt einer Variable ausgibt. Anscheinend ist jede dritte Zahl vom Typ Integer der Rest ist Float !

int(30)
int(15)
int(10)
float(7.5)
int(6)
int(5)
float(4.2857142857143)
float(3.75)
float(3.3333333333333)
int(3)
float(2.7272727272727)
float(2.5)
float(2.3076923076923)
float(2.1428571428571)
int(2)
float(1.875)
float(1.7647058823529)
float(1.6666666666667)
float(1.5789473684211)
float(1.5)
float(1.4285714285714)
float(1.3636363636364)
float(1.304347826087)
float(1.25)
float(1.2)
float(1.1538461538462)
float(1.1111111111111)
float(1.0714285714286)
float(1.0344827586207)
int(1)

2/3 aller Zuweisungen der Division würden in echten Programmiersprachen Fehler produzieren. Dies da 2 von 3 Resultaten nicht ganzzahlig sind, Kommazahlen (Float) also. Ein Typenwechsel ist in einer echten Programmiersprache nicht möglich. PHP castet den Variabelntyp bei 2 von 3 Divisionen automatisch auf Float und dann wieder für einmal zurück auf Integer. Das nennt sich bei PHP Autocast und ist eigentlich etwas sehr nettes. In Gewissen Situationen kann sich dieser Autocastmechanismus jedoch auch sehr nervig sein.

Vergleichsoperatoren

<?php
$heuhaufen = 'Ich bin ein String';
$nadel = 'Ich';
$suche = stripos($heuhaufen,$nadel);
if($suche == false) {
  die('Nix gefunden');
}else{
  die('Gefunden an Position '.$suche);
}
?>

Obiger Code wird NIEMALS in den else-Zweig hineinkommen. Die Problematik liegt hier wiederum beim Autocast von PHP, aber auch beim Vergleichsoperator == von PHP. Ein ==-Operator vergleicht nur den Wert der beiden Variabeln. Die Funktion gibt die Position von $nadel zurück. Und diese Position im String ist eben 0. Damit PHP den Integer 0 mit dem Boolean false vergleichen kann muss ein Type Casting stattfinden. Dabei wird ein Boolean Wert false zu Integer Wert 0 gecastet und die Bedingung ist damit erfüllt. Andererseits würde ein Boolean true als Integer 1 interpretiert. Damit obiger Code korrekt laufen kann stellt PHP den === Operator zur Verfügung. Dieser vergleicht neben dem Wert der Variabeln auch ihren Typ. Nur wenn Typ und Wert identisch sind wird die Bedingung erfüllt.

Initialisieren von Variabeln

Obwohl es in PHP bei solchen Aktionen keine Fehler gibt, sollte man gleich von Anfang an allen Variabeln einen Wert zuweisen (z.B. alle im Script verwendeten Variabeln mit einem Initialwert belegen). Es ist einfach sauberer als wild drauf los Werte zuzuweisen ! Ausserdem "killt" man damit das leidige register_globals-Problem von PHP , das bei entsprechender Serverkonfigurationen ein echtes Sicherheitsrisiko darstellt.

error_reporting(E_ALL|E_STRICT)

Die weiteren Fehler können durch ein entsprechendes Setzen des Error Reportings zur Laufzeit der Scripts abgefangen werden. Es sind dies nicht Parser Fehler, sondern die sogenannten Warnings, die neu dazukommen. Anhand derer kann man auf mögliche Fehlerquellen im Code schliessen.

<?php
//erste Anweisung kommt während der Entwicklung
//UND NUR WÄHREND DER ENTWICKLUNG
//als erste Zeile eines JEDEN PHP Scripts
error_reporting(E_ALL | E_STRICT);
ini_set('display_errors','1');
?>

Namespaces - Global lokal wie bitte?

Bei Programmiersprachen gibt es sogenannte Namespaces. Diese legen den Gültigkeitsbereich von Variablen fest. Dabei wird zwischen globalem und lokalen Namespaces unterschieden. Variabeln im globalen Bereich haben eine scriptweite Gültigkeit d.h. man kann in jedem Kontext darauf zugreifen. Wohingegen lokale Variabeln auf den Kontext ihrer Funktion oder Klasse beschränkt sind.

lokale Namespaces

Als lokale Namesspaces werden z.B. die Namensbereiche von Funktionen und Objekten bezeichnet. Jede Funktion hat ihren eigenen Namespace und die Variablen darin gelten nur im Kontext der jeweiligen Funktion. Wenn das nicht so wäre, dann würden uns bald die Variablennamen ausgehen oder wir müssten irrwitzig lange Namen verwenden.

<?php
class foo{
  public $var = 0;
  public function bar($zahl=-1){
    if($zahl >= 0){
      $this->var = $zahl;
      var_dump($this->var);
    }else{
      var_dump($this->var);
    }
  }
}
/**
 * Durch die Initialisierung der Klasse mittels new
 * hat PHP eine Referenz auf den lokalen Namespace
 * der verwendet wird
 */
$bar = new foo;
$bar->bar(); //Ausgabe 0
//"direkter" Zugriff auf die Klassenvariabel
$bar->var = 1;
$bar->bar(); //Ausgabe 1
//"Zugriff" via Argument einer Klassenmethode
$bar->bar(15); //Ausgabe 15
?>

Ausgabe: int(0) int(1) int(15)

Globaler Namespace

Der Globale Namensbereich hat eine Scriptweite Gültigkeit. Auf solche Variablen kann auch aus den lokalen Namespaces zugegriffen werden. Es ist zwar ein bisschen verpöhnt aus den Funktionen und Objekten auf globale Variablen zuzugreifen und diese zu verändern, aber es vereinfacht die Programmierung doch ein wenig und wenn man aufpasst, dann überschreibt man auch nichts was man noch irgendwo braucht.

<?php
//globale Variable string mit dem Wert Hallo Welt
$string = 'Hallo Welt<br />';
function foo(){
  //globale Variable string mit dem Wert Tschüss Welt
  global $string;
  $string = 'Tschüss Welt<br />';
}
function bar(){
  //nochmal eine globale Variable string mit Wert Guten Morgen Welt
  $GLOBALS['string'] = 'Guten Morgen Welt<br />';
}
echo $string;
foo();
echo $string;
bar();
echo $string;
?>
''Ausgabe:''
Hallo Welt
Tschüss Welt
Guten Morgen Welt

Wenn wir in einer Funktion eine $variable aufrufen, dann wird PHP immer im lokalen Raum dieser Funktion suchen und einen Fehler werfen, wenn sie nicht vorhanden ist. Der PHP Parser käme nie von sich aus auf die Idee, dass der Programmierer eine globale Variable ansprechen will.

Der umgekehrte Weg, vom globalen auf lokale Namespaces, ist eigentlich nicht möglich. Denn es gibt x-beliebig viele lokale Namespaces und PHP kann nicht wissen welchen man ansprechen will. Ich habe geschrieben eigentlich weil man mit Klassen und deren Objekten durchaus auf den lokalen Namespace des Objekts zugreifen kann.

<?php
class foo{
  public $var = 0;
  public function bar($zahl=-1){
    if($zahl >= 0){
      $this->var = $zahl;
      var_dump($this->var);
    }else{
      var_dump($this->var);
    }
  }
}
/**
 * Durch die Initialisierung der Klasse mittels new
 * hat PHP eine Referenz auf den lokalen Namespace
 * der verwendet wird
 */
$bar = new foo;
$bar->bar(); //Ausgabe 0
//"direkter" Zugriff auf die Klassenvariabel
$bar->var = 1;
$bar->bar(); //Ausgabe 1
//"Zugriff" via Argument einer Klassenmethode
$bar->bar(15); //Ausgabe 15
?>
</php>

Es gibt also beliebig viele lokale Namenräume, aber nur einen globalen. Das Problem mit register_globals auf ON ist, dass plötzlich Variabeln im globalen Raum existieren, mit denen man nicht gerechnet hat. Das Problem dabei ist nicht die Einstellung an sich sondern die "schlampige" Programmierung, die PHP erlaubt. Hier noch etwas zur Gefahr von globals on