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
沒有留言:
張貼留言