copyright (c)  2003   useGroup
PHP

Jochen Stärk
PHP-Tutorial
Referenzen auf Variablen

nach untenNutzen von Referenzen
nach untenVerschnellert und verkleinert
nach untenWeitere Beispiele
nach untenRückgabe aus einer Funktion
nach untenReferenzen als Indizes
nach untenReferenzen auf Objekte
nach untenHerausfinden, ob ein Objekt eine Referenz ist
nach untenReferenzen und foreach

Nutzen von Referenzen

nach obenNutzen von Referenzen 

Das = kopiert in PHP den Wert des rechten Teil des Ausdrucks in den Linken. Das ist an sich nicht ungewöhnlich, ungewöhnlich ist eigentlich mehr, dass das auch passiert, wenn man eine Funktion aufruft. Übergibt man die Variable $value als Parameter $firstParam, wird der Wert von $value in $firstParam kopiert (call by value).

Wie bei Babyalligatoren ist das solange praktisch, bis die Parameter groß werden. Wenn Sie ein paar tausend Einträge in einem Array haben, dauert es Zeit, bis diese kopiert sind, und außerdem wird Ihr Speicherplatz benutzt. Unpraktisch ist es auch, wenn Sie mit den Elementen im Array noch etwas vor haben. Denn weil alles, was in der Funktion geschieht, nur auf die Kopie angewendet wird, haben Sie zum Beispiel ein Problem, wenn Sie den Wert verändern und später in der aufrufenden Funktion noch nutzen möchten.

So haben sie zum Beispiel ein Funktion, die ein Array umsortiert und als HTML ausgibt. Rückgabewert der Funktion ist der HTML-Quelltext, und genau dann müssen Sie ohne Referenzen künsteln, wenn Sie das umsortierte Array noch einmal brauchen. Eine mögliche Lösung wäre es, anstelle nur HTML ein Array oder einen komplexen Datentyp aus HTML und umsortierten Array zurück zu geben. Beim zurück geben wird das Array nochmals kopiert. Referenzen sind eleganter.

Eine Referenz (bei PHP auch Alias genannt) auf eine Variable ist keine Kopie, Referenz und Variable haben immer denselben Wert. Ändern Sie die Variable, hat sofort die Referenz denselben geänderten Wert und ändern Sie die Referenz nimmt im selben Augenblick die Variable den geänderten Wert an.

Mit Referenzen wird das Beispiel mit dem umsortierten Array nicht nur schneller, sondern belegt auch weniger Speicherplatz. Sie übergeben als Parameter die Referenz auf das Array, nicht das Array selbst, und innerhalb der Funktion ändern Sie die Reihenfolge der Elemente und geben als Rückgabewert der Funktion nur den HTML-String zurück. Da die Referenz der Variablen entspricht, hat sich auch die Reihenfolge im Originalarray in der aufrufenden Routine geändert. Den Aufruf nennt man call by reference.


Verschnellert und verkleinert

nach obenVerschnellert und verkleinert 

Referenzen erzeugen Sie, indem Sie dem Dollarzeichen der Variable einen Ampersand (&) voranstellen. Das geht auch ohne eine Funktion aufzurufen.

$val="Bernd Buchhalter";
$ref=&$val;
$ref="Ralf Differenz";
echo "Bernd Buchhalter heißt jetzt $val";
Als Parameter eine Referenz zu übergeben ist allerdings wie angedeutet besonders witzig:

function dreizurueck(&$refEins, &$refZwei, &$refDrei)
{
  $refEins=1;
  $refZwei=2;
  $refDrei=3;
}

dreizurueck($one, $two, $three);
echo "$one $two $three";

Das Ergebnis ist bei großen Arrays oft um ein mehrfaches schneller als ohne Referenzen, weil ansonsten viel Zeit beim unnötigen Kopieren verschwendet wird. Obwohl man davon laut Zend nicht ohne weiteres ausgehen kann - intern optimiert PHP selbst einiges auf Pointer.


Weitere Beispiele

nach obenWeitere Beispiele 

In Sprachen, die weniger virtuos mit Listen umgehen, wird das zum Teil benutzt, um eine Funktion mehrere Werte "zurück geben" zu lassen; die Variablen, die mit den Rückgabewerten belegt werden sollen, werden dabei als Parameter übergeben. Abgesehen davon, dass man durchaus auch komplexe Datenstrukturen oder Objekte zurück geben kann ist das spätestens dann kontraproduktiv, wenn außenstehenden Programmierern nicht sofort klar ist, dass die Variablen innerhalb der Funktion geändert werden, und das Programm deswegen nicht oder falsch verstehen.

Ein Beispiel schlechten Gebrauchs wäre, wenn ein anderer nicht ersehen könnte, welche Parameter geändert werden.

function umsatzsteuer(&$steuercode, &$betrag, &$ust)
{
if ($steuercode==1) { $ust=0.16; $betrag*=1.16; }
else { $betrag*=$ust; }
return $betrag;
}
Hier wird die Umsatzsteuer nur dann geändert, also quasi zur Rückgabe verwendet, wenn $steuercode==1 ist, der Betrag wird als Variable verändert UND zurück gegeben (die Werte sind zwar gleich aber es ist trotzdem nicht schön den Anwender entscheiden zu lassen, welches Ergebnis er benutzt) und Steuercode wird nur so aus Spaß als Referenz übergeben. Das ist dann aber schon extrem grausig anzusehen. Ach so, und eine Referenz sowohl zur Ein- als auch zur Ausgabe zu nutzen, macht bei Arrays manchmal Sinn, bei normalen Variablen macht es das lesen und verstehen oft unnötig schwer.

So etwas wie function lohnsteuer(&$steuerklasse, $betrag, &$lohnsteuer) kommt weit häufiger vor. Entweder hat der Programmierer die Referenz beim zweiten Parameter vergessen oder er traut seinem Leser zu, dass der spontan die richtige Eingebung hat, und sieht, dass links die Referenz zum Beispiel eine Eingabevariable ist, und und Lohnsteuer beschrieben wird (quasi ein Ausgabeparameter ist). Um das optisch zu trennen hat er vielleicht Betrag als normalen Parameter eingefügt. Besser finde ich erklärende Benennung und ein Gruppieren aller Referenzen etwa an den Anfang der Parameterliste wie addiereLohnsteuer(&$ref_in_steuerklasse, &$ref_out_lohnsteuer, $betrag).


Rückgabe aus einer Funktion

nach obenRückgabe aus einer Funktion 

Die Rückgabe von Referenzen erreicht man, indem man vor den Funktionsnamen ein Ampersand schreibt, und zudem den zurück gegebenen Wert im aufrufenden Programm als Referenz (sprich mit &) behandelt.

function &giveObject()
{
  $returnRef=new CmyObject();
  return $returnRef;
}

$myObjectFromFunction = & giveObject();
Man kann vereinfacht annehmen, dass "=" eine Kopie anlegt, und "= &" eine Referenz.


Referenzen als Indizes

nach obenReferenzen als Indizes 

Referenzen kann man auch in einem assoziativen Array verwenden, um über mehrere, verschiedene Schlüssel auf einen Wert zuzugreifen. Es kann für solche Probleme elegantere Lösungen geben, so ist das immer mit Vorsicht zu genießen. Beispielsweise möchte ich auf ein assoziatives Array zugreifen können und auch einen Index benutzen können, um an die einzelne Elemente zu kommen:

$arrayVariable=array();
$anzElemente=0;

$neuerSchluessel="Pestalozzi";
$neuerWert="Wer etwas wert ist, den macht Erfahrung und Unglück besser.";
$arrayVariable[$neuerSchluessel]=$neuerWert;
$arrayVariable[$anzElemente]=&$arrayVariable[$neuerSchluessel];
$anzElemente++;

$neuerSchluessel="Ebner-Eschenbach";
$neuerWert="Der Weise ist selten klug.";
$arrayVariable[$neuerSchluessel]=$neuerWert;
$arrayVariable[$anzElemente]=&$arrayVariable[$neuerSchluessel];
$anzElemente++;

Diese Lösung hat einige schöne, und einige unschöne "Nebenwirkungen". Unelegant ist das in der Hinsicht, dass dann kleine ganze Zahlen, z.B. 3 als Schlüssel eventuell vorhandene Schlüssel überschreiben, es sollte sich also um Strings handeln, die keine Zahlen annehmen können und dürfen. Ebenfalls unelegant ist die Lösung, weil foreach und count den Array doppelt durchgehen.

Elegant ist die Lösung hingegen, weil die verschiedenen Schlüsel immer auf denselben Wert verweisen, weil einer nur eine Referenz ist, dadurch auch Speicherplatz gespart wird und der Zugriff auf den Array sehr einfach ist - einfach $arrayVariable["Schlüsselname"] oder $arrayVariable[0]..$arrayVariable[$anzElemente]

Das geht auf mit mehreren Schlüsseln, wird dann aber bald ein ziemlich zusammengewürfeltes Array ergeben. Für eine andere Anwendungsmöglichkeit könnte den Index durch einen anderen Schlüssel tauschen:

$arrayVariable=array();
$indexArray=array();
$anzElemente=0;

$neuerSchluessel="Pestalozzi";
$neuerWert="Wer etwas wert ist, den macht Erfahrung und Unglück besser.";
$arrayVariable[$neuerSchluessel]=$neuerWert;
$indexArray["Erfahrung"][]=&$arrayVariable[$neuerSchluessel];
$anzElemente++;

$neuerSchluessel="Ebner-Eschenbach";
$neuerWert="Der Weise ist selten klug.";
$arrayVariable[$neuerSchluessel]=$neuerWert;
$indexArray["Erfahrung"][]=&$arrayVariable[$neuerSchluessel];
$anzElemente++;
Sie können dann sowohl über $indexArray["Erfahrung"][0] als auch über $arrayVariable["Pestalozzi"] auf das erste Zitat zugreifen.


Referenzen auf Objekte

nach obenReferenzen auf Objekte 

Besonders wichtig sind Referenzen in PHP 4 im Zusammenhang mit Objekten. Da erst PHP 5 Objekte in Parametern von Funktionen automatisch als Referenz übergibt, und Methoden (Funktionen innerhalb von Objekten) meistens die Eigenschaften (Variablen des Objekts) ändern, übergeben Sie ein Objekt meist manuell als Referenz. Wenn Sie das nicht tun, und innerhalb der aufgerufenen Funktion eine Methode aufrufen, die eine Eigenschaft des Objekts ändert, hat das Objekt, weil Sie nur eine Kopie bearbeitet haben, nach Abschluss der Funktion im aufrufenden Code plötzlich wieder die Eigenschaften als hätte die Funktion sie nie geändert (hat sie auch nicht, da sie nur die Kopie geändert hat).

Da das unpraktischer ist als Würfelzucker in Tuben, und man im Zusammenhang mit Objekten ständig die Referenzen braucht und sich schnell verzettelt, werden Objekte in PHP 5, wie auch in Java, standartmäßig per call-by-reference und andere Datentypen wie Strings und Integer, per call-by-value an Funktionen übergeben beziehungsweise von diesen zurück gegeben.


Herausfinden, ob ein Objekt eine Referenz ist

nach obenHerausfinden, ob ein Objekt eine Referenz ist 

Es gibt leider keine Eigenschaft "ID" eines Objektes, die bei Referenzen gleich wäre und bei Kopien unterschiedliche Werte annimmt - denn bei Kopien wird der entsprechende Wert mit-kopiert. Auch Vergleiche fruchten nicht, da Sie immer gleich zurück liefern, selbst mit dem Identitätsoperator ===. Der einzige mir bekannte Weg, herauszufinden, ob etwas eine Referenz oder nur eine Kopie ist, ist der von Digiways, eine Variable zu verändern beziehungsweise zu erweitern und zu sehen, ob sich die andere auch geändert hat:

function comparereferences(&$a, &$b)
/*

Vergleicht zwei Objekte und findet heraus, ob das eine eine
Kopie oder eine Referenz des anderen ist, indem es in einem
eine zusätzliche member-variable anlegt, kuckt, ob die
im anderen jetzt auch existiert, und dann wieder löscht.

Wird von mir zu debuggen benutzt.

von http://www.digiways.com/articles/php/smartcompare/

*/
   {
       // creating unique name for the new member variable
       $tmp = uniqid("");
       // creating member variable with the name $tmp in the object $a
       $a->$tmp = true;
       // checking if it appeared in $b
       $bResult = !empty($b->$tmp);
       // cleaning up
       unset($a->$tmp);
       return $bResult;
   }


Referenzen und foreach

nach obenReferenzen und foreach 

Foreach ist wunderschön - leider arbeitet es nicht mit Referenzen zusammen, sondern kopiert sich die Elemente. Sollten Sie mal in die Verlegenheit kommen, Ihre Schlüssel nicht zu wissen, können Sie sich jedoch mit reset, current und next helfen:

    reset($arrayMitReferenzen);
    $currentRow =& current ($arrayMitReferenzen);
      do
      {
        $currentObject=&$currentRow->getObject($columnName);
        $currentObject->someMethod();

   } while ($currentRow =& next ($arrayMitReferenzen));
   


copyright (c)  2003   useGroup