PHP魔术方法
__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __serialize(), __unserialize(), __toString(), __invoke(), __set_state(), __clone() 和 __debugInfo() 等方法在 PHP 中被称为魔术方法(Magic methods)。在命名自己的类方法时不能使用这些方法名,除非是想使用其魔术功能。
注意: 所有的魔术方法 必须 声明为 public
警告
PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以 __ 为前缀。
__sleep() 和 __wakeup()
public __sleep ( ) : array
public __wakeup ( ) : void
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 null 被序列化,并产生一个 E_NOTICE 级别的错误。
注意:
__sleep() 不能返回父类的私有成员的名字。这样做会产生一个 E_NOTICE 级别的错误。可以用 Serializable 接口来替代。
__sleep() 方法常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。
与之相反,unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。
__wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
示例 #1 Sleep 和 wakeup
<?php class Connection { protected $link; private $server, $username, $password, $db; public function __construct($server, $username, $password, $db) { $this->server = $server; $this->username = $username; $this->password = $password; $this->db = $db; $this->connect(); } private function connect() { $this->link = mysql_connect($this->server, $this->username, $this->password); mysql_select_db($this->db, $this->link); } public function __sleep() { return array('server', 'username', 'password', 'db'); } public function __wakeup() { $this->connect(); } } ?>__serialize() 和 __unserialize()
public __serialize ( ) : array
public __unserialize ( array $data ) : void
serialize() 函数会检查类中是否存在一个魔术方法 __serialize()。如果存在,该方法将在任何序列化之前优先执行。它必须以一个代表对象序列化形式的 键/值 成对的关联数组形式来返回,如果没有返回数组,将会抛出一个 TypeError 错误。
注意:
如果类中同时定义了 __serialize() 和 __sleep() 两个魔术方法,则只有 __serialize() 方法会被调用。 __sleep() 方法会被忽略掉。如果对象实现了 Serializable 接口,接口的 serialize() 方法会被忽略,做为代替类中的 __serialize() 方法会被调用。
The intended use of __serialize() is to define a serialization-friendly arbitrary representation of the object. Elements of the array may correspond to properties of the object but that is not required.
Conversely, unserialize() checks for the presence of a function with the magic name __unserialize(). If present, this function will be passed the restored array that was returned from __serialize(). It may then restore the properties of the object from that array as appropriate.
注意:
如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法,则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。
注意:
此特性自 PHP 7.4.0 起可用。
示例 #2 序列化和反序列化
<?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()
public __toString ( ) : string
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
警告
不能在 __toString() 方法中抛出异常。这么做会导致致命错误。
示例 #3 简单示例
<?php // Declare a simple class class TestClass { public $foo; public function __construct($foo) { $this->foo = $foo; } public function __toString() { return $this->foo; } } $class = new TestClass('Hello'); echo $class; ?>以上例程会输出:
Hello
需要指出的是在 PHP 5.2.0 之前,__toString() 方法只有在直接使用于 echo 或 print 时才能生效。PHP 5.2.0 之后,则可以在任何字符串环境生效(例如通过 printf(),使用 %s 修饰符),但不能用于非字符串环境(如使用 %d 修饰符)。自 PHP 5.2.0 起,如果将一个未定义 __toString() 方法的对象转换为字符串,会产生 E_RECOVERABLE_ERROR 级别的错误。
__invoke()
__invoke ( $… = ? ) : mixed
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
注意:
本特性只在 PHP 5.3.0 及以上版本有效。
示例 #4 使用 __invoke()
<?php class CallableClass { function __invoke($x) { var_dump($x); } } $obj = new CallableClass; $obj(5); var_dump(is_callable($obj)); ?>以上例程会输出:
int(5)
bool(true)
__set_state()
static __set_state ( array $properties ) : object
自 PHP 5.1.0 起当调用 var_export() 导出类时,此静态 方法会被调用。
本方法的唯一参数是一个数组,其中包含按 array(‘property’ => value, …) 格式排列的类属性。
示例 #5 使用 __set_state()>(PHP 5.1.0 起)
<?php class A { public $var1; public $var2; public static function __set_state($an_array) // As of PHP 5.1.0 { $obj = new A; $obj->var1 = $an_array['var1']; $obj->var2 = $an_array['var2']; return $obj; } } $a = new A; $a->var1 = 5; $a->var2 = 'foo'; eval('$b = ' . var_export($a, true) . ';'); // $b = A::__set_state(array( // 'var1' => 5, // 'var2' => 'foo', // )); var_dump($b); ?>以上例程会输出:
object(A)#2 (2) {
[“var1”]=>
int(5)
[“var2”]=>
string(3) “foo”
}
__debugInfo()
__debugInfo ( ) : array
This method is called by var_dump() when dumping an object to get the properties that should be shown. If the method isn’t defined on an object, then all public, protected and private properties will be shown.
This feature was added in PHP 5.6.0.
示例 #6 Using __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©#1 (1) {
[“propSquared”]=>
int(1764)
}
add a note add a note
User Contributed Notes 30 notes
up
down
31jon at webignition dot net ¶12 years ago
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.
up
down
17jsnell at e-normous dot com ¶12 years ago
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:
up
down
2ctamayo at sitecrafting dot com ¶6 months ago
Due to a bug in PHP <= 7.3, overriding the __debugInfo() method from SPL classes is silently ignored.
Bug report: https://bugs.php.net/bug.php?id=69264
up
down
6smiley at HELLOSPAMBOT dot chillerlan dot net ¶5 years ago
A simple API wrapper, using __call() and the PHP 5.6 “…” token.
http://php.net/manual/functions.arguments.php#functions.variable-arg-list
up
down
5dhuseby domain getback tld com ¶13 years ago
The above hint for using array_keys((array)$obj) got me investigating how to get __sleep to really work with object hierarchies.
With PHP 5.2.3, If you want to serialize an object that is part of an object hierarchy and you want to selectively serialize members (public, private, and protected) by manually specifying the array of members, there are a few simple rules for naming members that you must follow:
- public members should be named using just their member name, like so:
- protected members should be named using “\0” . “*” . “\0” . member name, like so:
- private members should be named using “\0” . class name . “\0” . member name, like so:
So with this information let us serialize a class hierarchy correctly:
<?php class Base { private $foo = "foo_value"; protected $bar = "bar_value"; public function __sleep() { return array("\0Base\0foo", "\0*\0bar"); } } class Derived extends Base { public $baz = "baz_value"; private $boo = "boo_value"; public function __sleep() { // we have to merge our members with our parent's return array_merge(array("baz", "\0Derived\0boo"), parent::__sleep()); } } class Leaf extends Derived { private $qux = "qux_value"; protected $zaz = "zaz_value"; public $blah = "blah_value"; public function __sleep() { // again, merge our members with our parent's return array_merge(array("\0Leaf\0qux", "\0*\0zaz", "blah"), parent::__sleep()); } } // test it $test = new Leaf(); $s = serialize($test); $test2 = unserialize($s); echo $s; print_r($test); print_r($test2); ?>Now if you comment out all of the __sleep() functions and output the serialized string, you will see that the output doesn’t change. The most important part of course is that with the proper __sleep() functions, we can unserialize the string and get a properly set up object.
I hope this solves the mystery for everybody. __sleep() does work, if you use it correctly 😃
up
down
2rayRO ¶14 years ago
If you use the Magical Method ‘__set()’, be shure that the call of
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:
'); print('--> '.print_r($this->module, 1).' <--'); print('
'); print('
'); $this->test = '123'; } function __init() { print('Using of __init()!
'); print('--> '.print_r($this->module, 1).' <--'); print('
'); print('
'); } function testFunction($test = false) { if ($test != false) $this->test = $test; } } echo('
'); $wow = new Caller('Config', 'Guestbook'); print_r($wow->test); print(''); ?>
'); print('
'); $wow->test = '456'; print_r($wow->test); print('
'); print('
'); $wow->testFunction('789'); print_r($wow->test); print('
'); print('
'); print_r($wow->module); echo('
Outputs something Like:
Constructor will have no Module Information… Use __init() instead!
–> <–
Using of __init()!
–> Guestbook <–
123
456
789
Guestbook
up
down
3ddavenport at newagedigital dot com ¶16 years ago
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.
up
down
1jeffxlevy at gmail dot com ¶15 years ago
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).
up
down
0vali dot dr at gmail dot com ¶27 days ago
It should be noted that if you unset a class typed property and then try to access it, __get will be called. But it MUST return the original type.
https://wiki.php.net/rfc/typed_properties_v2#overloaded_properties
up
down
0ricasiano at gmail dot com ¶2 months ago
Objects with __toString() passed within methods with type declarations(either from function arguments or return types) automagically converts it to string.
Even though array_keys includes more information about the variable names than just the variable names – it still seems to work appropriately.
up
down
-1staff at pro-unreal dot de ¶7 years ago
To avoid instanciating the parent instead of the inherited class for __set_state() as reported by jsnell, you could use late static binding introduced in PHP 5.3:
up
down
-2osbertv at yahoo dot com ¶9 years ago
Invoking a class inside a class results in an error.
returns
Invoking B() Class
PHP Fatal error: Call to undefined method B::a()
up
down
-1Wesley ¶9 years ago
Warning __toString can be triggerd more then one time
wich cause a performance issue since it will gather all data twice.
what i used as a hotfix:
<?php __toString(){ if(null === $this->sToString) $this->sToString = $this->_show(); return $this->sToString; } ?>up
down
-1Anonymous ¶12 years ago
Serializing objects is problematic with references. This is solved redefining the __sleep() magic method. This is also problematic when parent class has private variables since the parent object is not accessible nor its private variables from within the child object.
I found a solution that seems working for classes that implements this __sleep() method, and for its subclasses. Without more work in subclasses. The inheritance system does the trick.
Recursively __sleep() call parent’ __sleep() and return the whole array of variables of the object instance to be serialized.
<?php class foo { } class a { private $var1; function __construct(foo &$obj = NULL) { $this->var1 = &$obj; } /** Return its variables array, if its parent exists and the __sleep method is accessible, call it and push the result into the array and return the whole thing. */ public function __sleep() { $a = array_keys(get_object_vars(&$this)); if (method_exists(parent, '__sleep')) { $p = parent::__sleep(); array_push($a, $p); }; return $a; } } class b extends a { function __construct(foo &$obj = NULL) { parent::__construct($obj); } } session_start(); $myfoo = &new foo(); $myb = &new b($myfoo); $myb = unserialize(serialize(&$myb)); ?>This should work, I haven’t tested deeper.
up
down
-1yanleech at gmail dot com ¶12 years ago
Maybe we can using unserialize() & __wakeup() instead “new” when creating a new instance of class.
Consider following codes:
class foo
{
static public $WAKEUP_STR = ‘O:3:“foo”:0:{}’;
public function foo(){}
public function bar(){}
}
f
o
o
=
u
n
s
e
r
i
a
l
i
z
e
(
f
o
o
:
:
foo = unserialize(foo::
foo=unserialize(foo::WAKEUP_STR);
up
down
-1martin dot goldinger at netserver dot ch ¶15 years ago
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.
up
down
-1mastabog at hotmail dot com ¶15 years ago
In reply to krisj1010 at gmail.com below:
__sleep() handles protected/private properties very well. You should never rely on get_class_vars() to retrieve property names since this function only returns the public properties. Use the Reflection API instead for that purpose. Better yet, if you know which ones you want to save it is always faster to specify the return array manually.
up
down
-2Anonymous ¶10 years ago
C+±style operator overloading finally makes an appearance with the introduction to __invoke(). Unfortunately, with just ‘()’. In that sense, it is no more useful than having a default class method (probably quite useful actually) and not having to type out an entire method name. Complimenting wbcarts at juno dot com’s point class below, the following allows calculating distance between one or more graph points…
Functionally, __invoke() can also be used to mimic the use of variable functions. Sadly, attempting any calling of __invoke() on a static level will produce a fatal error.
up
down
-2Anonymous ¶10 years ago
Concerning __set() with protected/private/overloaded properties, the behavior might not be so intuitive without knowing some underlying rules. Consider this test object for the following examples…
Combined Operators (.=, +=, *=, etc): you must also define a companion __get() method to grant write -and- read access to the property. Remember, “$x += y " i s s h o r t h a n d f o r " y" is shorthand for " y"isshorthandfor"x = $x + KaTeX parse error: Expected group after '_' at position 23: … other words, "_̲_set(x, (__get($x) + $y))”.
Properties that are Arrays: attempting to set array values like “$a->test_array[] = ‘asdf’;” from outside this object will result in an “Indirect modification of overloaded property” notice and the operation completely ignored. You can’t use ‘[]’ for array value assignment in this context (with the exception only if you made __get() return by reference, in which case, it would work fine and bypass the __set() method altogether). You can work around this doing something like unioning the array instead:
<?php $a->test_array[] = 'asdf'; // notice given and ignored unless __get() was declared to return by reference $a->test_array += array(1 => 'asdf'); // to add a key/value $a->test_array = array("key" => 'asdf') + $a->test_array; // to overwrite a key/value. ?>Properties that are Objects: as long as you have that __get() method, you can freely access and alter that sub object’s own properties, bypassing __set() entirely. Remember, objects are assigned and passed by reference naturally.
<?php $a->test_obj->prop = 1; // fine if $a did not have a set method declared. ?>All above tested in 5.3.2.
up
down
-2michal dot kocarek at seznam dot cz ¶12 years ago
Remember that setters and getters (__set, __get) will work in your class as long as you NOT SET the property with given name.
If you still want to have the public property definition in the class source code (phpDocumentor, editor code completition, or any other reason) when using these magic methods, simply unset() your public properties inside the constructor.
__set/__get function will be called and code reader will see at first sight, which public properties are available.
Example:
<?php class user { /** * @var int Gets and sets the user ID */ public $UserID; private $_userID; public function __construct() { // All the magic is in single line: // We unset public property, so our setters and getters // are used and phpDoc and editors with code completition are happy unset($this->UserID); } public function __set($key, $value) { // assign value for key UserID to _userID property } public function __get($key) { // return value of _userID for UserID property } } ?>up
down
-4danillo dot paiva dot toledo at gmail dot com ¶7 years ago
While I was studying Ruby I saw as such interesting things as properties created + its getters and setters in just one line.
I tryied to do the same in PHP and this is the code I have
class Father {
public function __call($name, KaTeX parse error: Expected '}', got 'EOF' at end of input: … if(isset(this->KaTeX parse error: Expected '}', got 'EOF' at end of input: … if(isset(args[0]))
return
t
h
i
s
−
>
this->
this−>name = $args[0];
return
t
h
i
s
−
>
this->
this−>name;
}
return false;
}
}
class Child extends Father {
public $country = “Brazil”;
public $state = “Sao Paulo”;
}
Sometimes we don’t need things like that on all classes but is quite interesting.
up
down
-2rudie-de-hotblocks at osu1 dot php dot net ¶11 years ago
Note also that the constructor is executed also, and before __set_state(), making this magic function less magic, imho, (except for the ability to assign private members).
up
down
-3docey ¶15 years ago
about __sleep and _wakeup, consider using a method like this:
class core
{
var $sub_core; //ref of subcore
var $_sleep_subcore; // place where serialize version of sub_core will be stored
function core(){
$this->sub_core = new sub_core();
return true;
}
function __wakeup()
{
// on wakeup of core, core unserializes sub_core
// wich it had stored when it was serialized itself
t
h
i
s
−
>
s
u
b
c
o
r
e
=
u
n
s
e
r
i
a
l
i
z
e
(
this->sub_core = unserialize(
this−>subcore=unserialize(this->_sleep_subcore);
return true;
}
function __sleep()
{
// sub_core will be serialized when core is serialized.
// the serialized subcore will be stored as a string inside core.
t
h
i
s
−
>
s
l
e
e
p
s
u
b
c
o
r
e
=
s
e
r
i
a
l
i
z
e
(
this->_sleep_subcore = serialize(
this−>sleepsubcore=serialize(this->sub_core);
$return_arr[] = “_sleep_subcore”;
return $return_arr;
}
}
class sub_core
{
var $info;
function sub_core()
{
$this->info[“somedata”] = “somedata overhere”
}
function __wakeup()
{
return true;
}
function __sleep()
{
$return_arr[] = “info”
return $return_arr;
}
}
this way subcore is being serialized by core when core is being serialized. subcore handles its own data and core stores it as a serialize string inside itself. on wakeup core unserializes subcore.
this may have a performance cost, but if you have many objects connected this way this is the best way of serializing them. you only need to serialize the the main object wich will serialize all those below which will serialize all those below them again. in effect causing a sort of chainreaction in wich each object takes care of its own info.
offcoarse you always need to store the eventualy serialized string in a safe place. somebody got experience with this way of __wakeup and __sleep.
works in PHP4&5
up
down
-4Travis Swicegood ¶13 years ago
There is no need to use eval() to mimic mixins (i.e., multiple inheritance) within PHP 5. You only need to:
You could just as easily add an addMixin() method that would allow you to add multiple objects to an array, and then iterate over that array until you found the right method. As noted, these are referred to as a Mixins in other languages.
up
down
-9tom ¶10 years ago
Note a common pitfall when using __wakeup.
If you unserialize a datastructure, you may not rely on the parent object to have been fully unserialized by the time __wakeup is called. Example
<?php class A { public $b; public $name; } class B extends A { public $parent; public function __wakeup() { var_dump($parent->name); } } $a = new A(); $a->name = "foo"; $a->b = new B(); $a->b->parent = $a; $s = serialize($a); $a = unserialize($s); ?>Expected output: “foo”.
Actual output: NULL.
Reason: $b is unserialized before $name. By the time B::__wakeup is called, $a->name does not yet have a value.
So be aware that the order in which your class variables are defined is important! You need to manually order them by dependencies - or write a __sleep function and order them by depencies there. (Currently I can’t tell which option I hate
相关文章:http://www.xitong5s.com/
http://www.jieba8.com/