拦截器方法:它可以拦截发送到未定义方法和属性的消息。
方法 | 描述 |
__get($property) | 访问未定义的属性时被调用 |
__set($property) | 给未定义的属性赋值时被调用 |
__isset($property,$value) | 对未定义的属性使用isset()时被调用 |
__unset($property) | 对未定义的属性调用unset()时被调用 |
__call($method,$arg_array) | 调用未定义的方法时被调用 |
__autoload($classname) | 自动载入类 |
重载可以通过__get, __set, __call几个特殊方法来进行. 当Zend引擎试图访问一个成员并没有找到时,PHP将会调用这些方法.
__get()和__set()方法用于处理类中为声明的属性。
__call方法说明了你如何调用未经定义的方法.
你调用未定义方法时,方法名和方法接收的参数将会传给__call方法, PHP传递__call的值返回给未定义的方法.
_get():当客户端代码试图访问未声明的属性时,__get()方法会被自动调用,并带一个包含要访问的属性名称的字符串参数。无论__get()返回什么都会发送给客户端代码,就好像带有该值一样。__get方法可以用来捕获一个对象中不存在的变量和方法。
class Person{ function __get($property){//访问属性不存在时调用指定方法的返回值 $method = "get{$property}"; if(method_exists($this,$method)){//判断函数是否存在 return $this->method(); } } function getName(){ return "Bob"; } function getAge(){ return "44"; } }
如下:
$p = new Person(); print $p->name;
如果访问未定义的属性,那么就会调用__get()方法,__get($name)方法内部获取到getname()方法的返回值,返回给结果,就如同内部有name属性一样。如果没有,则什么也不做。用户试图访问的属性被解析为NULL。
__isset():与__get()方法相似,当客户在一个未定义的属性上调用isset()时,__isset()被调用。
function __isset($property){ $method = "get{$property}"; return (method_exists($method)); }
现在,可以通过检查属性是否存在,来返回了:
if(isset($p->name)){ print($p->name); }
当调用isset()方法时,属性不存在,那么会调用__isset()方法,判断获取属性的方法是否存在,如果存在的话,可以后续执行获取的方法:$p->name,这样方法就会调用__get()方法来获取值了。
_set():在客户端代码试图给未定义的属性赋值时被调用,它会接受2个参数:属性名和客户要设置的值,然后我们在决定如何使用这些参数。__set方法可以用来捕获和按参数修改一个对象中不存在的变量和方法。
class Person{ private $_name; private $_age; //如果试图设置未定义的属性,那么调用此方法 function __set($property,$value){ $method = "set{$property}"; if(method_exists($method)){ return $this->$method($value); } } function setName($name){ $this->_name = $name; if(!is_null($name)){ $this->_name = strtoupper($this->_name); } } function setAge($age){ $this->_age = strtoupper($age); } }
当设置一个属性时,如果一个类没定义这个属性,那么__set()方法会被调用,参数为这个属性和你要设置的$value,这个值如何使用取决于你写的__set()方法的具体实现。在本例中我们调用set{$proterty}()方法来设置属性的值。
$p = new Person(); $p->name = "bob"; echo $p->name;//返回bob
__unset():与__set()相对应,当unset()在一个未定义的属性上被调用时,__unset()方法会被调用,并以该属性的名称作为参数,然后根据属性名进行必要的处理。
class Person{ //如果试图使用unset()处理未定义的属性,那么调用此方法 function __unset($property){ $method = "set{$property}"; if(method_exists($method)){ return $this->$method(null); } } }
__call():当客户端代码要调用类中未定义的方法时,__call()方法会被调用。__call()接受2个参数,一个是方法的名称,一个是方法的参数数组。__call()对于实现委托很有用。委托是指一个对象转发或者委托一个请求给另一个对象,被委托的对象替原来的对象处理请求。类似于继承,但是继承的父子关系是确定的,而委托可以在代码运行时改变使用的对象,具有更好的灵活性。
class PersonWriter{//被委托的类 function writeName(Person $p){//注意这里的参数是Person对象 print $p->getName()."\n"; } function writeAge(Person $p){ print $p->getAge()."\n"; } }
委托类:
class Person{ private $writer; function __construct(PersonWriter $writer){ $this->witer = $writer; } function __call($method,$args){ if(method_exists($this->writer,$methodname)){ return $this->writer->$methodname($this); } } function getName(){return "Bob";} function getAge(){return 44;} }
调用方式:
$person = new Person(new PersonWriter()); $person->wirteName();//Bob
__autoload($classname):很多开发者写面向对象的应用程序时,对每个类的定义建立一个 PHP 源文件。一个很大的烦恼是不得不在每个脚本(每个类一个文件)开头写一个长长的包含文件的列表。
在软件开发的系统中,不可能把所有的类都写在一个PHP文件中,当在一个PHP文件中需要调用另一个文件中声明的类时,就需要通过include把 这个文件引入。不过有的时候,在文件众多的项目中,要一一将所需类的文件都include进来,是一个很让人头疼的事,所以我们能不能在用到什么类的时 候,再把这个类所在的php文件导入呢?这就是我们这里我们要讲的自动加载类。
在 PHP 5 中,可以定义一个 __autoload()函数,它会在试图使用尚未被定义的类时自动调 用,通过调用此函数,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类, __autoload()函数接收的一个参数,就是你想加载的类的 类名,所以你做项目时,在组织定义类的文件名时,需要按照一定的规则,最好以类名为中心,也可以加上统一的前缀或后缀形成文件名,比如 xxx_classname.php、classname_xxx.php以及就是classname.php等等.
本例尝试分别从 MyClass1.php 和 MyClass2.php 文件中加载 MyClass1 和 MyClass2 类
function __autoload($classname) { require_once $classname . '.php'; } //MyClass1类不存在时,自动调用__autoload()函数,传入参数”MyClass1” $obj = new MyClass1(); //MyClass2类不存在时,自动调用__autoload()函数,传入参数”MyClass2” $obj2 = new MyClass2();
__clone():复杂对象只是简单地将一个变量赋值给另一个变量。
class CopyMe{} $first = new CopyMe(); $second = $first;
在PHP4中,$second和$first是两个完全不同的对象。我们无法检查两个变量是否指向相同的对象。
在PHP5中,$second和$first指向同一个对象。运行PHP5的代码时,两个变量指向同一个引用,没有各自保留一份相同的副本,有时候是需要的。
PHP5中提供了clone关键字:
class CopyMe{} $first = new CopyMe(); $second = clone $first;//现在的$second和$first是不同的两个对象了
但是此时还有问题,每一个Person对象都有一个标示$id,如果这个对象$id对应的是数据库中的一条信息,可能出现的情况是,两个完全不同的 对象指向数据库中的一条记录,此时,对一个对象的操作就会印象到另一个对象了。如果我们可以控制对象复制的内容,那么久可以避免上面的问题了。这就是 __clone()的用途:
浅复制:
Person{ private $name; private $age; private $id; function __construct($name,$age){ $this->name = $name; $this->age = $age; } function setId($id){ $this->id = $id; } //这个函数在赋值的对象上运行 function __clone(){//将复制的对象id设置为0 $this->id = 0; } }
当在一个Person对象上调用clone时,产生一个新的副本,并且新副本的__clone()方法会被调用:
$person = new Person("bob",44); $person->setId(343); $person2 = clone $person;//此时的person2:name:bob age:44 id:0
深复制:以上的浅复制,可以保证所有基本数据类型的属性被完全赋值。在复制对象属性时只复制引用,并不复制引用 的对象。如果对象有一个$balance属性,可以将该属性也复制给新副本,那么此时就有两个Person对象同时引用一个$balance。对一个操 作,就以为着另一个也改变了。
class Account{//账单 public $balance;//余额 function __construct($balance){ $this->balance = $balance; } }
下面是Person对象,且引用了Account对象
Person{ private $name; private $age; private $id; function __construct($name,$age,Account $account){ $this->name = $name; $this->age = $age; $this->account = $account; } function setId($id){ $this->id = $id; } //这个函数在赋值的对象上运行 function __clone(){//将复制的对象id设置为0 $this->id = 0; } }
当复制这个对象是:
$person = new Person("bob",44,new Accoun(200)); $person->setId(343); $person2 = clone $person; //给person充一些钱 $person->account->balance +=20; //结果是$person2也被充了钱 print $person2->account->balance;//220
解决方法:
//这个函数在赋值的对象上运行 function __clone(){//将复制的对象id设置为0 $this->id = 0; $this->account = clone $account;//深度clone }
__sleep():串行化的时候用。
__wakeup():
反串行化的时候用。
串行化serialize可以把变量包括对象,转化成连续bytes数据.
你可以将串行化后的变量存在一个文件里或在网络上传输. 然后再反串行化还原为原来的数据.
你在反串行化类的对象之前定义的类,PHP可以成功地存储其对象的属性和方法. 有时你可能需要一个对象在反串行化后立即执行.
为了这样的目的,PHP会自动寻找__sleep和__wakeup方法.
当一个对象被串行化,PHP会调用__sleep方法(如果存在的话).
在反串行化一个对象后,PHP 会调用__wakeup方法. 这两个方法都不接受参数. __sleep方法必须返回一个数组,包含需要串行化的属性.
PHP会抛弃其它属性的值.
如果没有__sleep方法,PHP将保存所有属性.
如何用__sleep和__wakeup方法来串行化一个对象.
Id属性是一个不打算保留在对象中的临时属性. __sleep方法保证在串行化的对象中不包含id属性.
当反串行化一个User对象,__wakeup方法建立id属性的新值. 这个例子被设计成自我保持.
在实际开发中,你可能发现包含资源(如图像或数据流)的对象需要这些方法。
class User{ public $name; public $id; function __construct(){ //give user a unique ID 赋予一个不同的ID $this->id = uniqid(); } function __sleep(){ //do not serialize this->id 不串行化id return(array("name")); } function __wakeup(){ //give user a unique ID $this->id = uniqid(); } } //create object 建立一个对象 $u = new User; $u->name = "Leon"; //serialize it 串行化 注意不串行化id属性,id的值被抛弃 $s = serialize($u); //unserialize it 反串行化 id被重新赋值 $u2 = unserialize($s); //$u and $u2 have different IDs $u和$u2有不同的ID print_r($u); print_r($u2);