Die magischen Methoden von PHP 5 – Interzeptoren

Es gibt seit PHP 5 die sogenannten Interzeptormethoden in Klassen, die aufgerufen werden, wenn ein Fehler verursacht werden würde. Mit diesen Methoden kann man sozusagen im letzten Moment verhindern, dass PHP eine Fehlermeldung ausspuckt. In diesem Artikel stelle ich euch die verschiedenen magischen Methoden bzw. Funktionen vor. Betrachtet den Artikel als Übersicht. Ich habe nicht alle Sonderfälle erklärt. Der Artikel soll nur einen groben Überblick verschaffen.
Beachtet: Alle Methoden beginnen mit ZWEI Unterstrichen!

Es gibt diese Interzeptormethoden:



__autoload()

Das ist die wohl am meisten verwendete Interzeptorfunktion. Sie wird eingesetzt, um Klassen nachzuladen, wenn sie benötigt werden. So spart man sich das Einbinden sämtlicher Klassen am Anfang eines Skripts.

Wenn man seine Klassen klug benennt, ist es möglich, dass Klassen der gleichen Art / Klassen eines Packages in Unterordnern abgelegt werden. Hierzu gibt es ein kleines Beispiel aus meinem Framework:

/**
 * Klasse dient als Container für 2 statische Methoden und wird nie instanziert.
 * @package MVC-Framework
 * @version 1
 * @author Simon
 *
 * FW_Autload ist eine Klasse, die verwendet wird, um einen Autoloader zu registrieren.
 */
abstract class FW_Autoload
{
  /**
   * @access public
   * @param string classname
   *
   * Diese statische Methode lädt eine Klasse, wenn sie benötigt wird.
   * Sie findet nur Klassen, die mit FW_ beginnen.
   * --> Ist der Autoloader fürs FW
   */
  public static function load($className)
  {
    if(substr($className, 0,3) == "FW_") //nur wenn die Klasse zum FW gehört
    {
      $classFile = str_replace("_", "/", substr($className, 3)).".class.php";
      if(file_exists($classFile))
      {
       require_once($classFile);
      }
    }
  }

  /**
   * Singleton-Elemente: private __construct
   *                     private __clone
   */
  private function __construct() {} //verhindern, dass new verwendet wird
  private function __clone() {} //verhindern, dass durch Kopieren 2 Objekte entstehen

  /**
   * @access public static
   * @param mixed autoloader
   *
   * registriert einen weiteren autoloader
   */
  public static function register($autoloader = null)
  {
    if($autoloader === null)
    {
      spl_autoload_register(array('FW_Autoload', 'load'));
    }
    else
    {
      spl_autoload_register($autoloader);
    }
  }
}

Man kann mit FW_Autoload::register() den Autoloader FW_Autoload::load() als Autoloader registrieren. Wenn man der register()-Methode einen Parameter mitgibt, kann man noch weitere Autoloader registrieren. Diese werden von PHP dann in einem Stack abgelegt und so lange abgearbeitet, bis die gewünschte Klasse geladen werden kann.

Der Autoloader selbst zerlegt dann den Namen der angeforderten Klasse so, dass jeder Unterstrich eine Verzeichnisebene darstellt. Wenn man jetzt beispielsweise die Klasse FW_Database_MySQL will, muss sie sich in der Datei Database\MySQL.class.php befinden. Der Autoloader lädt dann automatisch die Klassendatei.

In diesem Beispiel ist es erforderlich, dass jede Klasse mit FW_ anfängt. Ich habe das so gelöst, weil es für das Framework einen eigenen Autoloader gibt, der nur FW_-Klassen verarbeitet. So kommen sich die anderen Autoloader nicht in die Quere.


__get()

Diese Methode kann implementiert werden, wenn man dem Benutzer einer Klasse ermöglichen möchte, undefinierte Attribute (Eigenschaften) der Klasse anzusprechen (lesend).

class MyClass
{
  private $attribut;

  public function __get($var_name)
  {
     echo "Du versuchst, auf die Eigenschaft ".$var_name." zuzugreifen. Sie existiert aber nicht.";
  }
}

Versucht man jetzt, folgenden Code auszuführen, bekommt man die Meldung „Du versuchst, auf die Eigenschaft das_ist_ein_test zuzugreifen. Sie existiert aber nicht.“

$objekt = new MyClass;
echo $objekt->das_ist_ein_test;

Wenn __get nicht vorhanden wäre, würde PHP folgenden Fehler bringen:

Notice: Undefined property: MyClass::$das_ist_ein_test in C:\xampp\htdocs\get.php on line 9

So richtig viel bringt uns das aber noch nicht. Manchmal kann es Sinn machen, bestimmte Werte in einem Array abzulegen. In dem Fall kann man dann __get() dazu nutzen, auf diese Werte auf einfache Weise zuzugreifen:

class MyClass
{
  private $attributes = array();

  public function __construct()
  {
     $this->attributes["hallo"] = "das geht";
     $this->attributes["hi"] = "das auch";
  }

  public function __get($key)
  {
    if(array_key_exists($key, $this->attributes))
    {
        return $this->attributes[$key];
    }
    else
    {
      return null;
    }
  }
}

$t = new MyClass;
echo $t->hallo."\n";
echo $t->hi."\n";
echo $t->undefined;

Im Konstruktor legen wir ein Array mit Standartwerten an. Später kann über __get() darauf zugegriffen werden. Dabei steht der Schlüssel des Arrays für den Namen des Pseudoattributs.
Wird eine „undefinierte“ Eigenschaft aufgerufen, wird einfach null zurückgegeben. Das Beispiel erzeugt diese Ausgabe:

das geht
das auch


__set()
Wie __get() ist auch __set() in der Lage, auf Zugriffe auf undefnierte Eigenschaften einer Klasse zu reagieren. Im Gegensatz zu __get() liest __set() aber nicht, sondern schreibt. Es erwartet 2 Parameter: Den Namen der angeforderten Eigenschaft und deren Inhalt.

class MyClass
{
  private $attributes = array();

  public function __set($key, $value)
  {
    $this->attributes[$key] = $value;
  }

  public function __get($key)
  {
    if(array_key_exists($key, $this->attributes))
    {
        return $this->attributes[$key];
    }
    else
    {
      return null;
    }
  }
}

$t = new MyClass;
$t->hallo = "hi";
echo $t->hallo;

Hier wollen wir den Wert von MyClass::$hallo verändern. Da die Variable aber nicht vorhanden ist, führt PHP automatisch MyClass::__set(„hallo“, „hi“) aus und veranlasst so eine Änderung von MyClass::$attributes[„hallo“].

Später wollen wir den gesetzten Wert wieder lesen. Da MyClass::$hallo noch immer nicht existiert, wird es per __get() geholt. Ziemlich praktisch!


__isset
Wenn man prüfen möchte, ob es ein Attribut in einem Objekt gibt, kann man einfach isset() verwenden. Wenn wir aber vorher schon __set() so definiert haben, dass es Werte in einem Array ablegt, geht das nicht so ohne weiteres. Aus diesem Grund bringt PHP eine weitere magische Methode mit: __isset()

class MyClass
{
  private $attributes = array(
                              "name" => "hans",
                              "ort" => "stuttgart",
                              "alter" => 29
                             );

  public function __isset($key)
  {
    return isset($this->attributes[$key]);
  }
}

$t = new MyClass;
echo (int)isset($t->name);
echo (int)isset($t->geschlecht);
echo (int)isset($t->ort);

Es wird ein Array mit 3 Werten belegt.
Danach wird die __isset()-Methode implementiert, die einen Parameter annimmt: Den Namen der angeforderten Variable.

Später prüfen wir die Attribute name, geschlecht und ort auf Existenz. Die Ausgabe sollte so aussehen:

101

(Name und Ort existieren, Geschlecht aber nicht.)
Der (int)-Cast hat zur Folge, dass true in 1 und false in 0 umgewandelt wird.


__unset()
Da fehlt doch noch was! Ja, genau! Wir können noch keine Daten aus einem Array in einem Objekt löschen. Dazu stellt uns PHP __unset() bereit. __unset() erwartet als Parameter den Namen des zu löschenden Attributs.

class MyClass
{
  private $attributes = array(
                              "name" => "hans",
                              "ort" => "stuttgart",
                              "alter" => 29
                             );

  public function __unset($key)
  {
    if(isset($this->attributes[$key]))
    {
      unset($this->attributes[$key]);
    }
  }
}

Es wird wieder ein Array angelegt, das 3 Werte beinhaltet. Danach wird __unset($key) definiert. Der Interzeptor prüft dann, ob die Variable, die gelöscht werden soll, überhaupt existiert und löscht sie dann. Nichts spektakuläres!

Um zu sehen, dass das wirklich klappt, lassen wir uns den Inhalt des Objekts mit print_r() vor und nach dem löschen ausgeben ausgeben:

$t = new MyClass;
print_r($t);
unset($t->ort);
unset($t->alter);
print_r($t);

Vorher:

MyClass Object
(
[attributes:private] => Array
(
[name] => hans
[ort] => stuttgart
[alter] => 29
)

)

Nachher:

MyClass Object
(
[attributes:private] => Array
(
[name] => hans
)

)

Wie wir sehen, gibt es nach dem Löschen keinen Ort und auch kein Alter mehr. Genau wie wir das wollten!


__clone()
Ja, es gibt tatsächlich noch mehr von diesen Interzeptoren! Da haben die PHP-Entwickler richtig was geleistet 🙂

Man kann mit dem Befehl __clone() angeben, wie PHP sich verhalten soll, wenn ein Objekt geklont wird. z.B. kann man so Eigenschaften ändern, auch wenn ich mich hier nach dem Sinn frage.

Ich habe __clone() bis jetzt noch nie verwendet, außer beim Singleton-Pattern zum Verhindern von zwei Klasseninstanzen.

Trotzdem zeige ich euch hier ein kleines Beispiel zu __clone():

class MyClass
{
  private $cloned = false;

  public function __clone()
  {
    $this->cloned = true;
  }
}

$original = new MyClass;
print_r($original);

$kopie = clone $original;
print_r($kopie);

Wichtig ist die Sicht, aus der man die Methode sehen muss: Aus dem neuen (entstehenden) Objekt! $this refernziert also nicht das Original, sondern die Kopie.
In diesem Beispiel gibt es ein Flag namens „cloned“, das beim Original false und bei einem Klon true ist.

Deutlich wird das durch print_r() auf das Original und die Kopie:
Original:

MyClass Object
(
[cloned:private] => 0
)

Kopie:

MyClass Object
(
[cloned:private] => 1
)

Ein wirklicher Anwendungsfall fällt mir hierzu aber nicht ein!


__toString()
Jetzt kommen wir zu einer Methode, von der ich lange den Sinn nicht verstanden habe: __toString()
Man kann ein Objekt normalerweise nicht an das echo-Sprachkonstrukt weitergeben, sonst erhält man diese nette Fehlermeldung:

Catchable fatal error: Object of class MyClass could not be converted to string in C:\xampp\htdocs\objstr.php on line 15

Durch __toString() wird es aber möglich, Objekte „auszugeben“. Was ausgegeben wird, bestimmen wir!

Dieses Mal gibt es ein Beispiel für einen realen Fall: Eine Namensliste.

class Namensliste
{
  private $names = array();

  public function addName($name)
  {
    $this->names[] = $name;
  }

  public function __toString()
  {
    $ret = "";

    foreach((array)$this->names as $key => $value)
    {
      $ret .= $key." -> ".$value."";
    }

    return $ret;
  }
}

Über addName() erweitern wir das Array $names um ein Element.
PHP will, dass __toString() immer einen String zurückgibt (anstatt ihn auszugeben). Deshalb erweitern wir hier für jedes Element den String $ret und geben ihn anschließend zurück.

Anwendung:

$list = new Namensliste;
$list->addName("Simon");
$list->addName("Tobias");
$list->addName("Angela");
echo $list;

Ausgabe:

0 -> Simon
1 -> Tobias
2 -> Angela

Ich verwende __toString() auch in meinem aktuellen Projekt (Formularvalidierung).


__sleep()
Will man Objekte in einer Datenbank ablegen, verwendet man in der Regel die Funktion serialize().
Manchmal gibt es in einem Objekt eine bestehende Datenbankverbindung oder eine geöffnete Datei. Falls man diese geöffnete Date bzw. Verbindung vorher schließen möchte, ist __sleep() der optimale Platz dafür.


__wakeup()
Und da es im Normalfall auch wieder zu einem Aufwachen des Objekts kommt, gibt es noch __wakeup(). Sie wird aufgerufen, wenn man das Objekt mit unserialize wiederherstellen möchte. Hier kann man dann z.B. wieder eine Datenbankverbindung aufbauen oder eine Datei öffnen.

__construct() & __destruct()
Streng genommen sind auch diese beiden Methoden magische Methoden, weil sie BEIM Erzeugen bzw. Zerstören eines Objekts ausgeführt werden. In der Regel werden sie aber nicht so bezeichnet. Wie sie funktionieren, sollte euch bekannt sein. Ansonsten kann ich euch das OOP-Tutorial von Peter Kropff nahelegen.

1 Star2 Stars3 Stars4 Stars5 Stars (1 Stimme, durchschnittlich 5,00 / 5)
Loading...


13 Kommentare zu “Die magischen Methoden von PHP 5 – Interzeptoren”

  1. Schöne Übersicht. Habe einige davon selbst noch nicht gekannt. Allerdings muss ich auch sagen, dass es, zumindest in meinen Augen, keinen sinnvollen Zweck für __get() und __set() gibt.

    Wenn man Attribute deklariert sollten diese sowieso private sein und nur mittels getter- und setter-Methoden gesetzt werden können. Zumindest von außerhalb der Klasse. Ist bestimmt auch ein Streitpunkt, an dem sich die Geister scheiden, aber das ist nun mal meine Meinung dazu. Außerdem beschleunigen diese Methoden ein Skript nicht unbedingt.

  2. Es stimmt, dass diese Methoden das Skript ein bisschen verlangsamen, aber in der Regel merkt man davon nichts. Das spielt sich im Millisekundenbereich ab.

    Du kennst keinen sinnvollen Anwendungszweck für __get() 😉 Stell dir eine Klasse vor, die für ein Template Werte speichert. Wenn du im Template diese Werte abfragen willst, kannst du entweder eine stinknormale get-Methode schreiben, was aber mehr Tipparbeit im Template darstellt, oder du kannst die Interzeptormethode __get() verwenden.

    Dadurch spart man sich Tipparbeit. Weitere Vorteile fallen mir gerade nicht ein, aber es gibt sicher noch welche!

    MfG Simon

  3. Das spielt sich im Millisekundenbereich ab.

    Und jetzt nehmen wir mal an, dass man, aus welchem Grund auch immer, tausende von Attributen setzen muss und schon werden aus Millisekunden, Hundertstel-, Zehntel-, ganze Sekunden. Und glaub mir, ich kenne dieses Phänomen, da ich damit schon zu tun hatte 😉

    Allgemein würde ich sagen, dass diese beiden Methoden nur dann genutzt werden sollten, wenn man keine andere Wahl hat oder es einfach nicht anders geht. Allerdings würde ich mir dann eher Gedanken darüber machen, ob meine Klasse ihren Zweck auch wirklich erfüllt 😀

  4. Ok, da magst du Recht haben. Du hast mich überzeugt 😀

    Trotzdem finde ich das Feature nett und setze es auch weiterhin ein, wenn es die Performance nicht spürbar beeinflusst.

    Geändert ist sowas schnell!

  5. […] zurücksetzen, neu belegen und per __toString-Interzeptor […]

  6. […] auf die Daten mit den Interzeptoren (__set, __get, __unset, […]

  7. […] deklarieren ansonsten gibt eine abfrage var_dump(isset($controller->page)) bool false aus Die magischen Methoden von PHP 5 – Interzeptoren __________________ <?php echo "ad astra"; […]

  8. […] … Streng genommen sind auch diese beiden Methoden magische Methoden, weil sie BEIM Erzeugen bzw. Zerstören eines Objekts ausgeführt werden. In der Regel werden sie aber nicht so bezeichnet. Quelle: Net Developers/die-magischen-methoden-von-php-5-interzeptoren/ […]

  9. Es gibt eine fertige Autoloader Implementierung: http://php-autoloader.malkusch.de/de/
    Einfach Einbinden und fertig. Es muss nichts mehr konfiguriert werden. Klassen werden in beliebigen Dateien gefunden.

  10. Sieht ganz nett aus.

    Aber ist das nicht sehr unperformant? Hab mir nicht den gesamten Code angeschaut.

  11. Nein ist es nicht.

  12. Wäre nett, wenn du das noch erklären könntest 😉

    Es sieht für mich so aus, als müsstest du sämtliche Dateien nach Klassen durchsuchen.

  13. Ja, er sucht auch so lange bis er die gewünschte Klassendefinition gefunden hat. Diesen langen Prozess macht er aber genau einmal. Danach findet er die Definition unmittelbar dank einem Index:
    http://php-autoloader.malkusch.de/de/Index/

Hinterlasse einen Kommentar!


»Informationen zum Artikel

Autor: Simon
Datum: 10.03.2009
Zeit: 21:46 Uhr
Kategorien: Wissenswertes
Gelesen: 14528x heute: 2x

Kommentare: RSS 2.0.
Diesen Artikel kommentieren oder einen Trackback senden.