这是php编程法则?
php中常用的魔术方法
对于PHP开发者来说,掌握一些编程法则是十分重要的。而在PHP中,以双下划线(__)开头的方法称为魔术方法,它们扮演着非常重要的角色。
常用的魔术方法包括:
-__construct():类的构造方法;
-__destruct():类的析构方法;
-__call($funName, $arguments):当访问未定义或没有访问权限的方法时,__call()会被调用;
-__callStatic($funName, $arguments):当访问未定义或没有访问权限的静态方法时,__call()会被调用;
-__get($propertyName):读取类的成员变量时__get()会被调用;
-__set($property, $value):写入类的成员变量时__set()会被调用;
-__isset($content):当针对未定义或没有访问权限的成员使用isset()或empty()时__isset()会被调用;
-__unset($content):在未定义或没有访问权限的成员上使用reset()时__unset()会被调用;
-__sleep():在执行serialize()时__sleep()会被调用;
-__wakeup():在执行deserialization()时__wakeup()会被调用;
-__toString():使用echo方法直接输出对象时,__toString()会被调用;
-__invoke():像调用函数一样调用对象时,对象的__invoke()会被调用;
-__set_state($an_array):调用var_export()时__set_state()会被调用;
-__clone():复制对象时__clone()会被调用;
-__autoload($className):试图加载未定义的类;
-__debuginfo():输出调试信息。
本文将通过具体点的例子说明,这些PHP魔术方法的用法。
1) 析构方法的声明格式
1 2 3 4 |
<span class="">function __destruct() </span>{ <span class="">// 方法体</span> } |
注意:析构方法不能带任何参数。
2) 析构方法的用法
一般来说,PHP中析构方法并不是太常用。在类中它是可选的,通常用于在对象销毁之前执行某些清理工作。
下面的例子演示了如何使用析构方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<span class=""><?php class Person{ public $name; public $age; public $sex; public function __construct($name="", $sex="Male", $age=22) { $this->name = $name; $this->sex = $sex; $this->age = $age; } /** * say方法 */ public function say() { echo "Name:".$this->name.",Sex:".$this->sex.",Age:".$this->age; } /** * 定义析构方法 */ public function __destruct() { echo "Well, my name is ".$this->name; } } $Person = new Person("calvin"); unset($Person); // 销毁上面创建的$Person对象</span> |
以上程序的输出结果为:
1 |
Well, my name <span class="">is </span></code>calvin |
3. __call()
该方法有两个参数。第一个参数$function_name自动接收未定义方法的名称,第二个参数$arguments以数组的方式接收该方法调用的多个参数。
1) __call()方法的用法
1 2 3 4 |
<span class="">function __call(string $function_name, array $arguments) </span>{ <span class="">// 方法体</span> } |
程序中调用未定义的方法时,__call()方法会自动被调用。
示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<span class=""><?php class Person { function say() { echo "Hello, world!<br>"; } function __call($funName, $arguments) { echo "The function you called:" . $funName . "(parameter:" ; // 输出不存在的方法的名称 print_r($arguments); // 输出不存在的方法的参数列表 echo ")does not exist!!<br>\n"; } } $Person = new Person(); $Person->run("teacher"); // 如果对象内不存在的方法被调用,则 __call() 方法会被自动调用 $Person->eat("calvin", "apple"); $Person->say();</span> |
输出结果如下:
1 2 3 |
The <span class="">function you called: run (parameter: Array([0] => teacher)</span>) does <span class="">not</span> exist! The <span class="">function you called: eat (parameter: Array([0] => calvin[1] => apple)</span>) does <span class="">not</span> exist! Hello world! |
4. __callStatic()
当程序中调用未定义的静态方法时,__callStatic()方法会被调用。
__callStatic()的用法与__call()类似。示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<span class=""><?php class Person { function say() { echo "Hello, world!<br>"; } public static function __callStatic($funName, $arguments) { echo "The static method you called:" . $funName . "(parameter:" ; // 输出不存在的方法的名称 print_r($arguments); // 输出不存在的方法的参数列表 echo ")does not exist!<br>\n"; } } $Person = new Person(); $Person::run("teacher"); // 如果对象内不存在的方法被调用,则 __callStatic() 方法会被自动调用 $Person::eat("calvin", "apple"); $Person->say();</span> |
输出结果如下:
1 2 3 4 |
The <span class="">static</span> method you called: run (parameter: <span class="">Array</span>([<span class="">0</span>] => teacher)) does not exist! The <span class="">static</span> method you called: eat (parameter: <span class="">Array</span>([<span class="">0</span>] => calvin[<span class="">1</span>] => apple)) does not exist! Hello world! |
5. __get()
当试图访问外部对象的私有属性时,程序会抛出异常并结束执行。但我们可以使用__get()方法来解决这个问题。它能在对象外部取得对象的私有方法。示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<span class=""><?php class Person { private $name; private $age; function __construct($name="", $age=1) { $this->name = $name; $this->age = $age; } public function __get($propertyName) { if ($propertyName == "age") { if ($this->age > 30) { return $this->age - 10; } else { return $this->$propertyName; } } else { return $this->$propertyName; } } } $Person = new Person("calvin", 60); // 用Person类初始化对象,并通过构造方法给属性赋初始值 echo "Name:" . $Person->name . "<br>"; // 访问私有属性时, __get() 方法会自动被调用,这样就能间接取得属性值 echo "Age:" . $Person->age . "<br>"; // __get() 方法自动被调用,并返回不同的值</span> |
输出结果如下:
1 2 |
<span class="">Name</span>: calvin <span class="">Age</span>: 50 |
6. __set()
__set($property, $value) 方法用来设置对象的私有属性。当试图设置对象中未定义的属性时,就会触发__set()方法,调用参数为被设置的属性名和属性值。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<span class=""><?php class Person { private $name; private $age; public function __construct($name="", $age=25) { $this->name = $name; $this->age = $age; } public function __set($property, $value) { if ($property=="age") { if ($value > 150 || $value < 0) { return; } } $this->$property = $value; } public function say(){ echo "My name is ".$this->name.",I'm ".$this->age." years old"; } } $Person=new Person("calvin", 25); // 注意下面的代码会改变初始值 $Person->name = "yuki"; // "name" 属性成功赋值。如果没有 __set() 方法,程序就会抛出异常 $Person->age = 16; // "age" 属性成功赋值 $Person->age = 160; // 160 是个非法值,所以赋值失败 $Person->say(); // 输出:My name is yuki, I'm 16 years old.</span> |
下面是输出结果:
1 |
My name <span class="">is</span> yuki, I<span class="">'m 16 years old</span> |
7. __isset()
在介绍__isset()方法之前,我先介绍下issset()方法。isset()方法主要用于判断某个变量是否被设置。
在对象外部使用isset()方法有两种情况:
- 如果参数是公有属性,那么可以利用isset()方法判断属性是否被设置;
- 如果参数是私有属性,isset()方法将无法使用。
那么,是否有办法判断私有属性被设置呢?当然,只需要在类里定义__isset()方法,就可以在对象外部利用isset()方法判断某个私有属性是否被设置了。
对未定义或没有权限访问的属性调用isset()或empty()时,就会调用__isset()方法。示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<span class=""><?php class Person { public $sex; private $name; private $age; public function __construct($name="", $age=25, $sex='Male') { $this->name = $name; $this->age = $age; $this->sex = $sex; } /** * @param $content * * @return bool */ public function __isset($content) { echo "The {$content} property is private,the __isset() method is called automatically.<br>"; echo isset($this->$content); } } $person = new Person("calvin", 25); // 赋初始值 echo isset($person->sex),"<br>"; echo isset($person->name),"<br>"; echo isset($person->age),"<br>";</span> |
输出结果如下:
1 2 3 4 5 |
<span class="">1</span> The name property <span class="">is</span> <span class="">private</span>,the __isset() method <span class="">is</span> called automatically. <span class="">1</span> The age property <span class="">is</span> <span class="">private</span>,the __isset() method <span class="">is</span> called automatically. <span class="">1</span> |
8. __unset()
与__isset()类似,在未定义或无权限访问的属性上调用unset()方法时会触发__unset()方法。示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<span class=""><?php class Person { public $sex; private $name; private $age; public function __construct($name="", $age=25, $sex='Male') { $this->name = $name; $this->age = $age; $this->sex = $sex; } /** * @param $content * * @return bool */ public function __unset($content) { echo "It is called automatically when we use the unset() method outside the class.<br>"; echo isset($this->$content); } } $person = new Person("calvin", 25); // 赋初始值 unset($person->sex),"<br>"; unset($person->name),"<br>"; unset($person->age),"<br>";</span> |
输出结果如下:
1 2 3 4 |
It <span class="">is</span> called automatically <span class="">when</span> we use the unset() method outside the <span class="">class.</span> <span class="">1</span> It <span class="">is</span> called automatically <span class="">when</span> we use the unset() method outside the <span class="">class.</span> <span class="">1</span> |
9. __sleep()
serialize()方法会检查类中是否存在__sleep()魔术方法。如果存在,就会调用该方法来执行序列化操作。
__sleep()方法通常用来在保存数据之前指定哪些属性需要被序列化。如果对象中包含一些完全不需要序列化的巨大对象,__sleep()就能派上用场了。
具体用法请参考以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<span class=""><?php class Person { public $sex; public $name; public $age; public function __construct($name="", $age=25, $sex='Male') { $this->name = $name; $this->age = $age; $this->sex = $sex; } /** * @return array */ public function __sleep() { echo "It is called when the serialize() method is called outside the class.<br>"; $this->name = base64_encode($this->name); return array('name', 'age'); // 返回值中的元素必须是属性的名称 } } $person = new Person('calvin'); // Initially assigned. echo serialize($person); echo '<br/>';</span> |
输出结果如下:
1 2 |
<span class="">It</span> <span class="">is</span> <span class="">called</span> <span class="">when</span> <span class="">the</span> <span class="">serialize</span>() <span class="">method</span> <span class="">is</span> <span class="">called</span> <span class="">outside</span> <span class="">the</span> <span class="">class</span>. <span class="">O</span><span class="">:6</span><span class="">:"Person"</span><span class="">:2</span>:{<span class="">s</span>:<span class="">4</span>:<span class="">"name"</span>;<span class="">s</span>:<span class="">8</span>:<span class="">"5bCP5piO"</span>;<span class="">s</span>:<span class="">3</span>:<span class="">"age"</span>;<span class="">i</span>:<span class="">25</span>;} |
10. __wakeup()
与__sleep()方法相对的就是__wakeup()方法,常用来反序列化,如重建数据连接,或执行其他初始化操作等。
示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<span class=""><?php class Person { public $sex; public $name; public $age; public function __construct($name="", $age=25, $sex='Male') { $this->name = $name; $this->age = $age; $this->sex = $sex; } /** * @return array */ public function __sleep() { echo "It is called when the serialize() method is called outside the class.<br>"; $this->name = base64_encode($this->name); return array('name', 'age'); // 返回值中的元素必须是属性的名称 } /** * __wakeup */ public function __wakeup() { echo "It is called when the unserialize() method is called outside the class.<br>"; $this->name = 2; $this->sex = 'Male'; // 这里不需要返回数组 } } $person = new Person('calvin'); // 赋初始值 var_dump(serialize($person)); var_dump(unserialize(serialize($person)));</span> |
输出结果如下:
1 2 3 4 |
It <span class="">is</span> called <span class="">when</span> the serialize() method <span class="">is</span> called outside the <span class="">class.</span> string(<span class="">58</span>) <span class="">"O:6:"</span>Person<span class="">":2:{s:4:"</span>name<span class="">";s:8:"</span><span class="">5</span>bCP5piO<span class="">";s:3:"</span>age<span class="">";i:25;}"</span> It <span class="">is</span> called <span class="">when</span> the unserialize() method <span class="">is</span> called outside the <span class="">class.</span> <span class="">object</span>(Person)#<span class="">2</span> (<span class="">3</span>) { [<span class="">"sex"</span>]=> string(<span class="">3</span>) <span class="">"Male"</span> [<span class="">"name"</span>]=> int(<span class="">2</span>) [<span class="">"age"</span>]=> int(<span class="">25</span>) } |
11. __toString()
使用echo方法直接输出对象时会调用其__toString()方法。
注意:该方法必须返回字符串,否则会抛出”E_RECOVERABLE_ERROR”级别的异常。在__toString()方法中也不能抛出异常。
示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<span class=""><?php class Person { public $sex; public $name; public $age; public function __construct($name="", $age=25, $sex='Male') { $this->name = $name; $this->age = $age; $this->sex = $sex; } public function __toString() { return 'go go go'; } } $person = new Person('calvin'); // 赋初始值 echo $person;</span> |
返回结果如下:
1 |
<span class="">go</span> <span class="">go</span> <span class="">go</span> |
如果类中没有定义__toString()会怎样?我们来试试看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<span class=""><?php class Person { public $sex; public $name; public $age; public function __construct($name="", $age=25, $sex='Male') { $this->name = $name; $this->age = $age; $this->sex = $sex; } } $person = new Person('calvin'); // 赋初始值 echo $person;</span> |
返回结果如下:
1 |
Catchable fatal <span class="">error</span>: Object of class Person could <span class="">not</span> be converted to <span class="">string</span> <span class="">in</span> D:\phpStudy\WWW\test\index.php on line <span class="">18</span> |
可见,它会在页面上报告致命错误,说明这种用法不允许。
12. __invoke()
当试图用调用函数的方式调用对象时,就会自动调用其__invoke()方法。
注意:该功能只在PHP 5.3.0以及以上版本上有效。
示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<span class=""><?php class Person { public $sex; public $name; public $age; public function __construct($name="", $age=25, $sex='Male') { $this->name = $name; $this->age = $age; $this->sex = $sex; } public function __invoke() { echo 'This is an object'; } } $person = new Person('calvin'); // 赋初始值 $person();</span> |
输出结果如下:
1 |
This <span class="">is</span> an <span class="">object</span> |
如果在未定义__invoke()方法的情况下将对象作为函数使用,就会得到以下的结果:
1 |
Fatal error: Function name must be a <span class="">string</span> <span class="">in</span> D:\phpStudy\WWW\test\index.php <span class="">on</span> line <span class="">18</span> |
13. __set_state()
从PHP 5.1.0开始,__set_state()方法会在调用var_export()导出类代码时自动被调用。
__set_state()方法的参数是个数组,包含所有属性的值,格式为array(‘property’ => value, …)。
下面的示例中没有定义__set_state()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<span class=""><?php class Person { public $sex; public $name; public $age; public function __construct($name="", $age=25, $sex='Male') { $this->name = $name; $this->age = $age; $this->sex = $sex; } } $person = new Person('calvin'); // 赋初始值 var_export($person);</span> |
输出结果如下:
1 |
Person::__set_state(<span class="">array</span>( <span class="">'sex'</span> => <span class="">'Male'</span>, <span class="">'name'</span> => <span class="">'calvin'</span>, <span class="">'age'</span> => <span class="">25</span>, )) |
可见,输出结果是对象的属性。
下面来看看如果定义了__set_state()方法会怎样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<span class=""><?php class Person { public $sex; public $name; public $age; public function __construct($name="", $age=25, $sex='Male') { $this->name = $name; $this->age = $age; $this->sex = $sex; } public static function __set_state($an_array) { $a = new Person(); $a->name = $an_array['name']; return $a; } } $person = new Person('calvin'); // 赋初始值 $person->name = 'Jams'; var_export($person);</span> |
输出结果如下:
1 |
Person::__set_state(<span class="">array</span>( <span class="">'sex'</span> => <span class="">'Male'</span>, <span class="">'name'</span> => <span class="">'Jams'</span>, <span class="">'age'</span> => <span class="">25</span>, )) |
14. __clone()
PHP中可以使用clone关键字来复制对象,其格式如下:
1 |
<span class="">$copy_of_object</span> = <span class="">clone</span> <span class="">$object</span>; |
但是,clone关键字只会进行浅复制,所有引用的属性依然会指向原来的变量。
如果对象里定义了__clone()方法,那么复制时就会调用__clone()方法,从而允许我们修改被复制的值(如果需要的话)。
示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<span class=""><?php class Person { public $sex; public $name; public $age; public function __construct($name="", $age=25, $sex='Male') { $this->name = $name; $this->age = $age; $this->sex = $sex; } public function __clone() { echo __METHOD__."your are cloning the object.<br>"; } } $person = new Person('calvin'); // 赋初始值 $person2 = clone $person; var_dump('persion1:'); var_dump($person); echo '<br>'; var_dump('persion2:'); var_dump($person2);</span> |
输出结果如下:
1 2 3 |
Person::__clone your are cloning the object. <span class="">string</span>(<span class="">9</span>) <span class="">"persion1:"</span> object(Person)#<span class="">1</span> (<span class="">3</span>) { [<span class="">"sex"</span>]=> <span class="">string</span>(<span class="">3</span>) <span class="">"Male"</span> [<span class="">"name"</span>]=> <span class="">string</span>(<span class="">6</span>) <span class="">"calvin"</span> [<span class="">"age"</span>]=> <span class="">int</span>(<span class="">25</span>) } <span class="">string</span>(<span class="">9</span>) <span class="">"persion2:"</span> object(Person)#<span class="">2</span> (<span class="">3</span>) { [<span class="">"sex"</span>]=> <span class="">string</span>(<span class="">3</span>) <span class="">"Male"</span> [<span class="">"name"</span>]=> <span class="">string</span>(<span class="">6</span>) <span class="">"calvin"</span> [<span class="">"age"</span>]=> <span class="">int</span>(<span class="">25</span>) } |
15. __autoload()
__autoload()方法可以尝试加载未定义的类。
以前,如果在整个程序的生命周期内创建100个对象,就要用include()或require()包含100个类文件,或者在同一个类文件内定义100个类,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<span class="">/** * file non_autoload.php */</span> <span class="">require_once</span>(<span class="">'project/class/A.php'</span>); <span class="">require_once</span>(<span class="">'project/class/B.php'</span>); <span class="">require_once</span>(<span class="">'project/class/C.php'</span>); . . . <span class="">if</span> (ConditionA) { $a = <span class="">new</span> A(); $b = <span class="">new</span> B(); $c = <span class="">new</span> C(); <span class="">// …</span> } <span class="">else</span> <span class="">if</span> (ConditionB) { $a = newA(); $b = <span class="">new</span> B(); <span class="">// …</span> } |
那么使用__autoload()方法呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<span class="">/** * 文件 autoload_demo.php */</span> <span class="">function __autoload($className) </span>{ $filePath = “project/<span class="">class/</span>{$className}.php”; <span class="">if</span> (is_readable($filePath)) { <span class="">require</span>($filePath); } } <span class="">if</span> (ConditionA) { $a = <span class="">new</span> A(); $b = <span class="">new</span> B(); $c = <span class="">new</span> C(); <span class="">// …</span> } <span class="">else</span> <span class="">if</span> (ConditionB) { $a = newA(); $b = <span class="">new</span> B(); <span class="">// …</span> } |
当PHP引擎第一次使用类A时,如果A没有找到,就会调用__autoload方法,参数为类名”A”。然后我们需要在__autoload()方法中根据类名找到相应的类文件并包含该文件。如果文件没有找到,PHP引擎就会抛出异常。
16. __debugInfo()
执行var_dump()方法的时候会调用__debugInfo()方法。如果__debugInfo()没有定义,则var_dump()方法会输出对象中的所有属性。
示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<span class=""><?php class C { private $prop; public function __construct($val) { $this->prop = $val; } /** * @return array */ public function __debugInfo() { return [ 'propSquared' => $this->prop ** 2, ]; } } var_dump(new C(42));</span> |
输出结果如下:
1 |
object(C)#<span class="">1</span> (<span class="">1</span>) { [<span class="">"propSquared"</span>]=> <span class="">int</span>(<span class="">1764</span>) } |
注意:__debugInfo()方法只能用于PHP 5.6.0及更高版本。

原创文章,作者:calvin chan,如若转载,请注明出处:https://www.calvinyuki.com/209.html