PHP中的魔术方法详解

来源:文书网 5.04K

在PHP的学习中,以两个下划线__开头的方法称为魔术方法,下面小编就他们的用法整理出以下文章,希望对大家学习PHP有帮助,更多内容请关注应届毕业生网!

PHP中的魔术方法详解
  前言

PHP中把以两个下划线__开头的方法称为魔术方法(Magic methods),这些方法在PHP中充当了举足轻重的作用。 魔术方法包括:

__sleep(),执行serialize()时,先会调用这个函数

__wakeup(),执行unserialize()时,先会调用这个函数

__toString(),类被当成字符串时的回应方法

__invoke(),调用函数的方式调用一个对象时的回应方法

__set_state(),调用var_export()导出类时,此静态方法会被调用。

__clone(),当对象复制完成时调用

__autoload(),尝试加载未定义的类

__debugInfo(),打印所需调试信息

  范例

下面让我们以实例的形式向大家讲解下这几个魔术方法时如何使用的。

范例

  九、 __sleep(),执行serialize()时,先会调用这个函数

serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,则该方法会优先被调用,然后才执行序列化操作。

此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。

如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。

注意:

__sleep() 不能返回父类的私有成员的名字。这样做会产生一个 E_NOTICE 级别的错误。可以用 Serializable 接口来替代。

作用:

__sleep() 方法常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。

具体请参考如下代码:

12345678<?phpclassPerson{public$sex;private$name;private$age;publicfunction__construct($name="",$age=25,$sex='男'){$this->name=$name;$this->age=$age;$this->sex=$sex;}/***@param$content**@returnbool*/publicfunction__isset($content){echo"当在类外部使用isset()函数测定私有成员{$content}时,自动调用<br>";echoisset($this->$content);}}$person=newPerson("小明",25);//初始赋值echoisset($person->sex),"<br>";echoisset($person->name),"<br>";echoisset($person->age),"<br>";

代码运行结果:

当在类外部使用serialize()时会调用这里的__sleep()方法

O:6:"Person":2:{s:4:"name";s:8:"5bCP5piO";s:3:"age";i:25;}

十、 __wakeup(),执行unserialize()时,先会调用这个函数

如果说 __sleep() 是白的,那么 __wakeup() 就是黑的了。

那么为什么呢?

因为:

与之相反,`unserialize()` 会检查是否存在一个 `__wakeup()` 方法。如果存在,则会先调用 `__wakeup` 方法,预先准备对象需要的资源。

作用:

__wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。

还是看代码:

12345678<?phpclassPerson{public$sex;private$name;private$age;publicfunction__construct($name="",$age=25,$sex='男'){$this->name=$name;$this->age=$age;$this->sex=$sex;}/***@param$content**@returnbool*/publicfunction__isset($content){echo"当在类外部使用isset()函数测定私有成员{$content}时,自动调用<br>";echoisset($this->$content);}}$person=newPerson("小明",25);//初始赋值echoisset($person->sex),"<br>";echoisset($person->name),"<br>";echoisset($person->age),"<br>";

当在类外部使用serialize()时会调用这里的.__sleep()方法

string(58) "O:6:"Person":2:{s:4:"name";s:8:"5bCP5piO";s:3:"age";i:25;}" 当在类外部使用serialize()时会调用这里的__sleep()方法 当在类外部使用unserialize()时会调用这里的__wakeup()方法 object(Person)#2 (3) { ["sex"]=> string(3) "男" ["name"]=> int(2) ["age"]=> int(25) }

 十一、 __toString(),类被当成字符串时的回应方法  作用:

__toString() 方法用于一个类被当成字符串时应怎样回应。例如 `echo $obj;` 应该显示些什么。

注意:

此方法必须返回一个字符串,否则将发出一条 `E_RECOVERABLE_ERROR` 级别的致命错误。

警告:

不能在 __toString() 方法中抛出异常。这么做会导致致命错误。

代码:

12345678<?phpclassPerson{public$sex;public$name;public$age;publicfunction__construct($name="",$age=25,$sex='男'){$this->name=$name;$this->age=$age;$this->sex=$sex;}publicfunction__toString(){return'gogogo';}}$person=newPerson('小明');//初始赋值echo$person;

结果:

go go go

那么如果类中没有 __toString() 这个魔术方法运行会发生什么呢?让我们来测试下:

代码:

12345678<?phpclassPerson{public$sex;public$name;public$age;publicfunction__construct($name="",$age=25,$sex='男'){$this->name=$name;$this->age=$age;$this->sex=$sex;}}$person=newPerson('小明');//初始赋值echo$person;

结果:

Catchable fatal error: Object of class Person could not be converted to string in D:phpStudyWWWtest on line 18

很明显,页面报了一个致命错误,这是语法所不允许的。

 十二、 __invoke(),调用函数的方式调用一个对象时的回应方法

作用:

当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。

注意:

本特性只在 PHP 5.3.0 及以上版本有效。

直接上代码:

123456789<?phpclassPerson{public$sex;public$name;public$age;publicfunction__construct($name="",$age=25,$sex='男'){$this->name=$name;$this->age=$age;$this->sex=$sex;}publicfunction__invoke(){echo'这可是一个对象哦';}}$person=newPerson('小明');//初始赋值$person();

结果:

Catchable fatal error: Object of class Person could not be converted to string in D:phpStudyWWWtest on line 18

很明显,页面报了一个致命错误,这是语法所不允许的。

 十二、 __invoke(),调用函数的方式调用一个对象时的回应方法

作用:

当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。

注意:

本特性只在 PHP 5.3.0 及以上版本有效。

直接上代码:

123456789<?phpclassPerson{public$sex;public$name;public$age;publicfunction__construct($name="",$age=25,$sex='男'){$this->name=$name;$this->age=$age;$this->sex=$sex;}publicfunction__invoke(){echo'这可是一个对象哦';}}$person=newPerson('小明');//初始赋值$person();

查看运行结果:

这可是一个对象哦

当然,如果你执意要将对象当函数方法使用,那么会得到下面结果:

Fatal error: Function name must be a string in D:phpStudyWWWtest online 18

 十三、 __set_state(),调用var_export()导出类时,此静态方法会被调用。

作用:

自 PHP 5.1.0 起,当调用 var_export() 导出类时,此静态方法会被自动调用。

参数:

本方法的唯一参数是一个数组,其中包含按 array(‘property’ => value, …) 格式排列的类属性。

下面我们先来看看在没有加 __set_state() 情况按下,代码及运行结果如何:

上代码:

123456789<?phpclassPerson{public$sex;public$name;public$age;publicfunction__construct($name="",$age=25,$sex='男'){$this->name=$name;$this->age=$age;$this->sex=$sex;}}$person=newPerson('小明');//初始赋值var_export($person);

看结果:

Person::__set_state(array( 'sex' => '男', 'name' => '小明', 'age' => 25, ))

很明显,将对象中的属性都打印出来了

加了 __set_state() 之后:

继续上代码:

123456789<?phpclassPerson{public$sex;public$name;public$age;publicfunction__construct($name="",$age=25,$sex='男'){$this->name=$name;$this->age=$age;$this->sex=$sex;}publicstaticfunction__set_state($an_array){$a=newPerson();$a->name=$an_array['name'];return$a;}}$person=newPerson('小明');//初始赋值$person->name='小红';var_export($person);

继续看结果:

Person::__set_state(array( 'sex' => '男', 'name' => '小红', 'age' => 25, ))

十四、 __clone(),当对象复制完成时调用

在多数情况下,我们并不需要完全复制一个对象来获得其中属性。但有一个情况下确实需要:如果你有一个 GTK 窗口对象,该对象持有窗口相关的资源。你可能会想复制一个新的窗口,保持所有属性与原来的窗口相同,但必须是一个新的对象(因为如果不是新的对象,那么一个窗口中的改变就会影响到另一个窗口)。还有一种情况:如果对象 A 中保存着对象 B 的引用,当你复制对象 A 时,你想其中使用的对象不再是对象 B 而是 B 的一个副本,那么你必须得到对象 A 的一个副本。

作用:

对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的 __clone() 方法)。对象中的 __clone() 方法不能被直接调用。

语法:

$copy_of_object = clone $object;

注意:

当对象被复制后,PHP 5 会对对象的所有属性执行一个浅复制(shallow copy)。所有的引用属性 仍然会是一个指向原来的变量的引用。

当复制完成时,如果定义了 __clone() 方法,则新创建的对象(复制生成的对象)中的 __clone() 方法会被调用,可用于修改属性的值(如果有必要的话)。

看代码:

123456789<?phpclassPerson{public$sex;public$name;public$age;publicfunction__construct($name="",$age=25,$sex='男'){$this->name=$name;$this->age=$age;$this->sex=$sex;}publicfunction__clone(){echo__METHOD__."你正在克隆对象<br>";}}$person=newPerson('小明');//初始赋值$person2=clone$person;var_dump('persion1:');var_dump($person);echo'<br>';var_dump('persion2:');var_dump($person2);

看结果:

Person::__clone你正在克隆对象

string(9) "persion1:" object(Person)#1 (3) { ["sex"]=> string(3) "男" ["name"]=> string(6) "小明" ["age"]=> int(25) } string(9) "persion2:" object(Person)#2 (3) { ["sex"]=> string(3) "男" ["name"]=> string(6) "小明" ["age"]=> int(25) }

克隆成功。

十五、__autoload(),尝试加载未定义的类

作用:

你可以通过定义这个函数来启用类的自动加载。

在魔术函数 __autoload() 方法出现以前,如果你要在一个程序文件中实例化100个对象,那么你必须用include或者require包含进来100个类文件,或者你把这100个类定义在同一个类文件中 —— 相信这个文件一定会非常大,然后你就痛苦了。

但是有了 __autoload() 方法,以后就不必为此大伤脑筋了,这个类会在你实例化对象之前自动加载制定的文件。

还是通过例子来看看吧:

先看看以往的方式:

123456789101112131415161718/***文件non_*/require_once('project/class/');require_once('project/class/');require_once('project/class/');if(条件A){$a=newA();$b=newB();$c=newC();//…业务逻辑}elseif(条件B){$a=newA();$b=newB();//…业务逻辑}

看到了吗?不用100个,只是3个看起来就有点烦了。而且这样就会有一个问题:如果脚本执行“条件B”这个分支时,这个文件其实没有必要包含。因为,任何一个被包含的文件,无论是否使用,均会被php引擎编译。如果不使用,却被编译,这样可以被视作一种资源浪费。更进一步,如果包含了,包含了。并且大部分情况都执行“条件B”分支,那么就会浪费一部分资源去编译,,三个“无用”的文件。

那么如果使用 __autoload() 方式呢?

1234567891011121314151617181920/***文件autoload_*/function__autoload($className){$filePath=“project/class/{$className}”;if(is_readable($filePath)){require($filePath);}}if(条件A){$a=newA();$b=newB();$c=newC();//…业务逻辑}elseif(条件B){$a=newA();$b=newB();//…业务逻辑}

ok,不论效率怎么用,最起码界面看起来舒服多了,没有太多冗余的代。

再来看看这里的效率如何,我们分析下:

当php引擎第一次使用类A,但是找不到时,会自动调用 __autoload 方法,并将类名“A”作为参数传入。所以,我们在 __autoload() 中需要的做的就是根据类名,找到相应的文件,并包含进来,如果我们的方法也找不到,那么php引擎就会报错了。

注意:

这里可以只用require,因为一旦包含进来后,php引擎再遇到类A时,将不会调用__autoload,而是直接使用内存中的类A,不会导致多次包含。

扩展:

其实php发展到今天,已经有将 `spl_autoload_register` — 注册给定的函数作为 __autoload 的实现了,但是这个不在啊本文讲解之内,有兴趣可以自行看手册。

十六、__debugInfo(),打印所需调试信息

注意:

该方法在PHP 5.6.0及其以上版本才可以用,如果你发现使用无效或者报错,请查看啊你的版本。

看代码:

12345678910111213141516171819<?phpclassC{private$prop;publicfunction__construct($val){$this->prop=$val;}/***@returnarray*/publicfunction__debugInfo(){return['propSquared'=>$this->prop**2,];}}var_dump(newC(42));

结果:

object(C)#1 (1) { ["propSquared"]=> int(1764) }

再次注意:

这里的 `**` 是乘方的意思,也是在PHP5.6.0及其以上才可以使用,详情请查看PHP手册

总结

以上就是PHP中我了解到的魔术方法了,常用的包括 __set() __get() __autoload() 等应该熟悉,其他的了解也没有关系,毕竟知识不怕多嘛。

热门标签