PHP基础知识系列:拦截器方法

 

拦截器方法:它可以拦截发送到未定义方法和属性的消息。

 
方法   描述
__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);

 



 

 

 

 

 

posted on 2012-09-08 14:57  color_story  阅读(1687)  评论(0编辑  收藏  举报

导航