PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以双下划线前缀。这些魔术方法使用的时候不需要用户自己去调用,而是在特定的情况下会被自动调用。
【1】__construct()与__destruct()
PHP中允行开发者在一个类中定义一个方法作为构造方法。具有构造方法的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。触发调用它的情况就是创建对象new的时候。另外析构方法会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
【2】__set()与 __get()
在面向对象思想中,一般要对属性进行封装,这样类外对象不能直接进行访问或者修改,但是这样的话,我们另外需要定义一个类似setValue()这样公有的方法接口,从而简介的为私有属性赋值。但是如果私有属性比较多,那么我们还需要对每一个属性去写这样的公有方法,比较繁琐。那么我们可以用魔术方法__set()去解决这个问题。该方法能够控制在对象外部为非公有的成员进行属性赋值。需要用户在声明的类的时候去实现这个方法,才可以在设置非公有属性下去调用。如果没有写这个方法,而去直接修改非公有属性会报错。
#在给不可访问属性赋值时,__set() 会被调用。
public __set ( string $name , mixed $value ) : void
如果在类中声明了__get()方法。则可以在对象外部直接获取非公有属性。当然你也可以在方法中设定一些规则不能所有的都能去访问。它 有一个必选参数,必须要传入非公有属性的属性名,然后函数会返回一个值。
#读取不可访问属性的值时,__get() 会被调用。
public __get ( string $name ) : mixed
【3】__isset()与__unset()
我们知道PHP内置函数中也有一个isset(),用来测定变量是否存在,传入一个变量作为参数如果存在返回true,否则返回false。那么我们也可以用该函数去测定对象的成员属性,不过这个属性必须是公有的,所以如果是私有的,那就不行了。不过PHP也提供了对应的魔术方法。需要用户在类中声明__isset()方法。这样当在类的外部使用isset()函数或者empty()函数来测定对象中的非公有属性的时候,就会自动调用魔术方法来进行测定。
#当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
public __isset ( string $name ) : bool
另外我们也知道有一个unset()内置函数,用来删除指定的变量,需要传入变量名。同样的魔术方法__unset()的作用也是为了可以在类外删除对应的非公有属性。当在类外使用unset()删除某个不可访问的属性,就会自动调用魔术方法进行删除。
#当对不可访问属性调用 unset() 时,__unset() 会被调用。
public __unset ( string $name ) : void
看一下下面的例子吧:
class Person{
private $name;
protected $age;
public $sex;
function __construct($name='',$age=18){#构造方法也是一种魔术方法
$this->name = $name;
$this->age = $age;
}
public function __set($name,$value){#设置非公有的属性 会被调用
if($name = 'age') $this->$name = $value;#设置只能修改age
}
public function __get($name){#获取非公有的属性 会被调用
echo $this->$name;
}
public function __isset($name){#判断某个非公有的属性时候,会被调用
echo "<br/>Is '$name' set? ";
return isset($this->$name);
}
public function __unset($name){#销毁某个非公有的属性时候,会被调用
echo "<br/> Unsetting '$name' ";
unset($this->$name);//没有返回值
echo ' OK! ';
}
public function __destruct(){#析构方法 也是魔术方法
echo '<br/>Bye!....<br/>';
}
}
$obj = new Person('tacks');//实例化的时候,会自动调用魔术构造方法 顺便传入参数进行初始化
var_dump($obj);
$obj->name = 'AAA';//这里进行设置 但是不会起作用,因为我们在__set方法里面设定只能修改age
$obj->age = 20;//设置私有属性 会自动调用到魔术方法__set
echo $obj->name;//获取私有属性 会自动调用到魔术方法__get
echo isset($obj->age)?'Yes':'No';#判断一个属性,会自动调用到魔术方法__isset
echo '<br/>';
echo isset($obj->sex)?'Yes':'No';#判断一个属性,是公有的属性,没有调用到魔术方法
unset($obj->age);//没有返回值 会调用一个魔术方法__unset()
echo '<br/>Kill!';
#程序结束,对象销毁的时候,会自动调用魔术析构方法。
/*
object(Person)[1]
private 'name' => string 'tacks' (length=5)
protected 'age' => int 18
public 'sex' => null
tacks
Is 'age' set? Yes
No
Unsetting 'age' OK!
Kill!
Bye!....
*/
【4】__toString()
我们知道在使用实例化类产生的对象,它实际上是对象引用,即指针,存放在堆空间的首地址变量。我们不能直接用echo去输出对象的值,这样会报错。如果在定义的类中添加__toString()魔术方法,而且该方法必须返回一个字符串。则直接去输出对象引用的时候就不会报错,而是自动调用该方法。
#__toString() 方法用于一个类被当成字符串时应怎样回应。
#例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串
#否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
public __toString ( void ) : string
//_toString 必须返回一个字符串。
class Person{
private $mine = 'Tacks';
public function __toString(){#声明这个魔术方法
return $this->mine;#必须返回一个字符串
}
}
$obj = new Person;
echo $obj;#在被当作一个字符串的时候,自动调用魔术方法
【5】__call()与__callStatic()
如果对象调用了一个不存在的方法,那么系统一定会报错,并且退出程序不能继续执行。在PHP中,可以在类中添加一个魔术方法__call(),那么在调用对象中不存在的方法的时候,就会自动调用该方法,并且程序会继续执行,不受影响。所以我们可以接触该方法提示用户。这个魔术方法中有两个参数,第一个参数用来接收,调用不存在的方法名,第二个参数,则是以数组的形式接收不存在方法的参数列表。
PHP5.3后还有一个跟它很像的一个魔术方法__callStatic(),用于调用不存在的静态方法的时候,会被调用。
#在对象中调用一个不可访问方法时,__call() 会被调用。
public __call ( string $name , array $arguments ) : mixed
#在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
public static __callStatic ( string $name , array $arguments ) : mixed
class Person{#定义一个类
public function __call($fun,$args){#实现魔术方法__call()
echo 'Calling Object Method :',$fun,'() and args: ',implode(', ',$args),'<br/>';
}
public static function __callStatic($fun,$args){
echo 'Calling Static Method :',$fun,'() and args: ',implode(', ',$args),'<br/>';
}
public static function run(){
echo 'Running <br/>';
}
}
$obj = new Person;
$obj->say('name','age','sex');#当方法不存在的时候,会触发到__call()方法
Person::say('name','age','sex');#当类的静态方法不存在的时候, 会触发到__callStatic()方法
$obj->run();#其实静态方法,也可以用对象进行调用
Person::run();#利用类名::方法名 调用存在的静态方法
/*
Calling Object Method :say() and args: name, age, sex
Calling Static Method :say() and args: name, age, sex
Running
Running
*/
【6】类自动加载
类的自动加载。在编写面向对象(OOP) 程序时,很多开发者为每个类新建一个 PHP 文件。 这会带来一个烦恼:每个脚本的开头,都需要包含(include)一个长长的列表。spl_autoload_register() 函数可以注册任意数量的自动加载器,当使用尚未被定义的类(class)和接口(interface)时自动去加载。通过注册自动加载器,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。
spl_autoload_register(function ($class_name) {
require_once $class_name . '.class.php';
});
$obj1 = new A();
$obj2 = new B();
另外还有一个魔术方法用来自动加载, __autoload(),这个方法不是在类中声明的,它是一个全局函数,如果这个函数存在,PHP会用一个参数去调用它,参数即类名。
//声明一个自动加载类的魔术方法
function __autoload($class){
include($class.'.class.php');//加载类文件
}
$obj1 = new A();#实例化不存在的时候,会调用这个魔术方法
$obj2 = new B();
【7】__sleep()与__wakeup()
序列化serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。
反序列化unserialize()函数 会检查是否存在一个 __wakeup()方法。如果存在,则会先调用该方法,预先准备对象需要的资源。它经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
public __sleep ( void ) : array
__wakeup ( void ) : void
【8】__invoke()
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
class Demo{
function __invoke($k){#声明魔术方法
var_dump($k);//输出参数
}
}
$obj = new Demo;
$obj(99);//int 99 //在对象上用函数的方法,会自动调用__invoke()
var_dump(is_callable($obj));//boolean true//函数用于检测函数在当前环境中是否可调用。
【9】__set_state()
自 PHP 5.1.0 起当调用 var_export() 导出类时,此静态方法会被调用。本方法的唯一参数是一个数组,其中包含按 array('property' => value, ...) 格式排列的类属性。
# static __set_state ( array $properties ) : object
class Person{
public $name;
static public function __set_state($arr){
$obj = new Person;
$obj->name = $arr['name'];
return $obj;
}
}
$tacks = new Person;
$tacks->name = 'Tacks';
//把字符串按照 PHP 代码来计算。该字符串必须是合法的 PHP 代码,且必须以分号结尾
eval('$test = ' . var_export($tacks, true) . ';');
#会触发__set_state魔术方法
var_dump($test);
【10】对象克隆
PHP中的对象模型是通过引用调用对象的,有时候我们需要调用对象的一个副本,改变原来的对象的时候,不希望影响到副本。如果用new一个新对象再去赋值什么的比较繁琐。我们可以用clone去克隆一个对象,克隆后连个对象互补干扰,但是他们的成员属性和方法是一样的。克隆不经过构造方法比较快速。
对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的魔术方法)即对象中的 __clone() 方法,它不能被直接调用。当对象被复制后,PHP 5 会对对象的所有属性执行一个浅复制(shallow copy)。所有的引用属性 仍然会是一个指向原来的变量的引用。
class Money{
public $name;
public function __construct($name){#构造方法
echo "CONSTRUCT<br/>";
$this->name = $name;
}
public function intro(){
echo 'I am ',$this->name,' Money<br/>';
}
public function __clone(){#魔术方法 对象克隆的时候会被调用
echo 'CLONE<br/>';
}
}
$p1 = new Money('Sun Wukong');//CONSTRUCT
$p1->intro();//I am Sun Wukong Money
echo '<hr/>';
$p2 = clone $p1;#克隆了一份 ,不执行构造方法,比较快 会触发魔术方法__clone()
$p2->intro();//I am Sun Wukong Money
【小结】
-
__construct()构造方法完成类实例化的时候一些操作,__destruct()析构方法在对象销毁的前完成一些操作。
-
__set()在对象外部为非公有的成员进行属性赋值 ,__get()在对象外部访问非公有成员属性。
-
__isset()类的外部使用isset()或者empty()函数来测定对象中非公有属性。__unset()删除某个不可访问的属性
-
__toString()魔术方法直接去输出对象的引用
-
__call()在调用对象中不存在的方法的时候;callStatic()调用不存在的静态方法的时候。
-
引入类的自动加载利用spl_autoload_register()函数,或者__autoload()魔术方法。
-
__sleep()序列化对象的时候,__wakeup()反序列化对象的时候调用。
-
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
-
__set_state() ,当调用 var_export() 导出类时,此静态方法会被调用。
-
__clone() 用于克隆对象的时候会被调用 。