PHP

Nun, ich werde eine neue Kategorie einfuegen, diese heisst PHP. Gut, erstmal nix Besonderes, sowas gibts wie Sand am Meer, aber ich will auch dran teilhaben. Deshalb erstmal alle meine Links die mit PHP zu tun haben UND gleich noch was zum Lesen

  • http://akrabat.com/category/php/ – Autor von ‚Zend Framework in Action
  • http://www.zendframeworkinaction.com/ – Buch vom obigen Autor 🙂
  • http://phpimpact.wordpress.com/category/php/ – Werkelt tuechtig am Zend Framework rum.
  • http://www.ralfeggert.de/kategorie/php/ – macht dasselbe wie Federico Cargnelutti (phpimpact)
  • http://blog.libssh2.org/index.php?/categories/1-PHP – die Sara Golemon ist eine der PHP Entwicklerinnen und hat diverses zu PECL dazugefuegt. Ausserdem hat sie ‚Extending and Embedding PHP‚ geschrieben.
  • http://www.webmonkey.com/blog, nicht unbedingt nur PHP, aber trotzdem immer gute Artikel
  • http://devzone.zend.com/public/view, Zend .. nunja, was soll ich zu Zend sagen 🙂

Und nun was zum Lesen:

Original und Quelle: http://blog.libssh2.org/index.php?/archives/51-Youre-being-lied-to..html. Ich habs einfach mal grob zusammengefasst und uebersetzt:

Ihr wurdet angelogen

In PHP 5 gibt es die Aussage: ‚Objekte werden per Referenz kopiert‘. Waehrend in PHP 4 noch meist alles als kopiert wurde, soll in PHP 5 also nur ein Zeiger auf das Objekt kopiert werden. Nunja, das ist so nicht ganz richtig, das ist eher eine Luege.

Ok, um fair zu bleiben, eine unschuldige Luege, da Objekte sich so verhalten wie Referenzen. Aber es sind einfach keine. Einen Duck-Test wuerde es zwar bestehen, aber auch nur den. Hier ein kleines Beispiel:

<?php
$a = new stdClass;
$b = $a;

$a->foo = ‚bar‘;
var_dump($b);
/* Hier sieht man das $a und $b tatsaechlich auf dasselbe Objekt zeigen.
* Das ist das ’sieht aus wie eine Referenz‘ Verhalten von PHP 5
*/

$a = ‚baz‘;
var_dump($b);
/* Hier kann man nun beobachten das $b immer noch auf das Objekt zeigt.
* Waere es eine tatsaechliche Referenz zu $a
* haette es sich auch in einen einfachen String verwandelt.
*/
?>

Was geht hier vor? Nun, am einfachsten ist dies zu erklaeren, wenn man erklaert wie die zugrundeliegende Struktur von Objekten in PHP 5 ist. In PHP 5 wird eine Variable die ein Objekt enthaelt mit der Instanz des Objektes ueber eine simple Nummer verknuepft. Wenn nun eine Aktion auf dem Objekt ausgefuehrt wird, wird diese Nummer genutzt um mit einer Lookup-Tabelle die tatsaechliche Instanz rauszusuchen. In PHP 4 enthaelt dieselbe Variable das Instanz des Objektes selbst, inkl. der Eigenschaftstabelle. Dies bedeutet in der Praxis, dass beim Zuweisen eines Objektes in PHP 5 zu einer neuen Variable einfach nur dieser Lookup-Index kopiert wird, so dass beide Indizes gleich sind und auf dasselbe Objekt zeigen. Bei PHP 4 wuerde die komplette Instanz kopiert werden, eigentlich wird ein komplett neues Objekt erstellt, da Veraenderungen an der einen Variable/Objekt nicht das Andere beeinflussen.

Oder um es auf eine andere Weise auszudruecken, sind Objekte in PHP 4 einfach nur Arrays mit verknuepften Funktionen und in PHP 5 Resourcen (aehnlich wie MySQL-Ergebnisse oder Datei-Handles) und ebenso verknuepften Funktionen.

Wir betrachten diesen Code mal in PHP 4:

<?php
$fp = fopen(‚foo.txt‘, ‚r‘);
$otherVar = $fp;
fwrite($fp, „One\n“);
fwrite($otherVar, „Two\n“);
fclose($fp);
fwrite($otherVar, „Three\n“); /* Das schlaegt fehl, da die Datei bereits geschlossen ist */
?>

Du nimmst doch auch an, dass alle Strings in dieselbe Datei geschrieben werden, obwohl zwei verschiedene Variablen genutzt werden, richtig? Nun, genau so funktionieren Objekte in PHP 5. Die Instanz selber wird nicht mehr kopiert, sondern nur der numerische Index.

Ich beluege euch auch

„Kopieren“ einer Variable bedeutet nicht genau kopieren. Schauen wir uns mal den folgenden Quellcode an:

<?php
$a = ‚foo‘;
$b = $a;
$a = ‚bar‘;
?>

Nun, wir alle kennen PHP gut genug um zu wissen, das am Ende die Variable $b immer noch ‚foo‘ beinhaltet. Was ihr eventuell nicht wisst, ist das der Wert von $a der nach $b kopiert wurde, eigentlich nie wirklich dupliziert wurde.

Um zu verstehen was PHP hier eigentlic macht, musst du verstehen wie die interne Struktur einer Variablen funktioniert und wie es sich zu den fuer den Nutzer sichtbaren Variablennamen (‚a‘ und ‚b‘ in diesem Falle) verhaelt. Zu aller erst, die tatsaechlichen Inhalte einer Variable (bekannt als zval) bestehen aus vier Einzelteilen:

  • type (z.B. NULL, Boolean, Integer, Float, String, Array, Resource, Object)
  • der spezifische Wert,  value (z.B. 123, 3.1415926535, usw…)
  • is_ref – ein Marker ob die Variable referenziert ist oder nicht
  • refcount – was dir sagt wie oft die Variable referenziert wurde

Was du dir als Variable vorstellst (z.b. $x) ist eigentlich nur ein Etikett (‚x‘ in diesem Falle), dass dazu benutzt wird um mittels einer Lookup-Tabelle das passende zval zu finden. Die Etikette sind eigentlich nur Schluessel in einem assoziativen Array, und wenn man genau nachschaut ist der Mechanismus derselbe.

So, jetzt erstellen wir eine Variable (z.B. $x = 123;). PHP erstellt nur ein neues zval, speichert die tatsaechlichen Wert und verbindet das Etikett mit dem Wert:

‚x‘ => zval ( type => IS_LONG, value.lval = 123, is_ref = 0, refcount = 1 )

Bis jezt refcount ist 1, da der zval Wert bisher nur einmal referenziert wurde. Wenn wir jetzt eine zweite Variable erstellen und zwar per Call-By-Reference ($y =& $x;), wird dasselbe zval wieder benutzt. Es wird einfach ein neues Etikett erzeugt das auf das bereits bestehende zeigt. Ausserdem werden die Attribute entsprechen angepasst.

‚x‘ => zval ( type => IS_LONG, value.lval = 123, is_ref = 1, refcount = 2 )
‚y‘ /

Auf diese Weise sieht es spaeter so aus wenn wir $x aendern, dass sich $y mit aendert. Dies liegt daran, dass beide auf denselben internen Wert verweisen. Aber was waere, wenn wir das ganze per Call-By-Value kopiert haetten ($y = $x;)? Ueberraschenderweise ist das Ergebnis fast dasselbe:

‚x‘ => zval ( type => IS_LONG, value.lval = 123, is_ref = 0, refcount = 2 )
‚y‘ /

Hier wird wieder das originale zval wiederverwendet, der einzige Unterschied ist, dass das is_ref nicht auf 1 gesetzt wird. Dies ist bekannt unter dem Namen copy-on-write reference set (im Gegensatz um full-reference set das wir oben benutzt haben). Dieses Flag is_ref = 0 sagt nun PHP, falls irgendjemand versucht die Variable zu aendern (egal welches Etikett verwendet wird), dass jegliche anderen Referenzen nicht geaendert werden sollen. Wenn wir jetzt also sowas machen wie $x = 456; dann sieht es intern folgendermassen aus:

‚y‘ => zval ( type => IS_LONG, value.lval = 123, is_ref = 0, refcount = 1 )
‚x‘ => zval ( type => IS_LONG, value.lval = 456, is_ref = 0, refcount = 1 )

$x wurde nun von $y getrennt (genauer: von dem originalen zval) und ein neues zval wurde erzeugt. Ausserdem wurden wieder die Attribute angepasst (refcount).

Warum Referenzieren eine schlechte Idee ist, falls du es nicht machen musst

Schauen wir uns mal folgende Situation genauer an:

<?php
$a = ‚foo‘;
$b = $a;
$c = &$a;
?>

In der ersten Anweisung wird ein einzelnes zval erstellt, verknuepft mit einem Ettiket

‚a‘ => zval ( type => IS_STRING, value.str.val = ‚foo‘, is_ref = 0, refcount = 1 )

In der zweiten Anweisung wird dem zval ein zweites Ettiket zugewiesen. So weit so gut:

‚a‘ => zval ( type => IS_STRING, value.str.val = ‚foo‘, is_ref = 0, refcount = 2 )
‚b‘ /

An der dritten Anweisung rennen wir in Probleme. Weil die zval bereits in einer copy-on-write reference gebunden ist, das $b enthaelt, kann eben dieses zval nicht einfach zu is_ref = 1 geaendert werden. Wenn man das so machen wuerde, wuerde das $b in ein full-reference set von $a und $c umwandeln und das ist falsch. Um dieses Problem zu beheben, wird PHP gezwungen dieses zval in zwei identische Kopien zu duplizieren, und dann die Attribute entsprechend anzupassen:

‚b‘ => zval ( type => IS_STRING, value.str.val = ‚foo‘, is_ref = 0, refcount = 1 )
‚a‘ => zval ( type => IS_STRING, value.str.val = ‚foo‘, is_ref = 1, refcount = 2 )
‚c‘ /

Nun haben wir zwei Kopien vom selben identischen Wert, also verschwenden wir Speicher zum speichern und Prozessorzeit zum Erstellen eben dieses Duplikats. Da eine MENGE von Ereignissen dazu fuehren das PHP copy-on-write benutzt (z.B. ein Argument einer Funktion uebergeben), diese Art von erzwungener Duplikation wird sehr oft verwendet, sobald man anfaengt mit tatsachlichen Referenzen zu arbeiten.

Die Moral der Geschichte

Referenzen zu verwenden, wenn man nicht muss (um z.B. den Orignalwert ueber verschiedene Etikette zu aendern) ist eben NICHT eine besonders brilliante Idee deinerseits um Geschwindigkeit und Performance zu erhalten. Weil dein Austricksen der PHP-Engine eben dazu fuehrt, das alles langsamer wird, weil PHP manches eben besser macht als du denkst.

Und wie haengt das nun mit Objekten zusammen? Diese sind nichts besonderes. Sie sind nicht anders als andere Variablen. In diesem Quellcodeblock:

<?php
$a = new stdClass;
$b = $a;
?>

sind die Etiketten immer noch ’nur‘ copy-on-write reference sets.  Viel wichtiger ist, das bei einer Duplizierung

  1. nur ein einzelner Integerwert kopiert wird (was eindeutig billiger ist)
  2. und dieser duplizierte Integerwert zeigt immer noch auf diesselbe Stelle

Somit haben wir ‚verhaelt sich so wie eine Referenz‘ Verhalten, ohne tatsachelich eine Referenz zu haben.

UPDATE: Um Missverstaendnissen vorzubeugen: Ich hab den Artikel nicht verfasst, nur uebersetzt. Es gibt das z.B. das Missverstaendnis, dass der erste Codeblock doch genau das macht was jeglicher Programmierer erwartet. Ja, in der Tat, das tut er auch, nur behauptet das PHP Manual was anderes. Behauptet zumindest die Autorin, ich habs noch nicht gefunden im Manual.