Magische Methoden in PHP sind spezielle Methoden, die mit einem
doppelten Unterstrich (__) beginnen und automatisch
ausgeführt werden, wenn bestimmte Ereignisse im Lebenszyklus eines
Objekts auftreten. Diese Methoden ermöglichen es, das Verhalten von
Objekten auf fortgeschrittene Weise anzupassen und erweitern die
Funktionalität von Klassen erheblich.
PHP definiert eine Reihe magischer Methoden, die unterschiedliche Funktionen erfüllen:
| Methode | Beschreibung |
|---|---|
__construct() |
Konstruktor, wird bei der Objekterstellung aufgerufen |
__destruct() |
Destruktor, wird bei der Objektzerstörung aufgerufen |
__call() |
Wird aufgerufen, wenn eine nicht erreichbare Methode aufgerufen wird |
__callStatic() |
Wird aufgerufen, wenn eine nicht erreichbare statische Methode aufgerufen wird |
__get() |
Wird aufgerufen, wenn auf eine nicht erreichbare Eigenschaft zugegriffen wird |
__set() |
Wird aufgerufen, wenn einer nicht erreichbaren Eigenschaft ein Wert zugewiesen wird |
__isset() |
Wird aufgerufen, wenn isset() auf eine nicht
erreichbare Eigenschaft angewendet wird |
__unset() |
Wird aufgerufen, wenn unset() auf eine nicht
erreichbare Eigenschaft angewendet wird |
__sleep() |
Wird vor der Serialisierung aufgerufen |
__wakeup() |
Wird nach der Deserialisierung aufgerufen |
__serialize() |
Wird für die Serialisierung (ab PHP 7.4) aufgerufen |
__unserialize() |
Wird für die Deserialisierung (ab PHP 7.4) aufgerufen |
__toString() |
Wird aufgerufen, wenn das Objekt als String verwendet wird |
__invoke() |
Wird aufgerufen, wenn das Objekt wie eine Funktion aufgerufen wird |
__set_state() |
Wird für var_export() verwendet |
__clone() |
Wird nach der Objektklonung aufgerufen |
__debugInfo() |
Wird von var_dump() aufgerufen (ab PHP 5.6) |
Lassen Sie uns jede dieser Methoden im Detail untersuchen.
Die Methoden __construct() und __destruct()
markieren den Beginn bzw. das Ende des Lebenszyklus eines Objekts.
Der Konstruktor wird automatisch aufgerufen, wenn ein neues Objekt erstellt wird. Er wird typischerweise für die Initialisierung von Objekteigenschaften verwendet:
class Person {
private string $name;
private int $alter;
public function __construct(string $name, int $alter = 30) {
$this->name = $name;
$this->alter = $alter;
echo "Neue Person erstellt: {$this->name}, {$this->alter} Jahre alt\n";
}
}
$person = new Person("Max Mustermann", 25); // Ausgabe: Neue Person erstellt: Max Mustermann, 25 Jahre altDer Destruktor wird aufgerufen, wenn alle Referenzen auf ein Objekt entfernt wurden oder wenn das Skript beendet wird:
class Verbindung {
private $verbindungsHandle;
public function __construct(string $server) {
echo "Verbindung zu {$server} hergestellt\n";
$this->verbindungsHandle = "HANDLE_" . rand(1000, 9999);
}
public function __destruct() {
echo "Verbindung {$this->verbindungsHandle} wird geschlossen\n";
// Aufräumlogik, z.B. Datenbanktransaktionen beenden, Dateien schließen usw.
}
}
function testVerbindung() {
$verbindung = new Verbindung("beispiel.de");
// Verbindung wird verwendet...
// Am Ende der Funktion wird der Destruktor automatisch aufgerufen
}
testVerbindung();
// Ausgabe:
// Verbindung zu beispiel.de hergestellt
// Verbindung HANDLE_XXXX wird geschlossenDer Destruktor ist besonders nützlich für das Freigeben von Ressourcen oder das Aufräumen, bevor ein Objekt endgültig entfernt wird.
Diese Methoden ermöglichen die Überwachung und Kontrolle des Zugriffs auf Objekteigenschaften, insbesondere auf Eigenschaften, die nicht direkt zugänglich sind.
Wird aufgerufen, wenn auf eine nicht zugängliche Eigenschaft zugegriffen wird:
class User {
private array $daten = [
'name' => 'Unbekannt',
'email' => 'keine@beispiel.de',
'rolle' => 'Gast'
];
public function __get(string $name) {
if (array_key_exists($name, $this->daten)) {
return $this->daten[$name];
}
$klassenmethode = 'get' . ucfirst($name);
if (method_exists($this, $klassenmethode)) {
return $this->$klassenmethode();
}
throw new Exception("Eigenschaft {$name} existiert nicht");
}
private function getVollständigerName(): string {
return $this->daten['name'] . " (" . $this->daten['rolle'] . ")";
}
}
$user = new User();
echo $user->name; // Ausgabe: Unbekannt
echo $user->vollständigerName; // Ausgabe: Unbekannt (Gast)Wird aufgerufen, wenn einer nicht zugänglichen Eigenschaft ein Wert zugewiesen wird:
class User {
private array $daten = [
'name' => 'Unbekannt',
'email' => 'keine@beispiel.de',
'rolle' => 'Gast'
];
private array $erlaubteEigenschaften = ['name', 'email', 'rolle'];
public function __set(string $name, $wert) {
if (in_array($name, $this->erlaubteEigenschaften)) {
// Hier könnte auch Validierung stattfinden
$this->daten[$name] = $wert;
} else {
throw new Exception("Die Eigenschaft {$name} kann nicht gesetzt werden");
}
}
}
$user = new User();
$user->name = "Max Mustermann"; // Setzt $daten['name'] = "Max Mustermann"
$user->passwort = "geheim"; // Wirft Exception: Die Eigenschaft passwort kann nicht gesetzt werdenWird aufgerufen, wenn isset() oder empty()
für eine nicht zugängliche Eigenschaft verwendet wird:
class Konfiguration {
private array $einstellungen = [
'debug' => true,
'cache' => false,
'timeout' => 30
];
public function __isset(string $name): bool {
return array_key_exists($name, $this->einstellungen);
}
}
$config = new Konfiguration();
var_dump(isset($config->debug)); // Ausgabe: bool(true)
var_dump(isset($config->unbekannt)); // Ausgabe: bool(false)Wird aufgerufen, wenn unset() für eine nicht zugängliche
Eigenschaft verwendet wird:
class Konfiguration {
private array $einstellungen = [
'debug' => true,
'cache' => false,
'timeout' => 30
];
public function __isset(string $name): bool {
return array_key_exists($name, $this->einstellungen);
}
public function __unset(string $name): void {
if (array_key_exists($name, $this->einstellungen)) {
unset($this->einstellungen[$name]);
}
}
}
$config = new Konfiguration();
unset($config->debug); // Entfernt 'debug' aus dem $einstellungen-ArrayDiese Methoden werden aufgerufen, wenn nicht existierende oder nicht zugängliche Methoden aufgerufen werden.
Wird aufgerufen, wenn eine nicht zugängliche Instanzmethode aufgerufen wird:
class ServiceProxy {
private object $service;
public function __construct(object $service) {
$this->service = $service;
}
public function __call(string $name, array $argumente) {
if (method_exists($this->service, $name)) {
// Delegiert den Aufruf an den zugrunde liegenden Service
return call_user_func_array([$this->service, $name], $argumente);
}
throw new Exception("Die Methode {$name} existiert nicht");
}
}
class UserService {
public function findById(int $id): array {
return ['id' => $id, 'name' => 'Benutzer ' . $id];
}
}
$proxy = new ServiceProxy(new UserService());
$user = $proxy->findById(123); // Ruft UserService::findById(123) aufWird aufgerufen, wenn eine nicht zugängliche statische Methode aufgerufen wird:
class Model {
protected static array $finderMethoden = ['findByName', 'findByEmail', 'findByRole'];
public static function __callStatic(string $name, array $argumente) {
// Prüft, ob der Methodenname ein bekannter Finder ist
if (in_array($name, static::$finderMethoden)) {
$feldName = strtolower(substr($name, 7)); // Entfernt "findBy" vom Methodennamen
$wert = $argumente[0] ?? null;
return static::findWhere($feldName, $wert);
}
throw new Exception("Die statische Methode {$name} existiert nicht");
}
protected static function findWhere(string $feld, $wert): array {
echo "Suche nach {$feld} = {$wert}\n";
// In einer realen Anwendung würde hier eine Datenbankabfrage erfolgen
return ['id' => 1, $feld => $wert];
}
}
$benutzer = Model::findByName("Max"); // Ausgabe: Suche nach name = MaxDiese Methoden kontrollieren, wie ein Objekt serialisiert und deserialisiert wird.
Wird aufgerufen, bevor ein Objekt serialisiert wird. Es sollte ein Array mit den Namen der zu serialisierenden Eigenschaften zurückgeben:
class Verbindung {
private string $server;
private string $benutzer;
private string $passwort;
private $handle;
public function __construct(string $server, string $benutzer, string $passwort) {
$this->server = $server;
$this->benutzer = $benutzer;
$this->passwort = $passwort;
$this->verbinden();
}
private function verbinden() {
$this->handle = "HANDLE_" . rand(1000, 9999);
echo "Verbindung hergestellt: {$this->handle}\n";
}
public function __sleep(): array {
// Nur diese Eigenschaften werden serialisiert
// $handle wird ausgelassen, da es nicht sinnvoll ist, einen Verbindungshandle zu serialisieren
return ['server', 'benutzer', 'passwort'];
}
}
$verbindung = new Verbindung("db.beispiel.de", "admin", "geheim");
$serialisiert = serialize($verbindung);Wird aufgerufen, nachdem ein Objekt deserialisiert wurde. Es wird häufig verwendet, um Ressourcen wiederherzustellen oder Verbindungen neu zu öffnen:
class Verbindung {
private string $server;
private string $benutzer;
private string $passwort;
private $handle;
public function __construct(string $server, string $benutzer, string $passwort) {
$this->server = $server;
$this->benutzer = $benutzer;
$this->passwort = $passwort;
$this->verbinden();
}
private function verbinden() {
$this->handle = "HANDLE_" . rand(1000, 9999);
echo "Verbindung hergestellt: {$this->handle}\n";
}
public function __sleep(): array {
return ['server', 'benutzer', 'passwort'];
}
public function __wakeup(): void {
// Stellt die Verbindung nach der Deserialisierung wieder her
$this->verbinden();
}
}
$verbindung = new Verbindung("db.beispiel.de", "admin", "geheim");
$serialisiert = serialize($verbindung);
$neueVerbindung = unserialize($serialisiert); // Ruft __wakeup() auf und stellt die Verbindung wieder herDiese neueren Methoden bieten mehr Kontrolle über den Serialisierungsprozess:
class Benutzer {
private string $name;
private string $email;
private string $passwort;
public function __construct(string $name, string $email, string $passwort) {
$this->name = $name;
$this->email = $email;
$this->passwort = $passwort;
}
public function __serialize(): array {
// Passwort wird vor der Serialisierung entfernt
return [
'name' => $this->name,
'email' => $this->email,
// passwort wird absichtlich ausgelassen
];
}
public function __unserialize(array $data): void {
$this->name = $data['name'];
$this->email = $data['email'];
$this->passwort = ''; // Setzt das Passwort zurück
}
}Wird aufgerufen, wenn ein Objekt in einen String umgewandelt wird:
class Adresse {
private string $straße;
private string $hausnummer;
private string $plz;
private string $ort;
public function __construct(string $straße, string $hausnummer, string $plz, string $ort) {
$this->straße = $straße;
$this->hausnummer = $hausnummer;
$this->plz = $plz;
$this->ort = $ort;
}
public function __toString(): string {
return "{$this->straße} {$this->hausnummer}, {$this->plz} {$this->ort}";
}
}
$adresse = new Adresse("Hauptstraße", "42", "12345", "Musterstadt");
echo $adresse; // Ausgabe: Hauptstraße 42, 12345 MusterstadtErmöglicht es, ein Objekt wie eine Funktion aufzurufen:
class Kalkulator {
private float $ergebnis = 0;
public function __invoke(float $a, float $b, string $operation = '+'): float {
switch ($operation) {
case '+':
$this->ergebnis = $a + $b;
break;
case '-':
$this->ergebnis = $a - $b;
break;
case '*':
$this->ergebnis = $a * $b;
break;
case '/':
if ($b === 0.0) {
throw new Exception("Division durch Null");
}
$this->ergebnis = $a / $b;
break;
default:
throw new Exception("Unbekannte Operation: {$operation}");
}
return $this->ergebnis;
}
public function getErgebnis(): float {
return $this->ergebnis;
}
}
$rechner = new Kalkulator();
echo $rechner(5, 3, '+'); // Ausgabe: 8
echo $rechner(10, 2, '/'); // Ausgabe: 5Die __invoke()-Methode ist besonders nützlich für
Callable-Objekte, die als Callbacks verwendet werden können:
// Sortieren eines Arrays mit einem Callable-Objekt
class Sortierer {
private string $feld;
private string $richtung;
public function __construct(string $feld, string $richtung = 'ASC') {
$this->feld = $feld;
$this->richtung = strtoupper($richtung);
}
public function __invoke(array $a, array $b): int {
$vergleich = $a[$this->feld] <=> $b[$this->feld];
return $this->richtung === 'ASC' ? $vergleich : -$vergleich;
}
}
$daten = [
['id' => 3, 'name' => 'Charlie'],
['id' => 1, 'name' => 'Alice'],
['id' => 2, 'name' => 'Bob']
];
// Nach Namen aufsteigend sortieren
usort($daten, new Sortierer('name'));
print_r($daten);
// Nach ID absteigend sortieren
usort($daten, new Sortierer('id', 'DESC'));
print_r($daten);Wird aufgerufen, nachdem ein Objekt geklont wurde. Dies ist nützlich, um tiefe Kopien von Objekten zu erstellen:
class Post {
public string $titel;
public object $autor;
public array $kommentare;
public function __construct(string $titel, object $autor) {
$this->titel = $titel;
$this->autor = $autor;
$this->kommentare = [];
}
public function __clone() {
// Erstellt eine tiefe Kopie des Autor-Objekts
$this->autor = clone $this->autor;
// Erstellt neue Kopien der Kommentare
$alteKommentare = $this->kommentare;
$this->kommentare = [];
foreach ($alteKommentare as $kommentar) {
$this->kommentare[] = clone $kommentar;
}
}
}
class Autor {
public string $name;
public function __construct(string $name) {
$this->name = $name;
}
}
class Kommentar {
public string $text;
public function __construct(string $text) {
$this->text = $text;
}
}
$autor = new Autor("Max Mustermann");
$post = new Post("Magische Methoden in PHP", $autor);
$post->kommentare[] = new Kommentar("Toller Artikel!");
// Klonen erstellt eine tiefe Kopie
$kopie = clone $post;
$kopie->autor->name = "Erika Musterfrau"; // Ändert nur den Namen in der Kopie
$kopie->kommentare[0]->text = "Interessant!"; // Ändert nur den Kommentar in der Kopie
echo $post->autor->name; // Ausgabe: Max Mustermann
echo $kopie->autor->name; // Ausgabe: Erika Musterfrau
echo $post->kommentare[0]->text; // Ausgabe: Toller Artikel!
echo $kopie->kommentare[0]->text; // Ausgabe: Interessant!Die __debugInfo()-Methode wird von
var_dump() aufgerufen und gibt an, welche Informationen
angezeigt werden sollen:
class APIClient {
private string $apiKey;
private string $baseUrl;
private array $letzteAnfrage;
private array $letzteAntwort;
public function __construct(string $apiKey, string $baseUrl) {
$this->apiKey = $apiKey;
$this->baseUrl = $baseUrl;
$this->letzteAnfrage = [];
$this->letzteAntwort = [];
}
public function __debugInfo(): array {
return [
'baseUrl' => $this->baseUrl,
'apiKey' => '***versteckt***', // Versteckt den API-Schlüssel
'letzteAnfrage' => $this->letzteAnfrage,
'letzteAntwort' => $this->letzteAntwort
];
}
}
$client = new APIClient("geheimer-api-schlüssel", "https://api.beispiel.de");
var_dump($client); // Zeigt angepasste Debug-Informationen an, ohne den API-Schlüssel preiszugebenDie statische Methode __set_state() wird von
var_export() verwendet:
class Punkt {
public float $x;
public float $y;
public function __construct(float $x, float $y) {
$this->x = $x;
$this->y = $y;
}
public static function __set_state(array $properties): self {
return new self($properties['x'], $properties['y']);
}
}
$punkt = new Punkt(1.5, 2.5);
$exportiert = var_export($punkt, true);
echo $exportiert . PHP_EOL;
// Ausgabe: Punkt::__set_state(array('x' => 1.5, 'y' => 2.5))
// Der exportierte Code kann evaluiert werden, um das Objekt zu rekonstruieren
$neuerPunkt = eval("return " . $exportiert . ";");
echo "X: {$neuerPunkt->x}, Y: {$neuerPunkt->y}"; // Ausgabe: X: 1.5, Y: 2.5Im folgenden Beispiel werden mehrere magische Methoden verwendet, um ein einfaches aktives Datensatzmodell zu implementieren:
class Model {
protected array $attribute = [];
protected array $geändert = [];
protected bool $neu = true;
// Konstruktor zum Laden von Datensätzen aus der Datenbank
public function __construct(array $daten = []) {
foreach ($daten as $key => $value) {
$this->attribute[$key] = $value;
}
if (isset($this->attribute['id'])) {
$this->neu = false;
}
}
// Eigenschaftszugriff über magische Methoden
public function __get(string $name) {
return $this->attribute[$name] ?? null;
}
public function __set(string $name, $wert) {
$this->attribute[$name] = $wert;
$this->geändert[$name] = true;
}
public function __isset(string $name): bool {
return isset($this->attribute[$name]);
}
public function __unset(string $name): void {
unset($this->attribute[$name]);
$this->geändert[$name] = true;
}
// Methoden zum Speichern und Laden
public function speichern(): bool {
if ($this->neu) {
// INSERT-Operation simulieren
echo "INSERT INTO " . static::getTabelle() . " ("
. implode(", ", array_keys($this->attribute))
. ") VALUES ('"
. implode("', '", array_values($this->attribute))
. "')" . PHP_EOL;
// Simuliert das Setzen einer ID
$this->attribute['id'] = rand(1, 1000);
$this->neu = false;
} else {
// UPDATE-Operation simulieren
$updates = [];
foreach ($this->geändert as $key => $value) {
if (isset($this->attribute[$key])) {
$updates[] = "{$key} = '{$this->attribute[$key]}'";
}
}
if (!empty($updates)) {
echo "UPDATE " . static::getTabelle() . " SET "
. implode(", ", $updates)
. " WHERE id = {$this->attribute['id']}" . PHP_EOL;
}
}
$this->geändert = [];
return true;
}
public static function finden(int $id): ?self {
echo "SELECT * FROM " . static::getTabelle() . " WHERE id = {$id}" . PHP_EOL;
// Simuliert einen Datensatz aus der Datenbank
return new static([
'id' => $id,
'name' => "Datensatz {$id}",
'erstellt' => date('Y-m-d H:i:s')
]);
}
// Dynamische Finder-Methoden
public static function __callStatic(string $name, array $argumente) {
if (strpos($name, 'findeVon') === 0) {
$feld = lcfirst(substr($name, 8)); // Entfernt "findeVon" vom Methodennamen
$wert = $argumente[0] ?? null;
echo "SELECT * FROM " . static::getTabelle() . " WHERE {$feld} = '{$wert}'" . PHP_EOL;
// Simuliert einen Datensatz aus der Datenbank
return new static([
'id' => rand(1, 1000),
$feld => $wert,
'erstellt' => date('Y-m-d H:i:s')
]);
}
throw new Exception("Die statische Methode {$name} existiert nicht");
}
// Methode zum Konvertieren in einen String
public function __toString(): string {
$klasse = get_class($this);
$id = $this->attribute['id'] ?? 'neu';
return "{$klasse} #{$id}";
}
// Debug-Informationen
public function __debugInfo(): array {
return [
'id' => $this->attribute['id'] ?? null,
'attribute' => $this->attribute,
'geändert' => array_keys($this->geändert),
'neu' => $this->neu
];
}
// Tabellennamen ableiten (kann in Unterklassen überschrieben werden)
protected static function getTabelle(): string {
$klassenName = static::class;
$teile = explode("\\", $klassenName);
return strtolower(end($teile)) . 's';
}
}
// Beispiel für eine konkrete Modellklasse
class Benutzer extends Model {
protected static function getTabelle(): string {
return 'benutzer';
}
}
// Verwendung des Modells
$benutzer = new Benutzer(['name' => 'Max Mustermann', 'email' => 'max@beispiel.de']);
$benutzer->speichern(); // INSERT
$benutzer->email = 'neuemail@beispiel.de';
$benutzer->speichern(); // UPDATE
$gefundenerBenutzer = Benutzer::finden(42); // SELECT mit WHERE id = 42
$emailBenutzer = Benutzer::findeVonEmail('test@beispiel.de'); // SELECT mit WHERE email = '...'
echo $gefundenerBenutzer; // Benutzer #42
var_dump($emailBenutzer); // Debug-Informationen dank __debugInfo()Dieses Beispiel demonstriert, wie magische Methoden zusammen verwendet werden können, um eine elegante API für den Datenbankzugriff zu schaffen. Es verwendet:
__get(), __set(), __isset()
und __unset() für den Zugriff auf Attribute__callStatic() für dynamische Finder-Methoden__toString() für die String-Repräsentation__debugInfo() für angepasste Debug-Informationen