php有一类很神奇的方法,这些方法是保留方法,通常不会在外部被显式调用,他们使用双下划线(__)开头,他们被称为魔术方法(Magic Methods)。php官方也不建议定义其他双下划线开头的方法。
这次介绍属性重载方法:get/set/isset/unset
public void __set ( string $name , mixed $value ) public mixed __get ( string $name ) public bool __isset ( string $name ) public void __unset ( string $name )
这些方法触发的时机,都是在访问不可访问的属性的时候。
如以下:
<?php class Cls{ private $a = 0;
protected $b = 1;
public $c = 2;
var $d = 3; private $_properties = array(0); public function __set($name, $value){
echo __METHOD__ . ' is called! ' . json_encode(func_get_args()) . "\n";
return $this->_properties[$name] = $value;
} public function __get($name){
echo __METHOD__ . ' is called! ' . json_encode(func_get_args()) . "\n";
return $this->_properties[$name];
} public function __isset($name){
echo __METHOD__ . ' is called! ' . json_encode(func_get_args()) . "\n";
return isset($this->_properties[$name]);
} public function __unset($name){
echo __METHOD__ . ' is called! ' . json_encode(func_get_args()) . "\n";
unset($this->_properties[$name]);
} public function dump(){
echo "====================== DUMP START ====================\n";
echo <<<STR
a: $this->a
b: $this->b
c: $this->c
d: $this->d\n
STR;
foreach($this->_properties as $k => $v){
echo <<<STR
Property $k: $v\n
STR;
} echo "====================== DUMP STOP =====================\n"; }
}
$string = 'abcdef';
$obj = new Cls(); $list = array('isset', 'get', 'set', 'isset', 'unset', 'isset');
$obj->dump();
foreach($list as $method){
for($i = 0 ; $i < strlen($string) ; $i ++){
$char = $string{$i};
echo "------ $method : $char ------\n";
switch($method){
case 'isset':
echo json_encode($tmp = isset($obj->$char)) . "\n";
break;
case 'get':
echo json_encode($tmp = $obj->$char) . "\n";
break;
case 'set':
echo json_encode($tmp = $obj->$char = $char . "-val") . "\n";
break;
case 'unset':
unset($obj->$char);
break;
default:
break;
}
}
$obj->dump();
}
结果输出:
====================== DUMP START ====================
a: 0
b: 1
c: 2
d: 3
Property 0: 0
====================== DUMP STOP =====================
------ isset : a ------
Cls::__isset is called! ["a"]
false
------ isset : b ------
Cls::__isset is called! ["b"]
false
------ isset : c ------
true
------ isset : d ------
true
------ isset : e ------
Cls::__isset is called! ["e"]
false
------ isset : f ------
Cls::__isset is called! ["f"]
false
====================== DUMP START ====================
a: 0
b: 1
c: 2
d: 3
Property 0: 0
====================== DUMP STOP =====================
------ get : a ------
Cls::__get is called! ["a"]
null
------ get : b ------
Cls::__get is called! ["b"]
null
------ get : c ------
2
------ get : d ------
3
------ get : e ------
Cls::__get is called! ["e"]
null
------ get : f ------
Cls::__get is called! ["f"]
null
====================== DUMP START ====================
a: 0
b: 1
c: 2
d: 3
Property 0: 0
====================== DUMP STOP =====================
------ set : a ------
Cls::__set is called! ["a","a-val"]
"a-val"
------ set : b ------
Cls::__set is called! ["b","b-val"]
"b-val"
------ set : c ------
"c-val"
------ set : d ------
"d-val"
------ set : e ------
Cls::__set is called! ["e","e-val"]
"e-val"
------ set : f ------
Cls::__set is called! ["f","f-val"]
"f-val"
====================== DUMP START ====================
a: 0
b: 1
c: c-val
d: d-val
Property 0: 0
Property a: a-val
Property b: b-val
Property e: e-val
Property f: f-val
====================== DUMP STOP =====================
------ isset : a ------
Cls::__isset is called! ["a"]
true
------ isset : b ------
Cls::__isset is called! ["b"]
true
------ isset : c ------
true
------ isset : d ------
true
------ isset : e ------
Cls::__isset is called! ["e"]
true
------ isset : f ------
Cls::__isset is called! ["f"]
true
====================== DUMP START ====================
a: 0
b: 1
c: c-val
d: d-val
Property 0: 0
Property a: a-val
Property b: b-val
Property e: e-val
Property f: f-val
====================== DUMP STOP =====================
------ unset : a ------
Cls::__unset is called! ["a"]
------ unset : b ------
Cls::__unset is called! ["b"]
------ unset : c ------
------ unset : d ------
------ unset : e ------
Cls::__unset is called! ["e"]
------ unset : f ------
Cls::__unset is called! ["f"]
====================== DUMP START ====================
Cls::__get is called! ["c"]
Cls::__get is called! ["d"]
a: 0
b: 1
c:
d:
Property 0: 0
====================== DUMP STOP =====================
------ isset : a ------
Cls::__isset is called! ["a"]
false
------ isset : b ------
Cls::__isset is called! ["b"]
false
------ isset : c ------
Cls::__isset is called! ["c"]
false
------ isset : d ------
Cls::__isset is called! ["d"]
false
------ isset : e ------
Cls::__isset is called! ["e"]
false
------ isset : f ------
Cls::__isset is called! ["f"]
false
====================== DUMP START ====================
Cls::__get is called! ["c"]
Cls::__get is called! ["d"]
a: 0
b: 1
c:
d:
Property 0: 0
====================== DUMP STOP =====================
特别强调,当这个属性不存在(基于当前访问权限)的时候,才会触发这些方法。因此严重建议使用确定的键值进行get/set处理,特别是对应的键值不要有访问权限问题,如上例中的a和b,会导致在类内部、类外部、子类内处理的方式不同,开发时产生不可预知的返回值,极易产生bug。
empty方法不能调用属性重载方法。
$tmp = $obj->$char = $char . "-val"
此例中,__set自身的返回值将被忽略,同样__get也不会被调用。
此外:属性重载方法必须声明为public,同时参数不能使用引用传递,静态属性不能通过这些方法重载,这些方法也不能被声明为static。
当然,使用魔术方法的时候,也会对反射系统造成影响。