1.__get()与__set()
我们可以通过__get()、__set()、__call()方法来存取类中没有定义的成员方法和属性。当我们试图写入一个不存在或不可见的属性时,PHP就会执行类中的__set()方法。__set()方法必须接收两个参数,用来存放试图写入的属性名称和属性值。来看下面的脚本例子,如代码清单2-23所示:
代码清单2-23 使用__set与__get魔术方法
<?php class MyShop { private $p = array(); function __set($name, $value) { //取得属性名称和值 echo "set::$name:$value <br />"; $this->p[$name] = $value; } function __get($name) { //取得属性名称 print "get::$name <br />"; return array_key_exists($name,$this->p) ? $this->p[$name] : null; } } $shop = new MyShop(); $shop->apple = 2; $shop->pear = 3; $shop->pear++; echo "苹果=". $shop->apple. "<br>"; echo "梨=". $shop->pear. "<br />"; ?>
执行结果如下:
set::apple:2 set::pear:3 get::pear set::pear:4 get::apple 苹果=2 get::pear 梨=4
从以上例子中,我们可以总结出__get()和__set()方法的功能:在引用该类中不存在的成员变量时,可以调用这两个方法,我们还可以用它们实现错误消息的提示,可以通过这两个方法动态地创建新变量来扩展一个类。
2.__call()
当我们试图调用类中一个不存在或不可见的方法时,PHP会执行该类中的__call()方法。__call()也必须接收两个参数,用来存放试图调用的方法名称及其参数(参数会被放在一个与该参数同名的数组中),如代码清单2-24所示:
代码清单2-24 使用__call方法
<?php class callClass { function __call($method_name, $parameters) { echo('使用__call尝试调用一个不存在/不可用的成员方法'); echo('<i>'.$method_name.'</i>'); echo('<b> , __call()开始调用</b><br>'); echo('<b>从传递的参数传入parameters数组,内容如下</b><br><pre>'); print_r($parameters); } } $obj = new callClass(); $obj->someMethod(1,9.9,"测试文本"); ?>
该脚本执行的结果如下:
使用__call尝试调用一个不存在/不可用的成员方法someMethod , __call()开始调用 从传递的参数传入parameters数组,内容如下: Array( [0] => 1 [1] => 9.9 [2] => 测试文本 )
看了上面例子,相信你应该能够理解__call()的含义了。下面我们运行一个实际应用例子,如代码清单2-25所示:
代码清单2-25 __call方法应用实例
<?php class MyShop { private $obj; function __construct($obj) { $this->obj = $obj; } function __call($method, $args) { print $method."::".implode($args,",")."\n"; if (isset($this->obj) && method_exists($this->obj, $method)) { return call_user_func_array(array($this->obj, $method), $args); } } } class Calculate { private $items = 0; function add($num){ $this->items += $num; } function sum(){ return $this->items; } } $obj = new Calculate(); $shop = new MyShop($obj); $shop->add(2); print $shop->sum(); ?>
那么,上面的脚本代码执行后的结果是这样的:
add::2 sum:: 2
3.__sleep()与__wakeup()
__sleep()方法在序列化(serialize)一个实例的时候被调用,__wakeup()则是在反序列化(unserialize)的时候被调用。
需要注意一点,__sleep()必须返回一个数组或者对象(一般返回的是当前对象$this),返回的值将会被用来做序列化的值,如果不返回这个值,则表示序列化失败。这也意味着反序列化不会触发__wakeup()事件。
下面我们再来看一个引用序列化的实用例子,如代码清单2-26所示:
代码清单2-26 序列化引用实例
<?php class myMagic { public $a='xx'; public $b='55'; function __sleep(){ echo "I am sleepy\n"; return $this; } function __wakeup(){ echo "wake up!\n"; } } $i = new myMagic; $si = serialize($i); echo "sleeping now.....\n"; print_r(unserialize($si)); ?>
4.__toString()
我们先看一个应用实例,如代码清单2-27所示:
代码清单2-27 错误应用实例
<?php class Person { private $name; function __construct($name){ $this->name = $name; } } $obj = new Person("Raymond du"); echo $obj; ?>
该程序将输出以下的信息:
Catchable fatal error: Object of class Person could not be converted to string in toString.php on line 9
上面这句话提示我们:捕捉到致命的错误,Person类不能被转换为String。
PHP提供一个叫__toString()的魔术方法,可以把类的实例转化为字符串,所以对于上面的实例,可以做以下修改,如代码清单2-28所示:
代码清单2-28 __toString方法应用实例
<?php class Person { private $name; function __construct($name){ $this->name = $name; } function __toString(){ return $this->name; } } $obj = new Person("Raymond du"); echo $obj; ?>
以上的代码将输出为:
Raymond du
__toString()成员方法调用并打印当前类中构建器中的值。在这个结构中,调用了公共的字符串操作,这样字符串的连接也就形成了一个字符串。
5.__autoload()
在编写面向对象程序时,常规做法是将每一个类保存为一个PHP源文件,这样做的好处是很容易找到一个类在什么地方,并且在需要调用某个类的时候,直接使用include或者require引用到当前文件就可以了。缺点是,我们每次都要包含一些源文件,如果调用的类或函数很多,需要在源代码头部包含一堆include或require的代码。
__autoload()方法可以方便地解决这个问题,在一些情况下省却使用include或require语句的麻烦。
如果我们在类中定义了__autoload()方法(一个类中仅能使用一次),而你访问一个类还未定义,则__autoload()方法开始将这个类名作为文件名参数来调用该类,如果能够成功引入该类,脚本将继续顺利执行下去,如果没有调用到该类,PHP引擎则抛出一个fatal错误,停止该脚本的执行。
下面的例子显示了如何调用__autoload()方法。这个文件名为MyClass.php,文件内容如代码清单2-29所示:
代码清单2-29 MyClass.php脚本内容
<?php class MyClass { function printWorld(){ echo "物有本末,事有始终,知所先后,则近道矣\n"; } }?>
general.inc.php文件的内容如代码清单2-30所示:
代码清单2-30 general.inc.php脚本内容
<?php function __autoload($class_name){ $file = (dirname(__FILE__) . "/libs/classes/$class_name.php"); if(!file_exists($file)){ return false; } else { require_once($file); } }?>
main.php文件用于包含上面的脚本,如代码清单2-30所示:
代码清单2-31 main.php脚本内容
<?php require_once("general.inc.php"); $obj = new MyClass(); if(is_object($obj)){ $obj->printWorld(); }else{ echo "类文件调入失败。"; }?>
上面的脚本例子将显示下列词句:
物有本末,事有始终,知所先后,则近道矣
词语本意可以忽略,从结果我们可以看到在MyClass.php中并未明确指出包含哪个文件,但是由于使用了__autoload()方法,把经常使用的类自动引入进来,如general.inc文件的内容,所以能显示上述的内容。
需要注意的是,虽然PHP对类名的大小写不敏感,但__autoload()方法会按你发送给它的类名严格进行大小写匹配。
如果你喜欢大小写混合的方式来命名一个类,那么在源代码内也要保持大小写一致,这样才可能引入你需要的类库。如果不太喜欢这样做,可以用strtolower()函数,在类引入之前强制命名为小写的形式。
另外,这里的内容可以再结合本书的include内容部分,更好地融会贯通。