マジックメソッド
マジックメソッドは、 ある動作がオブジェクトに対して行われた場合に、 PHP のデフォルトの動作を上書きする特別なメソッドです。
__
で始まる全てのメソッドは、
PHP によって予約されています。
よって、PHP の動作を上書きするのでなければ、
このようなメソッド名を使うことは推奨されません。
以下の関数名は、マジックメソッドと見なされます: __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __serialize(), __unserialize(), __toString(), __invoke(), __set_state(), __clone(), __debugInfo()
__construct(),
__destruct(),
__clone() を除く全てのマジックメソッドは、
public
として宣言しなければいけません。
そうしないと、
E_WARNING
レベルの警告が発生します。
PHP 8.0.0 より前のバージョンでは、
__sleep(),
__wakeup(),
__serialize(),
__unserialize(),
__set_state() については、エラーは発生していませんでした。
マジックメソッドの定義で型宣言が使われている場合、 それらは、このマニュアルで説明しているシグネチャと同じでなければいけません。 そうでない場合、致命的なエラーが発生します。 PHP 8.0.0 より前のバージョンでは、 エラーは全く発生していませんでした。 但し、__construct() と __destruct() については、戻り値の型を宣言してはいけません。 宣言してしまうと、致命的なエラーが発生します。
__sleep() と __wakeup()
serialize() は、クラスに特殊な名前
__sleep() の関数があるかどうかを調べます。
もしあれば、シリアル化の前にその関数を実行します。
この関数で、オブジェクトをクリアすることができます。
またこの関数は、シリアル化するオブジェクトについて、
すべての変数の名前を配列で返すことが前提となっています。
このメソッドが何も返さなかった場合は、null
がシリアル化され、E_NOTICE
が発生します。
注意:
__sleep() で、親クラスの private プロパティの名前を返すことはできません。 そうしようとすると
E_NOTICE
レベルのエラーとなります。 この場合は、__serialize() を代わりに使って下さい。
注意:
PHP 8.0.0 以降では、 __sleep() から配列でない値を返すと、 E_WARNING が発生するようになりました。これより前のバージョンでは、 E_NOTICE が発生していました。
典型的な __sleep() の使用法は、 途中のデータをコミットしたり、 似たようなタスクのクリアを行うといったものです。 また、オブジェクトが非常に大きく、かつ、完全に保存する必要がない場合、 この関数が有用です。
逆に、unserialize() は、 特殊な名前 __wakeup() を有する 関数の存在を調べます。 もし存在する場合、この関数は、オブジェクトが有する可能性が あるあらゆるリソースを再構築することができます。
__wakeup() の使用目的は、 シリアル化の際に失われたデータベース接続を再度確立したり、 その他の再初期化を行うことです。
例1 sleep と wakeup
<?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() と __unserialize()
serialize() は、クラスが 特殊な名前 __serialize() を持つかを調べます。 もしあれば、シリアル化の前にその関数を実行します。 この関数は、オブジェクトをシリアル化した状態を示すキー/値のペアを連想配列にして返さなければなりません。 配列を返さなかった場合、TypeError がスローされます。
注意:
__serialize() と __sleep() が両方同じオブジェクトに定義されていた場合、 __serialize() だけが呼び出されます。 __sleep() は無視されます。 オブジェクトが Serializable インターフェイスを実装していた場合、 インターフェイスの
serialize()
メソッドは無視され、 __serialize() が代わりに使われます。
__serialize() の目的は、 任意のオブジェクトの表現をシリアライズしやすいように定義することです。 配列の要素はオブジェクトのプロパティに対応していても構いませんが、必須ではありません。
逆に、unserialize() は 特殊な名前 __unserialize() を持つかを調べます。 もしあれば、__serialize() が返した配列を復元し、この関数に渡します。 この関数では、その配列から必要に応じてオブジェクトのプロパティを復元して構いません。
注意:
__unserialize() と __wakeup() が両方同じオブジェクトに定義されていた場合、 __unserialize() だけが呼び出されます。 __wakeup() は無視されます。
注意:
この機能は PHP 7.4.0 以降で利用できます。
例2 Serialize と unserialize
<?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 __serialize(): array
{
return [
'dsn' => $this->dsn,
'user' => $this->username,
'pass' => $this->password,
];
}
public function __unserialize(array $data): void
{
$this->dsn = $data['dsn'];
$this->username = $data['user'];
$this->password = $data['pass'];
$this->connect();
}
}?>
__toString()
__toString() メソッドにより、
クラスが文字列に変換される際の動作を決めることができます。たとえば
echo $obj;
としたときに何を表示させるかといったことです。
PHP 8.0.0 以降では、戻り値は PHP で標準的な型のセマンティクスに従います。 つまり、厳密な型付け が無効になっている場合、可能であれば文字列型に強制されます。
厳密な型付け が有効になっている場合、string 型を宣言していても Stringable は受け入れられ ません。 受け入れさせたい場合、Stringable と string を union 型を使って型宣言しなければいけません。
PHP 8.0.0 以降では、 __toString() メソッドを含む全てのクラスは、 暗黙のうちに Stringable インターフェイスも実装するようになりました。 よって、このインターフェイスの型チェックは通過することになります。 いずれにせよ、明示的にこのインターフェイスを実装することを推奨します。
PHP 7.4 では、戻り値は文字列でなければいけません。 そうでない場合、Error がスローされます。
PHP 7.4.0 より前のバージョンでは、
戻り値は文字列でなければいけません。
そうでない場合、E_RECOVERABLE_ERROR
が発生します。
PHP 7.4.0 より前のバージョンでは、__toString() メソッド内から例外をスローすることはできません。そうした場合、致命的なエラーが発生します。
例3 簡単な例
<?php
// 簡単なクラスを宣言
class TestClass
{
public $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
public function __toString()
{
return $this->foo;
}
}
$class = new TestClass('Hello');
echo $class;
?>
上の例の出力は以下となります。
Hello
__invoke()
__invoke() メソッドは、 スクリプトがオブジェクトを関数としてコールしようとした際にコールされます。
例4 __invoke() の使用
<?php
class CallableClass
{
public function __invoke($x)
{
var_dump($x);
}
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
?>
上の例の出力は以下となります。
int(5) bool(true)
例5 __invoke() の使用
<?php
class Sort
{
private $key;
public function __construct(string $key)
{
$this->key = $key;
}
public function __invoke(array $a, array $b): int
{
return $a[$this->key] <=> $b[$this->key];
}
}
$customers = [
['id' => 1, 'first_name' => 'John', 'last_name' => 'Do'],
['id' => 3, 'first_name' => 'Alice', 'last_name' => 'Gustav'],
['id' => 2, 'first_name' => 'Bob', 'last_name' => 'Filipe']
];
// $customers を 'first_name' でソートします
usort($customers, new Sort('first_name'));
print_r($customers);
// $customers を 'last_name' でソートします
usort($customers, new Sort('last_name'));
print_r($customers);
?>
上の例の出力は以下となります。
Array ( [0] => Array ( [id] => 3 [first_name] => Alice [last_name] => Gustav ) [1] => Array ( [id] => 2 [first_name] => Bob [last_name] => Filipe ) [2] => Array ( [id] => 1 [first_name] => John [last_name] => Do ) ) Array ( [0] => Array ( [id] => 1 [first_name] => John [last_name] => Do ) [1] => Array ( [id] => 2 [first_name] => Bob [last_name] => Filipe ) [2] => Array ( [id] => 3 [first_name] => Alice [last_name] => Gustav ) )
__set_state()
この static メソッドは、 var_export() によって エクスポートされたクラスのためにコールされます。
このメソッドの唯一のパラメータは、エクスポートされたプロパティを
['property' => value, ...]
の形式で保持する配列です。
例6 __set_state() の使用法
<?php
class A
{
public $var1;
public $var2;
public static function __set_state($an_array)
{
$obj = new A;
$obj->var1 = $an_array['var1'];
$obj->var2 = $an_array['var2'];
return $obj;
}
}
$a = new A;
$a->var1 = 5;
$a->var2 = 'foo';
$b = var_export($a, true);
var_dump($b);
eval('$c = ' . $b . ';');
var_dump($c);
?>
上の例の出力は以下となります。
string(60) "A::__set_state(array( 'var1' => 5, 'var2' => 'foo', ))" object(A)#2 (2) { ["var1"]=> int(5) ["var2"]=> string(3) "foo" }
注意: var_export() でオブジェクトをエクスポートする際に、 そのオブジェクトのクラスが __set_state() を実装しているかどうかはチェックされません。 よって、__set_state() が実装されていない場合、 エクスポートしたオブジェクトを再度インポートしようとすると、 Error 例外が発生します。 この振る舞いは、特に一部の内部クラスを扱う際に影響が出てきます。 エクスポートしたオブジェクトをインポートする際に、 __set_state() を実装するクラスのものだけをインポートさせるようにするのは、プログラマーの責務となります。
__debugInfo()
このメソッドは、var_dump() がオブジェクトをダンプするときに、 プロパティの情報を取得するために呼ばれます。 もしオブジェクトにこのメソッドが定義されていなければ、 すべての public, protected, private プロパティを表示します。
例7 __debugInfo() の使用法
<?php
class C {
private $prop;
public function __construct($val) {
$this->prop = $val;
}
public function __debugInfo() {
return [
'propSquared' => $this->prop ** 2,
];
}
}
var_dump(new C(42));
?>
上の例の出力は以下となります。
object(C)#1 (1) { ["propSquared"]=> int(1764) }
User Contributed Notes 10 notes
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;
}
}
class B extends A {
}
$b = new B;
$b->var1 = 5;
eval('$new_b = ' . var_export($b, true) . ';');
var_dump($new_b);
/*
object(A)#2 (1) {
["var1"]=>
int(5)
}
*/
?>
__debugInfo is also utilised when calling print_r on an object:
$ cat test.php
<?php
class FooQ {
private $bar = '';
public function __construct($val) {
$this->bar = $val;
}
public function __debugInfo()
{
return ['_bar' => $this->bar];
}
}
$fooq = new FooQ("q");
print_r ($fooq);
$ php test.php
FooQ Object
(
[_bar] => q
)
$
Due to a bug in PHP <= 7.3, overriding the __debugInfo() method from SPL classes is silently ignored.
<?php
class Debuggable extends ArrayObject {
public function __debugInfo() {
return ['special' => 'This should show up'];
}
}
var_dump(new Debuggable());
// Expected output:
// object(Debuggable)#1 (1) {
// ["special"]=>
// string(19) "This should show up"
// }
// Actual output:
// object(Debuggable)#1 (1) {
// ["storage":"ArrayObject":private]=>
// array(0) {
// }
// }
?>
Bug report: https://bugs.php.net/bug.php?id=69264
http://sandbox.onlinephpfunctions.com/code/4d2cc3648aed58c0dad90c7868173a4775e5ba0c
IMHO a bug or need feature change
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;
}
}
$shop_name = 'Shop_A';
$product_id = 123;
$demo_id = $shop_name . ':' . $product_id;
$demo_name = 'Some product in shop A';
$all_products = [ $demo_id => $demo_name ];
$pid = new shop_product_id( $shop_name, $product_id );
echo "with type hinting: ";
echo ($demo_name === $all_products[(string)$pid]) ? "ok" : "fail";
echo "\n";
echo "without type hinting: ";
echo ($demo_name === $all_products[$pid]) ? "ok" : "fail";
echo "\n";
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);
}
};
?>
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);
$this->unsetModuleInformation();
return $return;
}
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;
}
}
echo('<pre>');
$wow = new Caller('Config', 'Guestbook');
print_r($wow->test);
print('<br />');
print('<br />');
$wow->test = '456';
print_r($wow->test);
print('<br />');
print('<br />');
$wow->testFunction('789');
print_r($wow->test);
print('<br />');
print('<br />');
print_r($wow->module);
echo('</pre>');
?>
Outputs something Like:
Constructor will have no Module Information... Use __init() instead!
--> <--
Using of __init()!
--> Guestbook <--
123
456
789
Guestbook
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.