Magische Methoden sind Methoden, die PHPs Standardverhalten überschreiben,
wenn bestimmte Aktionen mit einem Objekt durchgeführt werden.
Achtung
Alle Methodennamen, die mit __ beginnen, sind durch
PHP reserviert. Es wird daher nicht empfohlen solche Methodennamen zu
verwenden, außer wenn das Verhalten von PHP überschrieben werden soll.
Falls Typdeklarationen in der Definition der magischen Methoden angegeben
werden, müssen diese identisch zu den Signaturen sein, die in diesem
Dokument beschrieben werden, andernfalls wird ein fataler Fehler
hervorgerufen. Vor PHP 8.0.0 wurde keine Diagnose ausgegeben. Allerdings
dürfen __construct() und
__destruct() keinen Rückgabetyp
deklarieren, sonst wird ein fataler Fehler ausgegeben.
serialize() prüft, ob die Klasse eine Funktion mit dem
magischen Namen __sleep() besitzt.
Wenn dem so ist, wird die Funktion vor jeder Serialisierung ausgeführt.
Sie kann das Objekt aufräumen und es wird von ihr erwartet, dass sie ein
Array mit den Namen aller Variablen zurückgibt, die serialisiert werden
sollen. Wenn die Methode nichts zurückgibt, wird null serialisiert und
eine E_NOTICE ausgegeben.
Hinweis:
__sleep() kann keine Namen von
privaten Eigenschaften in Elternklassen zurückgeben. Dies würde zu einem
Fehler der Stufe E_NOTICE führen. Stattdessen sollte
das Serializable-Interface verwendet werden.
Hinweis:
Seit PHP 8.0.0 erzeugt die Rückgabe eines Wertes von
__sleep(), der kein Array ist, eine
Warnung; vorher führte dies zu einem Hinweis.
Der Zweck von von __sleep() ist, nicht
gespeicherte Daten zu sichern oder ähnliche Aufräumarbeiten zu erledigen.
Die Funktion ist ebenfalls nützlich, wenn ein sehr großes Objekt nicht
komplett gespeichert werden muss.
Umgekehrt überprüft unserialize(), ob eine Funktion mit
dem magischen Namen __wakeup()
vorhanden ist. Falls vorhanden, kann diese Funktion alle Ressourcen, die
das Objekt möglicherweise hat, wiederherstellen.
Der Zweck von __wakeup() ist es, alle
Datenbankverbindungen, die bei der Serialisierung verlorengegangen sind,
wiederherzustellen und andere Aufgaben der erneuten Initialisierung
durchzuführen.
Beispiel #1 Sleep- und Wakeup-Beispiel
<?php class Connection { protected $link; private $dsn, $username, $password;
public function __construct($dsn, $username, $password) { $this->dsn = $dsn; $this->username = $username; $this->password = $password; $this->connect(); }
private function connect() { $this->link = new PDO($this->dsn, $this->username, $this->password); }
public function __sleep() { return array('dsn', 'username', 'password'); }
public function __wakeup() { $this->connect(); } }?>
serialize() prüft, ob die Klasse eine Funktion mit dem
magischen Namen __serialize()
besitzt. Wenn dem so ist, wird die Funktion vor jeder Serialisierung
ausgeführt. Sie muss ein assoziatives Array von Schlüssel/Wert-Paaren
erzeugen und zurückgeben, die die serialisierte Form des Objekts
darstellen. Wird kein Array zurückgegeben, wird ein
TypeError geworfen.
Hinweis:
Sind sowohl __serialize() als
auch __sleep() im selben Objekt
definiert, wird nur __serialize()
aufgerufen. __sleep() wird ignoriert.
Implementiert das Objekt das
Serializable-Interface, wird
die serialize()-Methode des Interfaces ignoriert und
stattdessen __serialize() verwendet.
Der Zweck von __serialize() ist,
eine für die Serialisierung geeignete beliebige Darstellung eines Objekts
zu definieren. Die Elemente des Arrays können den Eigenschaften des
Objekts entsprechen, aber das ist nicht erforderlich.
Umgekehrt überprüft unserialize(), ob eine Funktion mit
dem magischen Namen
__unserialize() vorhanden ist.
Falls vorhanden, wird dieser Funktion das wiederhergestellte Array
übergeben, das von __serialize()
zurückgegeben wurde. Sie kann dann gegebenenfalls die Eigenschaften des
Objekts aus diesem Array wiederherstellen.
Mit der Methode __toString() kann
eine Klasse entscheiden, wie sie reagieren soll, wenn sie wie eine
Zeichenkette behandelt wird. Die beeinflusst beispielsweise, was
echo $obj; ausgeben wird.
Warnung
Seit PHP 8.0.0 unterliegt der Rückgabewert dieser Methode der üblichen
PHP-Semantik für Typen. Das heißt, er bei deaktivierter
strikter Typisierung
nach Möglichkeit in einen String umgewandelt.
Wenn die
Strikte Typisierung
aktiviert ist, wird ein Stringable-Objekt
von einer string-Typdeklaration nicht
akzeptiert. Wenn ein solches Verhalten erwünscht ist, muss die
Typdeklaration Stringable und
string mittels Union-Typ akzeptieren.
Seit PHP 8.0.0 implementiert jede Klasse, die eine
__toString()-Methode enthält,
implizit auch das Interface Stringable und
erfüllt daher auch Typprüfungen auf dieses Interface. Es wird dennoch
empfohlen, dieses Interface explizit zu implementieren.
In PHP 7.4 muss der Rückgabewert ein Wert vom Typ
string sein, andernfalls wird ein
Error geworfen.
Vor PHP 7.4.0 muss der Rückgabewert ein Wert vom Typ
string sein, ansonsten wird ein Fehler der Stufe
E_RECOVERABLE_ERROR hervorgerufen.
Warnung
Es war vor PHP 7.4.0 nicht möglich eine Exception aus einer
__toString()-Methode zu werfen.
Dies resultierte in einem fatalen Fehler.
Beispiel #3 Einfaches Beispiel
<?php // Deklariere eine einfache Klasse class TestClass { public $foo;
public function __construct($foo) { $this->foo = $foo; }
public function __toString() { return $this->foo; } }
$class = new TestClass('Hallo'); echo $class; ?>
Das oben gezeigte Beispiel erzeugt folgende Ausgabe:
Hinweis:
Wenn ein Objekt exportiert wird, überprüft
var_export() nicht, ob
__set_state() von der
Objektklasse implementiert wird, sodass der erneute Import von Objekten
zu einer Error-Exception führt, falls
__set_state() nicht implementiert ist. Die betrifft insbesondere einige
interne Klassen.
Es liegt im Aufgabenbereich des Programmiers, sicherzustellen, dass nur
Objekte wieder re-importiert werden, welche auch __set_state()
implementieren.
Diese Methode wird von var_dump() aufgerufen, wenn
ein Objekt ausgegeben wird, um die Eigenschaften auszulesen die gezeigt
werden sollen. Wenn diese Methode in einem Objekt nicht definiert ist,
so werden alle Eigenschaften angezeigt, die public, protected oder private
sind.
The __toString() method is extremely useful for converting class attribute names and values into common string representations of data (of which there are many choices). I mention this as previous references to __toString() refer only to debugging uses.
I have previously used the __toString() method in the following ways:
- representing a data-holding object as: - XML - raw POST data - a GET query string - header name:value pairs
- representing a custom mail object as an actual email (headers then body, all correctly represented)
When creating a class, consider what possible standard string representations are available and, of those, which would be the most relevant with respect to the purpose of the class.
Being able to represent data-holding objects in standardised string forms makes it much easier for your internal representations of data to be shared in an interoperable way with other applications.
Please note that as of PHP 8.2 implementing __serialize() has no control over the output of json_encode(). you still have to implement JsonSerializable.
Be very careful to define __set_state() in classes which inherit from a parent using it, as the static __set_state() call will be called for any children. If you are not careful, you will end up with an object of the wrong type. Here is an example:
<?php
class A
{
public $var1;
public static function __set_state($an_array)
{
$obj = new A;
$obj->var1 = $an_array['var1'];
return $obj;
}
}
When you use sessions, its very important to keep the sessiondata small, due to low performance with unserialize. Every class shoud extend from this class. The result will be, that no null Values are written to the sessiondata. It will increase performance.
<? class BaseObject { function __sleep() { $vars = (array)$this; foreach ($vars as $key => $val) { if (is_null($val)) { unset($vars[$key]); } } return array_keys($vars); } }; ?>
providing a object as a array index doesn't try to us __toString() method so some volatile object identifier is used to index the array, which is breaking any persistency. Type hinting solves that, but while other than "string" type hinting doesn't work on ob jects, the automatic conversion to string should be very intuitive.
PS: tried to submit bug, but withot patch the bugs are ignored, unfortunately, I don't C coding
<?php
class shop_product_id {
protected $shop_name; protected $product_id;
function __construct($shop_name,$product_id){ $this->shop_name = $shop_name; $this->product_id = $product_id; }
function __toString(){ return $this->shop_name . ':' . $this->product_id; } }
Intriguing what happens when __sleep() and __wakeup() and sessions() are mixed. I had a hunch that, as session data is serialized, __sleep would be called when an object, or whatever, is stored in _SESSION. true. The same hunch applied when session_start() was called. Would __wakeup() be called? True. Very helpful, specifically as I'm building massive objects (well, lots of simple objects stored in sessions), and need lots of automated tasks (potentially) reloaded at "wakeup" time. (for instance, restarting a database session/connection).
One of the principles of OOP is encapsulation--the idea that an object should handle its own data and no others'. Asking base classes to take care of subclasses' data, esp considering that a class can't possibly know how many dozens of ways it will be extended, is irresponsible and dangerous.
Consider the following...
<?php class SomeStupidStorageClass { public function getContents($pos, $len) { ...stuff... } }
class CryptedStorageClass extends SomeStupidStorageClass { private $decrypted_block; public function getContents($pos, $len) { ...decrypt... } } ?>
If SomeStupidStorageClass decided to serialize its subclasses' data as well as its own, a portion of what was once an encrypted thingie could be stored, in the clear, wherever the thingie was stored. Obviously, CryptedStorageClass would never have chosen this...but it had to either know how to serialize its parent class's data without calling parent::_sleep(), or let the base class do what it wanted to.
Considering encapsulation again, no class should have to know how the parent handles its own private data. And it certainly shouldn't have to worry that users will find a way to break access controls in the name of convenience.
If a class wants both to have private/protected data and to survive serialization, it should have its own __sleep() method which asks the parent to report its own fields and then adds to the list if applicable. Like so....
<?php
class BetterClass { private $content;
public function __sleep() { return array('basedata1', 'basedata2'); }
public function getContents() { ...stuff... } }
class BetterDerivedClass extends BetterClass { private $decrypted_block;
public function __sleep() { return parent::__sleep(); }
public function getContents() { ...decrypt... } }
?>
The derived class has better control over its data, and we don't have to worry about something being stored that shouldn't be.
If you use the Magical Method '__set()', be shure that the call of <?php $myobject->test['myarray'] = 'data'; ?> will not appear!
For that u have to do it the fine way if you want to use __set Method ;) <?php $myobject->test = array('myarray' => 'data'); ?>
If a Variable is already set, the __set Magic Method already wont appear!
My first solution was to use a Caller Class. With that, i ever knew which Module i currently use! But who needs it... :] There are quiet better solutions for this... Here's the Code:
<?php class Caller { public $caller; public $module;
function __call($funcname, $args = array()) { $this->setModuleInformation();
if (is_object($this->caller) && function_exists('call_user_func_array')) $return = call_user_func_array(array(&$this->caller, $funcname), $args); else trigger_error("Call to Function with call_user_func_array failed", E_USER_ERROR);
function __construct($callerClassName = false, $callerModuleName = 'Webboard') { if ($callerClassName == false) trigger_error('No Classname', E_USER_ERROR);
$this->module = $callerModuleName;
if (class_exists($callerClassName)) $this->caller = new $callerClassName(); else trigger_error('Class not exists: \''.$callerClassName.'\'', E_USER_ERROR);
if (is_object($this->caller)) { $this->setModuleInformation(); if (method_exists($this->caller, '__init')) $this->caller->__init(); $this->unsetModuleInformation(); } else trigger_error('Caller is no object!', E_USER_ERROR); }
function __destruct() { $this->setModuleInformation(); if (method_exists($this->caller, '__deinit')) $this->caller->__deinit(); $this->unsetModuleInformation(); }
function __isset($isset) { $this->setModuleInformation(); if (is_object($this->caller)) $return = isset($this->caller->{$isset}); else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); return $return; }
function __unset($unset) { $this->setModuleInformation(); if (is_object($this->caller)) { if (isset($this->caller->{$unset})) unset($this->caller->{$unset}); } else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); }
function __set($set, $val) { $this->setModuleInformation(); if (is_object($this->caller)) $this->caller->{$set} = $val; else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); }
function __get($get) { $this->setModuleInformation(); if (is_object($this->caller)) { if (isset($this->caller->{$get})) $return = $this->caller->{$get}; else $return = false; } else trigger_error('Caller is no object!', E_USER_ERROR); $this->unsetModuleInformation(); return $return; }
function setModuleInformation() { $this->caller->module = $this->module; }
function unsetModuleInformation() { $this->caller->module = NULL; } }
// Well this can be a Config Class? class Config { public $module;
public $test;
function __construct() { print('Constructor will have no Module Information... Use __init() instead!<br />'); print('--> '.print_r($this->module, 1).' <--'); print('<br />'); print('<br />'); $this->test = '123'; }
function __init() { print('Using of __init()!<br />'); print('--> '.print_r($this->module, 1).' <--'); print('<br />'); print('<br />'); }
function testFunction($test = false) { if ($test != false) $this->test = $test; } }