简介
魔术方法是可以定义在类中的一些特殊方法,这些方法的名称、参数和返回值都是PHP预定义的,设计这些方法时只需要设计方法体中的具体业务逻辑即可。设计一个类时,魔术方法不一定非得设计,而如果设计了,那么程序运行过程中,php会在适当的时候自动调用这些魔术方法。
__toString
public string __toString ( void )对象被转换为字符串时,自动调用此方法,注意,不能在 __toString() 方法中抛出异常。这么做会导致致命错误。
class MyClass
{
private $dat;
public function __construct($dat) {
$this->dat = $dat;
}
public function __toString() {
return "Dat: $this->dat";
}
}
$a = new MyClass(5);
$b = (string)$a;
echo $a."\n"; // Dat: 5
echo $b."\n"; // Dat: 5
__invoke()
mixed __invoke ([ $... ] )当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
class MyClass
{
private $dat;
public function __construct($dat) {
$this->dat = $dat;
}
public function __invoke($num = 1) {
for($i=0; $i<$num; $i++)
echo "Dat: $this->dat\n";
}
}
$a = new MyClass(5);
$a(3);
/*
Dat: 5
Dat: 5
Dat: 5
*/
__set_state()
static object __set_state ( array $properties )var_export() 导出类时,会包含对此方法的调用。也就是说如果想用var_export()的导出结果来创建对象,那么就需要定义好此方法。
本方法的唯一参数是一个数组,其中包含按 array('property' => value, ...) 格式排列的类属性。
class MyClass
{
public $var1;
public $var2;
static function __set_state($an_array)
{
}
}
$a = new MyClass();
$a->var1 = 2;
$a->var2 = 3;
$b = var_export($a, true);
echo $b;
/*
MyClass::__set_state(array(
'var1' => 2,
'var2' => 3,
))
*/
本例中,var_export()的返回值是一个字符串,该字符串是一段可执行的php代码,而这段代码的运行结果就是得到一个$a对象。从这段代码中可以看出,调用的方法是静态方法__set_state(),而实参是一个数组。所以可依此来设计__set_state()方法。
class MyClass
{
public $var1;
public $var2;
public static function __set_state($an_array)
{
$obj = new MyClass;
$obj->var1 = $an_array['var1'];
$obj->var2 = $an_array['var2'];
return $obj;
}
}
$a = new MyClass();
$a->var1 = 2;
$a->var2 = 3;
$b = var_export($a, true);
eval('$n = '.$b.";");
var_dump($n);
/*
object(MyClass)#2 (2) {
["var1"]=>
int(2)
["var2"]=>
int(3)
}
*/
__debugInfo()
array __debugInfo ( void )定制var_dump()函数的输出,本函数返回一个数组,并可由var_dump()函数打印。
class MyClass
{
public $var1 = 1;
protected $var2 = 2;
private $var3 = 3;
}
$a = new MyClass();
var_dump($a);
/*
object(MyClass)#1 (3) {
["var1"]=>
int(1)
["var2":protected]=>
int(2)
["var3":"MyClass":private]=>
int(3)
}
*/
class MyClass
{
public $var1 = 1;
protected $var2 = 2;
private $var3 = 3;
public function __debugInfo()
{
return Array("var1"=>$this->var1,
"var2"=>$this->var2);
}
}
$a = new MyClass();
var_dump($a);
/*
object(MyClass)#1 (2) {
["var1"]=>
int(1)
["var2"]=>
int(2)
}
*/
__destruct
void __destruct ( void )析构函数,在对象的所有引用都被删除或者当对象被显式销毁时执行。和构造函数一样,父类的析构函数不会被引擎暗中调用。要执行父类的析构函数,必须在子类的析构函数体中显式调用 parent::__destruct()。此外也和构造函数一样,子类如果自己没有定义析构函数则会继承父类的。 即使在使用 exit() 终止脚本运行时也会调用析构函数。但是在析构函数中调用 exit() 则会中止其余关闭操作的运行。 另外,试图在析构函数(在脚本终止时被调用)中抛出一个异常会导致致命错误。
克隆
当直接复制一个对象时,实际上复制到的仅仅是一个引用。如果使用clone操作符进行复制,则可以得到一个副本。
class OtherClass
{
public $otherVar = 0;
}
class MyClass
{
public $var;
public $other;
}
$a = new MyClass();
$o = new OtherClass();
$a->var = 3;
$a->other = $o;
$b = $a;
$c = clone $a;
$a->var = 4;
$o->otherVar = 1;
var_dump($a);
var_dump($b);
var_dump($c);
/*
object(MyClass)#1 (2) {
["var"]=>
int(4)
["other"]=>
object(OtherClass)#2 (1) {
["otherVar"]=>
int(1)
}
}
object(MyClass)#1 (2) {
["var"]=>
int(4)
["other"]=>
object(OtherClass)#2 (1) {
["otherVar"]=>
int(1)
}
}
object(MyClass)#3 (2) {
["var"]=>
int(3)
["other"]=>
object(OtherClass)#2 (1) {
["otherVar"]=>
int(1)
}
}
*/
本例中,$c就是使用clone赋值的,其中的$var确实是一个副本,但$other仍然与$a和$b共享。
为解决此问题,可为类定义__clone()方法,该方法会在clone后自动调用,在方法中完成对象的重新创建即可。当复制完成时,如果定义了 __clone() 方法,则新创建的对象(复制生成的对象)中的 __clone() 方法会被调用,可用于修改属性的值(如果有必要的话)。
class OtherClass
{
public $otherVar = 0;
public function __clone() {
}
}
class MyClass
{
public $var;
public $other;
public function __clone() {
$this->other = clone $this->other;
}
}
$a = new MyClass();
$o = new OtherClass();
$a->var = 3;
$a->other = $o;
$b = $a;
$c = clone $a;
$a->var = 4;
$o->otherVar = 1;
var_dump($a);
var_dump($b);
var_dump($c);
/*
object(MyClass)#1 (2) {
["var"]=>
int(4)
["other"]=>
object(OtherClass)#2 (1) {
["otherVar"]=>
int(1)
}
}
object(MyClass)#1 (2) {
["var"]=>
int(4)
["other"]=>
object(OtherClass)#2 (1) {
["otherVar"]=>
int(1)
}
}
object(MyClass)#3 (2) {
["var"]=>
int(3)
["other"]=>
object(OtherClass)#4 (1) {
["otherVar"]=>
int(0)
}
}
*/
属性重载
当调用当前环境下未定义或不可见的类属性或方法时,会有一些预定义的魔术方法被自动调用,这些魔术方法也称为重载方法。所有的重载方法都必须被声明为 public,而且参数都不能通过引用传递。__set
public void __set ( string $name , mixed $value )
在给不可访问属性赋值时,__set() 会被调用。
__get
public mixed __get ( string $name )
读取不可访问属性的值时,__get() 会被调用。
__isset
public bool __isset ( string $name )
当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
__unset
public void __unset ( string $name )
当对不可访问属性调用 unset() 时,__unset() 会被调用。
属性重载只能在对象中进行。在静态方法中,这些魔术方法将不会被调用。
注意,因为 PHP 处理赋值运算的方式,__set() 的返回值将被忽略。类似的, 在下面这样的链式赋值中,__get() 不会被调用: $a = $obj->b = 8;
在除 isset() 外的其它语言结构中无法使用重载的属性,这意味着当对一个重载的属性使用 empty() 时,重载魔术方法将不会被调用。 为避开此限制,必须将重载属性赋值到本地变量再使用 empty()。
class MyClass
{
// 被重载的数据保存在此
private $data = array();
// 只有从类外部访问这个属性时,重载才会发生
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];
}
return null;
}
public function __isset($name) {
echo "/// Is '$name' set?\n";
return isset($this->data[$name]);
}
public function __unset($name) {
echo "/// Unsetting '$name'\n";
unset($this->data[$name]);
}
public function getHidden() {
return $this->hidden;
}
}
$a = new MyClass();
var_dump(isset($a->x)); // bool(false)
var_dump(isset($a->hidden)); // bool(false)
$a->x = 3;
$a->hidden = 4;
var_dump(isset($a->x)); // bool(true)
var_dump(isset($a->hidden)); // bool(true)
var_dump($a->x); // int(3)
var_dump($a->hidden); // int(4)
var_dump($a->getHidden()); // int(2)
unset($a->x);
unset($a->hidden);
/*
/// Is 'x' set?
bool(false)
/// Is 'hidden' set?
bool(false)
/// Setting 'x' to '3'
/// Setting 'hidden' to '4'
/// Is 'x' set?
bool(true)
/// Is 'hidden' set?
bool(true)
/// Getting 'x'
int(3)
/// Getting 'hidden'
int(4)
int(2)
/// Unsetting 'x'
/// Unsetting 'hidden'
*/
方法重载
__call
public mixed __call ( string $name , array $arguments )
在对象中调用一个不可访问方法时,__call() 会被调用。
__callStatic
public static mixed __callStatic ( string $name , array $arguments )
在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
$name 参数是要调用的方法名称。$arguments 参数是一个枚举数组,包含着要传递给方法 $name 的参数。
class MyClass
{
public function __call($name, $arguments) {
// 注意: $name 的值区分大小写
echo "Calling object method '$name' "
. implode(', ', $arguments) . "\n";
}
public static function __callStatic($name, $arguments) {
// 注意: $name 的值区分大小写
echo "Calling static method '$name' "
. implode(', ', $arguments) . "\n";
}
}
$a = new MyClass();
$a->func(1,2,3);
MyClass::staticFunc(4,5,6);
/*
Calling object method 'func' 1, 2, 3
Calling static method 'staticFunc' 4, 5, 6
*/