magic methods是一系列以__開頭的方法名稱,如果在類別中定義了這些方法,系統會在特定的時機呼叫。
__construct
在PHP4時,Class的建構函數( constructor )是跟類別同名的方法,到PHP5,則改名為__construct()這個magic method。
在Class new成 Object時呼叫
__destruct()
解構函數( destructor ),當物件要被從系統中「消滅」時,會呼叫這個方法。
__call / __callStatic
這是PHP實作method overload的方式,如果呼叫物件的某方法,而這個方法沒有在類別中定義的話,系統會嘗試呼叫__call()。實作__call()然後過濾系統傳入的方法名稱,就可以讓物件表現的像是有定義這個方法。__callStatic()也是一樣的作用,只是是針對靜態方法。
例:
class a { function __call($name, $args) { echo $name . " : " . print_r($args, true) . "\n"; } } $a = new a; $a->func1('abc', 'def'); class b { function __call($name, $args) { echo "\n__call:"; switch ($name) { case 'add': if (count($args) === 2) { if (is_numeric($args[0]) && is_numeric($args[1])) { return $args[0] + $args[1]; } if (is_string($args[0]) && is_string($args[1])) { return $args[0] . $args[1]; } } default: throw new Exception("[warning] b::$name method not found.\n"); } } public static function __callStatic($name, $args){ echo "\n__callStatic:"; switch ($name) { case 'add': if (count($args) === 2) { if (is_numeric($args[0]) && is_numeric($args[1])) { return $args[0] + $args[1]; } if (is_string($args[0]) && is_string($args[1])) { return $args[0] . $args[1]; } } break; default: throw new Exception("[warning] b::$name method not found.\n"); break; } } } $b = new b; echo $b->add(2, 3) . "\n"; echo $b->add('hello', ' world.') . "\n"; try { echo $b->add(2, ' world.') . "\n"; } catch (Exception $e) { echo $e->getMessage(); } echo $b::add(2, 3) . "\n"; echo $b::add('hello', ' world.') . "\n";結果:
func1 : Array ( [0] => abc [1] => def ) __call:5 __call:hello world. __call:[warning] b::add method not found. __callStatic:5 __callStatic:hello world.注意:
function __callStatic() 必須是 public static ,否則會跳Warning:
PHP Warning: The magic method __callStatic() must have public visibility and be static in magic_methods/method_overloading.php on line 30
上面例子如果$b->add()的兩個參數不同時為數字或字串,ex. $b->add(2, ' world.'); 。 則會報錯
__set() / __get() / __isset() / __unset()
PHP透過這幾個方法實現屬性的overload。讀取物件屬性時,如果屬性不存在,系統會嘗試呼叫__get()。如果類別有實作這個方法,就可以過濾傳入的屬性名稱,看看是否要返回值。__set則是對應到寫入物件屬性的狀況。另外,PHP可以透過isset()函數檢查物件屬性是否已設定、unset()函數來讓物件屬性回到未設定(null)的狀態,這時如果物件屬性不存在,則會嘗試呼叫__isset()及__unset()。
attribute overload例:
class a { function __get($name) { if ($name === 'var1') { return 'abc'; } } } $a = new a; echo $a->var1 . "\n"; echo $a->var2 . "\n"; class b { private $var1 = 'abc'; private $var2 = 'def'; function __get($name) { switch ($name) { case 'var1': return $this->var1; break; case 'var2': return 'ghi'; break; } } } $b = new b; echo $b->var1 . "\n"; echo $b->var2 . "\n"; class c {} $c = new c; echo $c->var1 . "\n";結果:
abc
abc
ghi
PHP Notice: Undefined property: c::$var1 in /cygdrive/e/www/test/magic_methods/attribute_overloading.php on line 37
__set() / __get() / __isset() / __unset()所有例子:class PropertyTest { /** 被重载的数据保存在此 */ private $data = array(); /** 重载不能被用在已经定义的属性 */ public $declared = 1; /** 只有从类外部访问这个属性时,重载才会发生 */ private $hidden = 2; public function __set($name, $value) { echo "Setting '$name' to '$value'\n"; $this->data[$name] = $value; } public function __get($name) { echo "Getting '$name'\n"; if (array_key_exists($name, $this->data)) { return $this->data[$name]; } $trace = debug_backtrace(); trigger_error( 'Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_NOTICE); return null; } /** PHP 5.1.0之后版本 */ public function __isset($name) { echo "Is '$name' set?\n"; return isset($this->data[$name]); } /** PHP 5.1.0之后版本 */ public function __unset($name) { echo "Unsetting '$name'\n"; unset($this->data[$name]); } /** 非魔术方法 */ public function getHidden() { return $this->hidden; } } echo "\n"; $obj = new PropertyTest; $obj->a = 1; echo $obj->a . "\n\n"; var_dump(isset($obj->a)); unset($obj->a); var_dump(isset($obj->a)); echo "\n"; echo $obj->declared . "\n\n"; echo "Let's experiment with the private property named 'hidden':\n"; echo "Privates are visible inside the class, so __get() not used...\n"; echo $obj->getHidden() . "\n"; echo "Privates not visible outside of class, so __get() is used...\n"; echo $obj->hidden . "\n";結果:
Setting 'a' to '1' Getting 'a' 1 Is 'a' set? bool(true) Unsetting 'a' Is 'a' set? bool(false) 1 Let's experiment with the private property named 'hidden': Privates are visible inside the class, so __get() not used... 2 Privates not visible outside of class, so __get() is used... Getting 'hidden' PHP Notice: Undefined property via __get(): hidden in magic_methods/set.php on line 68 in magic_methods/set.php on line 28說明:
__set()在 $obj->a = 1;時呼叫,並改寫存到$this->data中
__get()在 echo $obj->a; 時呼叫,因為前面$obj->a = 1;被__set()改寫了,所以取不到值,會呼叫__get()
__isset() 在 isset($obj->a) 時呼叫,所以被叫了兩次
__unset() 在 unset($obj->a); 時呼叫
關於overloading
定義:藉由接收的參數串列之型態或數量之不同,以求達到多個函式可共用相同函式名稱
overloading的目的是:
1. 降低所需命名的函式名稱
2. 提高user的易用性
PHP用__call()實作method overloading( function overloading、廣義或上面定義的overloading )
用__get()實作 attribute overloading( operator overloading )
__wakeup() / __sleep()
如果有在類別中定義的話,這兩個magic methods會在物件序列化( serialize() )/反序列化( unserialize() )時被呼叫。
如果類別有定義這個方法,__sleep()就會在物件開始序列化前被呼叫。他會返回一個陣列,裡面列舉需要被序列化的屬性名稱。這樣在進行序列化時,系統會針對這些屬性來進行操作。
例:
class a { public $name; public function __sleep() { $this->name = 'fillano'; return array("name"); } public function __wakeup() { $this->age = 18; } } $a = new a; $str = serialize($a); print_r($str); echo "\n"; $b = unserialize($str); print_r($b);結果:
O:1:"a":1:{s:4:"name";s:7:"fillano";} a Object ( [name] => fillano [age] => 18 )說明:
如果 __sleep() 沒有返回值 return array("name"); ,則會跳Notice:
PHP Notice: serialize(): __sleep should return an array only containing the names of instance-variables to serialize in magic_methods/sleep.php on line 13
__toString()
如果定義了這個方法並且回傳一個字串,那把物件當做字串操作時,系統會呼叫__toString()來取得代表物件的字串。
例:
class hello { public function __toString() { return "Hello "; } function test() { echo "test\n"; } } class world { public function __toString() { return "World.\n"; } } $a = new hello; $b = new world; echo $a . $b; $a->test();結果:
Hello World. test說明:
hello->test() 依然可以呼叫,不受__toString()影響
__invoke()
這是PHP5.3才有的magic method。當物件被當做函數來呼叫時,就會呼叫這個方法。可以用is_callable()函數來檢查物件是否可以當做函數執行,可以的話會回傳true。
例:
class a { public function __invoke() { $this->name = 'Jim'; return __CLASS__ . " is invoked.\n"; } } $a = new a; print_r($a); if (is_callable($a)) { echo $a(); } print_r($a);結果:
a Object ( ) a is invoked. a Object ( [name] => Jim )說明:
在$a()觸發__invoke()前,$a->name值是空的,在__invoke()中賦值後才有
__set_state()
這個從PHP5.1加入的magic method。PHP有一個var_export()函數,可以輸出變數的結構化資訊字串,這個資訊同時也是合法的PHP程式碼,所以可以被eval()執行。
如果類別定義了這個靜態方法,當使用var_export()來處理物件實例時,系統會先檢查這個方法是否存在,然後產生呼叫這個靜態方法的程式碼字串,在程式碼中,會把物件實例的屬性陣列當做參數傳遞給他。
例:
class A { public $var1; public $var2; public static function __set_state($an_array) // As of PHP 5.1.0 { echo "__set_state\n"; $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);結果:
__set_state object(A)#2 (2) { ["var1"]=> int(5) ["var2"]=> string(3) "foo" }注意:
必須同時使用eval()和var_export(),如: eval('$b = ' . var_export($a, true) . ';'); 才會觸發__set_state()
__clone()
__clone()會在複製( clone )完畢時對新的物件執行,所以可以在需要時,調整複製後的物件屬性。
例:
class A { public $obj; public function __construct() { $this->obj = new B; } public function __clone() { $this->obj = clone $this->obj; } } class B { public $name = 'fillano'; } $a = new A; $b = clone $a; $b->obj->name = 'james'; var_dump($a->obj->name); var_dump($b->obj->name);結果:
string(7) "fillano" string(5) "james"
物件Assignment 和 Cloning有什麼差別?
http://stackoverflow.com/questions/16893949/php-object-assignment-vs-cloning PHP Object Assignment vs Cloning
例:
class Bar { } $foo = new Bar; // $foo holds a reference to an instance of Bar $bar = $foo; // $bar holds a copy of the reference to the instance of Bar $baz = &$foo; // $baz references the same reference to the instance of Bar as $foo $blarg = clone $foo; // the instance of Bar that $foo referenced was copied // into a new instance of Bar and $blarg now holds a reference // to that new instance $foo->bear = 'bear'; $bar->xyz = 'xyz'; $baz->ted = 'ted'; echo "\nspl_object_hash(\$foo):".spl_object_hash($foo); echo "\$foo:"; print_r($foo); echo "\nspl_object_hash(\$bar):".spl_object_hash($bar); echo "\$bar:"; print_r($bar); echo "\nspl_object_hash(\$baz):".spl_object_hash($baz); echo "\$baz:"; print_r($baz); echo "\nspl_object_hash(\$blarg):".spl_object_hash($blarg); echo "\$blarg:"; print_r($blarg);結果:
spl_object_hash($foo):000000003db193cd00000003cd585be1$foo:Bar Object ( [bear] => bear [xyz] => xyz [ted] => ted ) spl_object_hash($bar):000000003db193cd00000003cd585be1$bar:Bar Object ( [bear] => bear [xyz] => xyz [ted] => ted ) spl_object_hash($baz):000000003db193cd00000003cd585be1$baz:Bar Object ( [bear] => bear [xyz] => xyz [ted] => ted ) spl_object_hash($blarg):000000003db193ce00000003cd585be1$blarg:Bar Object ( )說明:
用 assign( = )和assign reference( =& )在object上無意義,結果是一樣的,用spl_object_hash()查出物件的reference ID也一樣,改變其中一個屬性,其他跟著變。但用clone時,改變$foo、$bar、$baz屬性時,$blarg不受影響。
PHP的assign( = )和assign reference( =& )在 PHP常見問題::php的refrence符号&用法 可見不同之處
assignment 和 clone的另一個差別是, clone可以觸發 __clone()魔術方法
參考資料:
http://ithelp.ithome.com.tw/question/10135522 逐步提昇PHP技術能力 - PHP的語言特性 : magic methods
http://ithelp.ithome.com.tw/question/10132318 逐步提昇PHP技術能力 - PHP的語言特性 : 多載 (overloading)
http://antrash.pixnet.net/blog/post/79139547-%E8%AB%96%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91part-8%EF%BC%9Awhy-overloading%E3%80%81overriding Why Overloading、Overriding
http://www.5idev.com/p-php_object_clone.shtml PHP 对象克隆 clone 关键字与 __clone() 方法
http://stackoverflow.com/questions/16893949/php-object-assignment-vs-cloning PHP Object Assignment vs Cloning
http://stackoverflow.com/questions/1953782/php-getting-reference-id PHP: Getting reference ID
沒有留言:
張貼留言