Programmieren mit PHP

Aus Synology Wiki
Wechseln zu: Navigation, Suche

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
<?php
//globale Variable string mit dem Wert Hallo Welt
$string = 'Hallo Welt<br />';
function foo(){
  //lokale Variable string mit dem Wert Tschüss Welt
  $string = 'Tschüss Welt<br />';
  echo $string;
}
function bar(){
  //nochmal eine lokale Variable string mit Wert Guten Morgen Welt
  $string = 'Guten Morgen Welt<br />';
  return $string;
}
echo $string;
foo();
echo $string;
echo bar();
echo $string;
?>
''Ausgabe:''
Hallo Welt
Tschüss Welt
Hallo Welt
Guten Morgen Welt
Hallo Welt

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

Fehler selber finden

Einfache Fehler

Etwas vom wichtigsten beim Schreiben von eigenem Code ist es Fehler selber zu finden. Klingt einfach, ist aber viel schwieriger ;-) Einfache Fehler hat man sehr schnell selber gefunden, nämlich jene, die den PHP Parser zum Abbruch zwingen. Es sind dies z.B. die berühmten vergessenen Semikolons am Ende einer Anweisung oder das Unterschlagen einer Klammer. Solche Fehler hat man wirklich meist sehr schnell gefunden v.a. wenn man sich an zwei einfache Regeln hält (und die Fehler damit gar nicht macht ;-)

* Jede öffnende Klammer gleich wieder schliessen
* Am Ende jeder Anweisung ein ;

Der zweite Punkt ist wirklich einfach. Der erste ist vielleicht etwas gewöhnungsbedürftig, erspart einem aber u.U. ziemlich langes suchen nach einem Klammerfehler. Wenn ihr eine Schleife schreibt oder eine Funktion dann erstellt erst den "Körper" des Codes und füllt ihn erst dann mit Inhalt. Also z.B.

<?php
while() {

}
function foo() {

}
?>

Wenn ihr dann damit beginnt den Code auszubauen und Euch an die zwei obigen Regeln hält dann habt ihr keine solchen Fehler.

Gemeine Fehler

Wenn ihr Eurer error_reporting() wie oben angegeben einstellt dann findet ihr u.A. solche gemeinen Fehler (es sind übrigens 2 Fehler in diesem Code):

<?php
error_reporting(E_ALL);
$link1 = 0;
$link = 0;
/*
 * extrem viel anderer Code
 * noch viel mehr
 * und noch mehr
 */
 if($linkl === 0 && $Iink === 0){
   echo 'Code funzt';
 }else{
   echo 'Code ist kapputt';
 }
?>

Von diesen beiden Fehlern werdet ihr ohne entsprechendes Error Reporting bestenfalls einen von Auge finden. Vorallem wenn der Quellcode nicht wie oben einfach so kurz ist sondern ein paar hundert Zeilen lang ist, stösst ihr bei "händischer" Fehlersuche schnell an die Grenzen.

Ein entsprechendes Error Reporting würde euch mit einer solchen Meldung beglücken:

Notice: Undefined variable: linkl in /volume1/web/tobisworld/codes/html/Tipps/test.html on line 10
Code ist kapputt

Nachdem ihr dann den ersten Fehler behoben habt wird euch der PHP Parser beim nächsten Aufruf noch den zweiten Fehler melden, der zugegeben auch sehr einfach von Auge hätte gefunden werden können

Notice: Undefined variable: Iink in /volume1/web/tobisworld/codes/html/Tipps/test.html on line 10
Code ist kapputt

Fehler mittels Error Reporting finden

Es ist sehr wichtig für den Programmierer, dass er weiss wie man Fehler im eigenen Code eingrenzen und dann finden kann. Dazu muss das error_reporting entsprechend gesetzt sein und dann wird der Parser jede potentiell kritische Stelle mit einer Warning oder Notice quittieren (mehr zum Thema register_globals)

Fehler durch Debugging finden

Das war jetzt der einfach Teil. Viel schwieriger zu finden sind Fehler bei denen eben keine Fehlermeldung gegeben wird. Fehler also bei denen für den PHP Parser alles okay ist. Solche logischen Fehler sind bis zu einem gewissen Grad von vorherein vermeidbar. Bevor ihr in die Tasten haut, schreibt einfach mal auf ein Blatt Papier was ihr wollt und wie ihr das machen könntet. Mach Euch also Gedanken über Euren Code bevor ihr zu schreiben beginnt. Zeichnet einfach mal die Struktur auf

Was dann an logischen Fehlern übrig bleibt muss dann mittels Debugging ermittelt werden. Dazu muss man den Wert JEDER verwendeten Variabeln prüfen. Dafür stellt PHP sehr gute Werkzeuge zur Verfügung. Folgende Liste ist sicherlich nich abschliessend

<?php
echo();
var_dump();
print_r();
debug_backtrace();
mysql_error();
?>