Magie

Selbstverständlich gibt es in PHP Magie. PHP ist in der Lage, bestimmte Methoden automatisch auszuführen, wenn sie denn als magische Methoden deklariert sind. Dabei gibt es ein paar wichtige Dinge, die man beachten muss.

  • Alle magischen Methoden beginnen mit einem doppelten Unterstrich, also __.
  • Alle Methoden, die damit beginnen, werden von PHP als magisch eingestuft - deshalb sollte man eigene Methoden niemals mit zwei Unterstrichen beginnen lassen, es sei denn man hat einen super Grund dafür.
  • Magische Methoden werden zwar automatisch ausgeführt, müssen aber trotzdem in die jeweiligen Klassen implementiert werden.

Selbstverständlich gibt es immer wieder Uneinigkeit darüber, welches denn nun magische Methoden sind. Deshalb fange ich mal mit __construct und __destruct an, obwohl sie im ZDE (ZendDevelopmentEnviroment ist eine PHP-Entwicklungsumgebung) nicht als magische Methoden geführt werden. Außerdem gibt es seit PHP 5 die so genannten Interzeptormethoden in Klassen, die aufgerufen werden, wenn ein Fehler verursacht werden wird. Mit diesen Methoden kann man verhindern, dass PHP eine Fehlermeldung ausgibt bzw. man kann ggf. eine eigene Fehlermeldung basteln.

__construct()

Der Konstruktor ist eine Methode, die als erzeugende Methode meist am Anfang einer Klasse deklariert und automatisch ausgeführt wird. Mit ihr werden meist wichtige Variablen gesetzt oder der Konstruktor der Elternklasse wird ausgeführt. Ein super Beispiel dafür ist der MySQL-Aufruf, da man ihn so in vielen Frameworks findet:

class MySQL {

  private $db;
  
  public function __construct ($host, $user, $password) {
    $this->db = mysql_connect ($host, $user, $password);
  }
  
  public function getRows() { ... }
}

$mysql = new MySQL ("localhost", "root", "passwort");
$mysql->getRows();

Hm, naja, das sieht ja erstmal noch nicht so doll nach einer Arbeitserleichterung aus, aber mal sehen, wie es aussehen könnte, wenn wir nur eine Verbindung brauchen ...

class MySQL {

  private $db;
  private $host 	= "localhost";
  private $user 	= "root";
  private $password 	= "password";
  
  public function __construct () {
    $this->db = mysql_connect ($host, $user, $password);
  }
  
  public function getRows() { ... }
}

$mysql = new MySQL;
$mysql->getRows();

__destruct()

Der Destruktor wird immer automatisch am Ende des Klassenaufrufs ausgeführt. Mit ihm kann mal also z.B. Variablen, die man nicht mehr braucht, zerstören oder abschließende Routinen ausführen. Am Beispiel der MySQL-Klasse wäre das:

class MySQL {

  private $db;
  private $host 	= "localhost";
  private $user		= "root";
  private $password = "password";
  
  public function __construct () {
    $this->db = mysql_connect ($host, $user, $password);
  }
  
  public function getRows() { ... }
  
  public function __destruct () {
    mysql_close ($this->db);
 }


}

$mysql = new MySQL;
$mysql->getRows();

__call()

Wenn man einen Zugriff auf eine nicht existierende Methode versucht, so kann man das mit __call() abfangen. Im folgenden Beispiel haben wir die Methode aufpumpen() in unserer BallKlasse entfernt, das anderen jedoch nicht mitgeteilt. Gut, dass wir dafür eine __call() Methode implementiert haben :)

class BallKlasse {
	public $farbe;
	public $groesse;
	
	public function anmalen($farbe) {
		$this->farbe = $farbe;
	}
	
	public function __call($name, $argumente) {
		echo "Die Methode ".$name."(". implode(', ', $argumente).") gibt es nicht oder sie ist geschützt.";
	}
}


$ball = new BallKlasse();
$ball->anmalen("weiss");
$ball->aufpumpen(1);
// Die Methode aufpumpen(1) gibt es nicht oder sie ist geschützt.

__get()

Hier kann man das gleiche wie mit __call() erreichen, jedoch gilt das für nicht existierende Eigenschaften.

class BallKlasse {
	public $farbe;
	public $groesse;

  public function __get($eigenschaft)
  {
     echo "Du versuchst, auf die fehlende Eigenschaft ".$eigenschaft." zuzugreifen.";
  }
}

$ball = new BallKlasse;
echo $ball->preis;
// Du versuchst, auf die Eigenschaft preis zuzugreifen. Sie existiert aber nicht.

Eine sehr gute praktische Anwendbarkeit für __get() ist das Auslesen von fehlenden Schlüsseln eines Arrays, wie man im folgenden Beispiel sehen kann:

class BallKlasse {
  private $eigenschaften = array();

  public function __construct() {
     $this->eigenschaften["farbe"] = "weiss";
     $this->eigenschaften["groesse"] = 1;
  }

  public function __get($key) {
  	
    if(array_key_exists($key, $this->eigenschaften)) {
        return $this->eigenschaften[$key];
    } else {
      echo "Du versuchst, auf die fehlende Eigenschaft ".$key." zuzugreifen.";
    }
    
 }
}

$ball = new BallKlasse;
echo $ball->farbe."\n";
echo $ball->groesse."\n";
echo $ball->preis;
// weiss
// 1
// Du versuchst, auf die Eigenschaft preis zuzugreifen. Sie existiert aber nicht.

__set()

Wie __get() ist auch __set() in der Lage, auf Zugriffe auf nicht existierende Eigenschaften einer Klasse zu reagieren. Im Gegensatz zu __get() liest __set() aber nicht, sondern es schreibt. In unserem Beispiel mt dem Arry fügt es also einen Schlüssel dazu.

class BallKlasse {
  private $eigenschaften = array();

  public function __construct() {
     $this->eigenschaften["farbe"] = "weiss";
     $this->eigenschaften["groesse"] = 1;
  }

  public function __set($key, $value) {
    $this->eigenschaften[$key] = $value;
  }
  
  public function __get($key) {
  	
    if(array_key_exists($key, $this->eigenschaften)) {
        return $this->eigenschaften[$key];
    } else {
      echo "Du versuchst, auf die fehlende Eigenschaft ".$key." zuzugreifen.";
    }
    
 }
}

$ball = new BallKlasse;
$ball->preis = 10;

echo $ball->farbe."\n";
echo $ball->groesse."\n";
echo $ball->preis;
// weiss
// 1
// 10

__clone()

Mit __clone() kann man angeben, wie PHP sich verhalten soll, wenn ein Objekt geklont wird. Z.B. kann man so beim Klonen Eigenschaften ändern. Ich persönlich habe __clone() noch nie zum Klonen verwendet, nur zum Interdrücken der Möglichkeit eines Klonens, z.B. in Singleton-Klassen zum Verhindern einer Kopie mittels __clone() { }. Wichtig ist, dass man weiß, dass man nach dem Klonen das neue Objekt aus dem Klone erhalten hat und nicht aus dem Original - es ändert sich also u.U. etwas beim Vorgang des Klonens.

class BallKLasse {
  public $verkauf = 0;

  public function __clone() {
    $this->verkauf = 1;
  }
}

$original = new BallKlasse;
echo $original->verkauf."\n";
// 0

$kopie = clone $original;
echo $kopie->verkauf;
// 1

Naja, aber manchmal möchte man eben nicht, dass es von einem Objekt einen Klon gibt. Das kann man dann folgendermaßen verhindern:

class BallKLasse {
  public $verkauf = 0;

  private function __clone() {}
}

$original = new BallKlasse;
echo $original->verkauf."\n";
// 0

$kopie = clone $original;
echo $kopie->verkauf;
// Gibt jetzt eine Fehlermeldung aus

__autoload()

Mit Autoload kann man super benötigte Klassen zur Laufzeit nachladen. Natürlich müssen sich diese wie im folgenden Beispiel alle in demselben Verzeichnis befinden, aber man kann ja die Grundfunktionalität dieser magischen Funktion super erweitern. Wie eine solche Erweiterung aussehen kann, findet man z.B. hier. Das folgende Beispiel ist eher die Grundvariante :)

function __autoload($klasse) { 

      if (file_exists($klasse . '.php')) { 
          require_once $klasse . '.php'; 
          return true; 
      } else {
		echo "Kann die Klasse class ".$klasse." oder die Datei ".$klasse.".php nicht finden.";  
		exit;    	
      }
}

$ball = new Ball;
// Es wird versucht, die Klasse Ball in der Datei Ball.php zu laden

So, es gibt noch ein paar andere magische Methoden, wie z.B. __isset(), __unset(), __toString(), __sleep() und __wakeup(), jedoch möchte ich diese an dieser Stelle nicht näher erklären, da ich sie selten brauche.