昨天答辩实在曲折,到现在还没有开始,废话不说了,感觉继续奉上教程笔记。
今天我们要说的是__call() 和其他一些不常用的模式方法,我也许只会点到一下,如果你想搞的非常透彻还需要自己下功夫。
__call() 是个非常重要的魔术方法,可以说非常非常的重要。至于为什么,相信你学习完今天的笔记就会明白了。
当我们实例化一个类的对象后,调用类中的一个方法,比如$test->fun();。系统就会从这个类中去找fun()这个方法,如果找到了就去执行它,如果没有找到就去调用 __call()。
我们来看下例子:
[php]
class Test {
public function __call($fun, $args) { //第一次参数是方法名,第二个参数是传过来的参数,是以数组的方式传过来的。
echo "你在调用".$fun."方法";
print_r($args);
}
}
$test = new Test();
$test->fun("a","b"); //调用一个不存在的方法 参数是a和b .
//输出 你在调用test方法Array ( [0] => a [1] => b )
[/php]
从上面的例子,我们可以清晰的分析出__call()方法的执行过程。但是我们想,如果类中存在一个同名的private方法,PHP会如何处理呢?我们来看下面的例子。
[php]
class Test {
public function __call($fun, $args) {
echo "你在调用".$fun."方法";
print_r($args);
}
private function fun($a, $b) {
echo "你再调用类中的private方法";
}
}
$test = new Test();
$test->fun("a","b"); //调用类中存在的private方法
//输出 Fatal error: Call to private method Test::fun() from context '' in PHPDocument3 on line 14
[/php]
结果输出了错误,看来这种时候,魔术方法也不起作用。系统直接报错了。
现在我们再来想,如果调用本类中不存在的方法,而父类中存在的方法,是先调用本类的__call()呢还是先调用父类中存在的方法呢?我们继续来做实验。
[php]
class Test {
public function __call($fun, $args) {
echo "你在调用父类的".$fun."方法";
print_r($args);
}
public function fun() {
echo "你在调用父类的test方法";
}
}
class Test2 extends Test {
public function __call($fun, $args) {
echo "你在调用子类的".$fun."方法";
print_r($args);
}
}
$test2 = new Test2();
$test2->fun("a","b"); //调用父类存在的方法 输出 你在调用父类的test方法
$test2->abc(); //调用都不存在的方法 输出 你在调用子类的abc方法Array ( )
[/php]
结果,我们可到这样的现象,子类跳过了__call()而直接寻找父类中的方法,如果调用一个都不存在的方法,就调用本类的__call()方法,这个是由于子类的__call()把父类的__call()给覆盖了。
所以我们就可以得出这样的结论,在PHP中,系统对__call()方法的调用顺序是这样的:
下面说我为什么说__call()重要。
当我们实例化一个类的对象后,调用类中的一个方法,比如$test->fun();。系统就会从这个类中去找fun()这个方法,如果存在就调用 之,如果不存在就去父类中找这个方法,如果父类中也不存在就去找本类的__call()方法,如果本类中不存在__call()方法就去找父类中的方法, 以此类推。当然这个是在调用权限允许的范围内的。
我们都知道,在PHP5中并没有真正的支持重载。但是我们可以想办法去实现它,借助的工具就是__call()。我们来看例子。
[php]
class Test {
public function __call($fun, $argps) {
if (method_exists($this, $fun.count($argps))) {
return call_user_func_array(array(&$this, $fun.count($argps)), $argps);
} else {
throw new Exception('调用了未知方法:'.get_class($this).'->'.$fun); //不存在的方法
}
}
//一个参数的方法
public function fun1($a) {
echo "你在调用一个参数的方法,参数为:".$a;
}
//两个参数的方法
public function fun2($a, $b) {
echo "你在调用两个参数的方法,参数为:".$a."和".$b;
}
}
$test = new Test();
$test->fun("a"); //输出 你在调用一个参数的方法,参数为:a
$test->fun("a","b"); //输出 你在调用两个参数的方法,参数为:a和b
$test->fun("a","b","c"); //输出 Fatal error: Uncaught exception 'Exception' with message '调用了未知方法:Test->fun'
[/php]
这样我们基本就可以完成了重载了。
另外提下下面不常用的两个魔术方法。
第一个是__toString() 。这个著名的方法在Java中是非常常见的,但是到PHP这里我基本没见多少人用过了。原因是这样的,我们看下例子:
[php]
class Test {
public $a;
public function fun(){
echo "我只是一个方法而已.";
}
}
$test = new Test();
echo $test; //输出错误 Catchable fatal error: Object of class Test could not be converted to string
[/php]
如果我们想打印出一个对象,就需要调用__toString()这个魔术方法了,我们给他加上一个__toString()就不会出错了。
[php]
class Test {
public $a;
public function fun(){
echo "我只是一个方法而已.";
}
public function __toString() {
return "你在打印一个对象"; //这个方法必须返回一个字符串
}
}
$test = new Test();
echo $test; //输出 你在打印一个对象
[/php]
这个比较简单,我不多说了,还有一个方法,在Google搜索都搜不到几个有价值的东西,可见其少用的程度,汗一下。这个魔术方法就是:__set_state();
这个方法其实也比较简单,就是var_export()的回调函数。我们来看下例子。
[php]
class Test {
public $a;
public function fun(){
echo "我只是一个方法而已.";
}
}
$test = new Test();
var_export($test); //输出 Test::__set_state(array( 'a' => NULL, ))
[/php]
注意a是NULL,没有赋值,下面我写个回调
[php]
class Test {
public $a;
static function __set_state($array) { //必须为静态方法.参数是个数组
$tmp = new Test();
$tmp->a = "abc"; //我直接赋值,
return $tmp; //必须返回一个对象.可以是其他类的对象
}
}
$test = new Test();
eval( '$b = ' . var_export($test, true). ';' );
var_dump($b); //得到的$b就是Test的一个对象.并且a="abc"
[/php]
有人问这有什么用?是没多大用,最大的作用可以复制一个对象。只需改下代码而已。
[php]
class Test {
public $a;
static function __set_state($array) { //必须为静态方法.参数是个数组
$tmp = new Test();
$tmp->a = $array['a'];
return $tmp; //必须返回一个对象.可以是其他类的对象
}
}
$test = new Test();
$test->a = "我是$test";
eval( '$b = ' . var_export($test, true). ';' );
var_dump($b); //得到的$b就是$test的复制.并且a="abc"
[/php]
有人又会问了,克隆可以直接clone()方法啊!!那么好,这样的情况请问你如何克隆,我们来看下代码:
[php]
class Test {
public $a;
static function __set_state($array) { //必须为静态方法.参数是个数组
$tmp = new Test();
$tmp->a = str_replace('$test','$b',$array['a']); //!important
return $tmp; //必须返回一个对象.可以是其他类的对象
}
}
$test = new Test();
$test->a = '我是$test';
eval( '$b = ' . var_export($test, true). ';' );
var_dump($b); //得到的$b就是$test的复制.但是b做相应的改变b='我是$b'
[/php]
这样的话,我们虽然克隆了一个$test,但是我们又做了相应的改变。请允许我称为这种方法为“进化”。进化就是在克隆的基础上做一些自己相应的改变。
有的人问,你为什么不用__clone进行回调呢?这正是我想说的地方,个人认为__clone() 没有 __set_state()强大,因为__clone()没有可以接受的参数,局限了“进化”的范围,我用个例子说明。
[php]
class Test {
public $a;
function __clone(){
$this->a = str_replace("a","克隆a",$this->a); //无法通过参数改变a,但是我们还是可以想办法的:)
}
}
$test = new Test();
$test->a = '我是a';
$b = clone $test;
var_dump($b); //得到的$b就是$test的复制.并且a='我是克隆a'
[/php]
真累啊,基本方法都提到了,但是我只是带大家入门,更多更深的内容还需要大家自己挖掘。有些地方我还没有提到,所以大家要学会去主动学习。
另外,顺便提下,这些魔术方法都必须被定义为public方式的。因为你要在所有的地方都得调用它们。
好了。这个还没完,下此会有更神奇的东西,改变你对PHP的看法,请关注。
[php]
<?php
class Test {
private $mySecret; //我的秘密不想让人知道
public function __construct($secret) {
$this->mySecret = $secret;
}
public function __sleep() {
$this->mySecret = "这是个秘密!"; // 外部不可见.必须调用成员方法输出值.
echo '不让你知道';
}
public function __wakeup() {
}
public function get(){
return $this->mySecret;
}
}
$obj = new Test('i like php !');
echo $obj->get();
echo '<br />';
serialize($obj); // __sleep()
echo $obj->get();
echo '<br />';
unserialize($obj); // __wakeup()
echo $obj->get();
$obj = new Test('php');
echo $obj->get();
?>
[/php]
(责任编辑:ken)