带你吃透16个PHP魔术方法

 2803

什么是魔术方法?本篇文章带大家了解一下16个PHP 开发者必知必会的魔术方法,希望对大家有所帮助!


带你吃透16个PHP魔术方法


在PHP中,以双下划线(__)开始命名的方法被称作PHP中的魔术方法,它们在PHP中充当很重要的角色。魔术方法包括:


方法名描述
__construct()类的构造函数
__destruct()类的析构函数
__call($funName, $arguments)当调用一个未定义或不可达方法时, __call() 方法将被调用。
__callStatic($funName, $arguments)当调用一个未定义或不可达的静态方法时, __callStatic() 方法将被调用。
__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()使用调用函数(function)访问一个对象时, __invoke() 方法将首先被调用。
__set_state($an_array)当调用 var_export() 方法时,__set_state() 方法将被调用。
__clone()当对象被复制赋值时,__clone() 方法将被调用。
__autoload($className)试图载入一个未定义的类时调用。
__debugInfo()输出 debug 信息。

本文将使用一些实例展示 PHP 魔术方法的运用。


1.__construct()

当创建对象时,PHP 类的构造方法是第一个被调用的方法。每个类都有构造方法。若你没在类中明确声明定义它,将会有一个默认的无参类构造方法存在,虽然它不会在类中定义出现。

1) 构造方法的运用

类的构造方法通常用于执行一些初始化任务,诸如当创建对象时,为成员初始化赋值。

2) 类中构造方法的声明格式

  1. function __constrct([parameter list]){
  2.  
  3.     方法具体实现 //通常为成员变量初始赋值。
  4.  
  5. }

注意: 在多数类中仅可以声明一个构造方法。因为, PHP 不支持构造方法重载。

下面是个完整的例子:

  1. <?php
  2.     class Person
  3.     {                                                                     
  4.             public $name;       
  5.             public $age;       
  6.             public $sex;       
  7.  
  8.         /**
  9.          * 明确定义含参的构造方法
  10.          */                                                                                      
  11.         public function __construct($name="", $sex="Male", $age=22)
  12.         {     
  13.             $this->name = $name;
  14.             $this->sex = $sex;
  15.             $this->age = $age;
  16.         }
  17.  
  18.         /**
  19.          * say 方法定义
  20.          */
  21.         public function say()
  22.         {
  23.             echo "Name:" . $this->name . ",Sex:" . $this->sex . ",Age:" . $this->age;
  24.         }   
  25.  
  26.     }

无参创建 $Person1 对象。

  1. $Person1 = new Person();
  2. echo $Person1->say(); //显示:Name:,Sex:Male,Age:22

使用一个参数 "Jams" 调用创建 $Person2 对象。

  1. $Person2 = new Person("Jams");
  2. echo $Person2->say(); // 显示: Name: Jams, Sex: Male, Age: 22

使用3个参数调用创建 $Person3 对象。

  1. $Person3 = new Person ("Jack", "Male", 25);
  2. echo $Person3->say(); // 显示:Name: Jack, Sex: Male, Age: 25


2. __destruct()

析构函数与构造函数相反。

析构函数允许你在销毁对象之前执行一些操作,例如关闭文件,清空结果集等等。

析构函数是 PHP 5 引入的新功能。

析构函数的声明与构造函数类似,以两个下划线开头,名称固定为 __destruct()

析构函数的声明

  1. function __destruct()
  2. {
  3.     //method body
  4. }

析构函数不能带参数。

析构函数的使用

析构函数在类中一般不常见。它是类的可选部分,通常用于在类销毁之前完成一些清理任务。

这是使用析构函数的示例:

  1. <?php
  2. class Person{     
  3.  
  4.     public $name;         
  5.     public $age;         
  6.     public $sex;         
  7.  
  8.     public function __construct($name="", $sex="Male", $age=22)
  9.     {   
  10.         $this->name = $name;
  11.         $this->sex  = $sex;
  12.         $this->age  = $age;
  13.     }
  14.  
  15.     /**
  16.      * say method
  17.      */
  18.     public function say()
  19.     {
  20.         echo "Name:".$this->name.",Sex:".$this->sex.",Age:".$this->age;
  21.     }   
  22.  
  23.     /**
  24.      * declare a destructor method
  25.      */
  26.     public function __destruct()
  27.     {
  28.             echo "Well, my name is ".$this->name;
  29.     }
  30. }
  31.  
  32. $Person = new Person("John");
  33. unset($Person); //destroy the object of $Person created above

输出结果

  1. Well, my name is John


3. __call()

该方法接受两个参数。第一个参数为未定义的方法名称,第二个参数则为传入方法的参数构成的数组

使用

  1. function __call(string $function_name, array $arguments)
  2. {
  3.     // method body
  4. }

在程序中调用未定义方法时, __call() 方法将被调用。

示例

  1. <?php
  2. class Person
  3. {                             
  4.     function say()
  5.     {
  6.            echo "Hello, world!<br>";
  7.     }     
  8.  
  9.     function __call($funName, $arguments)
  10.     {
  11.           echo "The function you called:" . $funName . "(parameter:" ;  // Print the method's name that is not existed.
  12.           print_r($arguments); // Print the parameter list of the method that is not existed.
  13.           echo ")does not exist!!<br>\n";                   
  14.     }                                         
  15. }
  16. $Person = new Person();           
  17. $Person->run("teacher"); // If the method which is not existed is called within the object, then the __call() method will be called automatically.
  18. $Person->eat("John", "apple");             
  19. $Person->say();

显示结果

  1. The function you called: run (parameter: Array([0] => teacher)) does not exist!
  2. The function you called: eat (parameter: Array([0] => John[1] => apple)) does not exist!
  3. Hello world!


4. __callStatic()

当在程序中调用未定义的静态方法,__callStatic() 方法将会被自动调用。

__callStatic() 的用法类似于 __call() 。下面举个例子:

  1. <?php
  2. class Person
  3. {
  4.     function say()
  5.     {
  6.         echo "Hello, world!<br>";
  7.     }
  8.  
  9.     public static function __callStatic($funName, $arguments)
  10.     {
  11.         echo "The static method you called:" . $funName . "(parameter:" ;  // 打印出未定义的方法名。
  12.         print_r($arguments); // 打印出未定义方法的参数列表。
  13.         echo ")does not exist!<br>\n";
  14.     }
  15. }
  16. $Person = new Person();
  17. $Person::run("teacher"); // 如果此项目内不存在的方法被调用了,那么 __callStatic() 方法将被自动调用。
  18. $Person::eat("John", "apple");
  19. $Person->say();

执行结果如下:

  1. The static method you called: run (parameter: Array([0] => teacher)) does not exist!
  2. The static method you called: eat (parameter: Array([0] => John[1] => apple)) does not exist!
  3. Hello world!


5. __get()

当你尝试在外部访问对象的私有属性时,应用程序将抛出异常并结束运行。我们可以使用 __get 方法解决该问题。该方法可以获取从对象外部获取私有属性的值。举例如下

  1. <?php
  2. class Person
  3. {
  4.     private $name;
  5.     private $age;
  6.  
  7.     function __construct($name="", $age=1)
  8.     {
  9.         $this->name = $name;
  10.         $this->age = $age;
  11.     }
  12.  
  13.     public function __get($propertyName)
  14.     {   
  15.         if ($propertyName == "age") {
  16.             if ($this->age > 30) {
  17.                 return $this->age - 10;
  18.             } else {
  19.                 return $this->$propertyName;
  20.             }
  21.         } else {
  22.             return $this->$propertyName;
  23.         }
  24.     }
  25. }
  26. $Person = new Person("John", 60);   // Instantiate the object with the Person class and assign initial values to the properties with the constructor.
  27. echo "Name:" . $Person->name . "<br>";   // When the private property is accessed, the __get() method will be called automatically,so we can get the property value indirectly.
  28. echo "Age:" . $Person->age . "<br>";    // The __get() method is called automatically,and it returns different values according to the object itself.

结果显示如下

  1. Name: John
  2. Age: 50


6. __set()

set($property,$value)方法用于设置类的私有属性。分配了未定义的属性后,将触发set()方法,并且传递的参数是设置的属性名称和值。

下面是演示代码:

  1. <?php
  2. class Person
  3. {
  4.     private $name;
  5.     private $age;
  6.  
  7.     public function __construct($name="",  $age=25)
  8.     {
  9.         $this->name = $name;
  10.         $this->age  = $age;
  11.     }
  12.  
  13.     public function __set($property, $value) {
  14.         if ($property=="age")
  15.         {
  16.             if ($value > 150 || $value < 0) {
  17.                 return;
  18.             }
  19.         }
  20.         $this->$property = $value;
  21.     }
  22.  
  23.     public function say(){
  24.         echo "My name is ".$this->name.",I'm ".$this->age." years old";
  25.     }
  26. }
  27.  
  28. $Person=new Person("John", 25); //请注意,类初始化并为“name”和“age”分配初始值。
  29. $Person->name = "Lili";     // "name" 属性值被成功修改。如果没有__set()方法,程序将报错。
  30. $Person->age = 16; // "age"属性修改成功。
  31. $Person->age = 160; //160是无效值,因此修改失败。
  32. $Person->say();  //输出:My name is Lili, I'm 16 years old。

代码运行结果:

  1. My name is Lili, I'm 16 years old


7. __isset()

在使用__isset()方法之前,让我先解释一下isset()方法的用法。isset()方法主要用于确定是否设置了此变量。

如果在对象外部使用isset()方法,则有两种情况:

如果该参数是公共属性,则可以使用isset()方法确定是否设置了该属性。

如果参数是私有属性,则isset()方法将不起作用。

那么对于私有属性,有什么办法知道它是否被设置了吗?当然,只要在类中定义__isset()方法,就可以在类外部使用isset()方法来确定是否设置了私有属性。

当在未定义或不可访问的属性上调用isset()或empty()时,将调用__isset()方法。下面是一个例子:

  1. <?php
  2. class Person
  3. {
  4.     public $sex;
  5.     private $name;
  6.     private $age;
  7.  
  8.     public function __construct($name="",  $age=25, $sex='Male')
  9.     {
  10.         $this->name = $name;
  11.         $this->age  = $age;
  12.         $this->sex  = $sex;
  13.     }
  14.  
  15.     /**
  16.      * @param $content
  17.      *
  18.      * @return bool
  19.      */
  20.     public function __isset($content) {
  21.         echo "The {$content} property is private,the __isset() method is called automatically.<br>";
  22.         echo  isset($this->$content);
  23.     }
  24. }
  25.  
  26. $person = new Person("John", 25); // Initially assigned.
  27. echo isset($person->sex),"<br>";
  28. echo isset($person->name),"<br>";
  29. echo isset($person->age),"<br>";

代码运行结果如下:

  1. 1
  2. The name property is private,the __isset() method is called automatically.
  3. 1
  4. The age property is private,the __isset() method is called automatically.
  5. 1


8. __unset()

isset()方法类似,当在未定义或不可访问的属性上调用unset()方法时,将调用unset()方法。下面是一个例子:

  1. <?php
  2. class Person
  3. {
  4.     public $sex;
  5.     private $name;
  6.     private $age;
  7.  
  8.     public function __construct($name="",  $age=25, $sex='Male')
  9.     {
  10.         $this->name = $name;
  11.         $this->age  = $age;
  12.         $this->sex  = $sex;
  13.     }
  14.  
  15.     /**
  16.      * @param $content
  17.      *
  18.      * @return bool
  19.      */
  20.     public function __unset($content) {
  21.         echo "It is called automatically when we use the unset() method outside the class.<br>";
  22.         echo  isset($this->$content);
  23.     }
  24. }
  25.  
  26. $person = new Person("John", 25); // Initially assigned.
  27. unset($person->sex),"<br>";
  28. unset($person->name),"<br>";
  29. unset($person->age),"<br>";

代码的运行结果如下:

  1. It is called automatically when we use the unset() method outside the class.
  2. 1
  3. It is called automatically when we use the unset() method outside the class.
  4. 1


9. __sleep()

serialize()方法将检查类中是否有魔术方法__sleep()。如果存在,将首先调用该方法,然后执行序列化操作。

__sleep()方法通常用于指定保存数据之前需要序列化的属性。如果有一些非常大的对象不需要全部保存,那么您会发现此功能非常有用。

有关详细信息,请参考以下代码:

  1. <?php
  2. class Person
  3. {
  4.     public $sex;
  5.     public $name;
  6.     public $age;
  7.  
  8.     public function __construct($name="",  $age=25, $sex='Male')
  9.     {
  10.         $this->name = $name;
  11.         $this->age  = $age;
  12.         $this->sex  = $sex;
  13.     }
  14.  
  15.     /**
  16.      * @return array
  17.      */
  18.     public function __sleep() {
  19.         echo "It is called when the serialize() method is called outside the class.<br>";
  20.         $this->name = base64_encode($this->name);
  21.         return array('name', 'age'); // It must return a value of which the elements are the name of the properties returned.
  22.     }
  23. }
  24.  
  25. $person = new Person('John'); // Initially assigned.
  26. echo serialize($person);
  27. echo '<br/>';

代码运行结果如下:

  1. It is called when the serialize() method is called outside the class.
  2. O:6:"Person":2:{s:4:"name";s:8:"5bCP5piO";s:3:"age";i:25;}


10. __wakeup()

sleep()方法相比,wakeup()方法通常用于反序列化操作,例如重建数据库连接或执行其他初始化操作。

下面是相关实例:

  1. <?php
  2. class Person
  3. {
  4.     public $sex;
  5.     public $name;
  6.     public $age;
  7.  
  8.     public function __construct($name="",  $age=25, $sex='Male')
  9.     {
  10.         $this->name = $name;
  11.         $this->age  = $age;
  12.         $this->sex  = $sex;
  13.     }
  14.  
  15.     /**
  16.      * @return array
  17.      */
  18.     public function __sleep() {
  19.         echo "It is called when the serialize() method is called outside the class.<br>";
  20.         $this->name = base64_encode($this->name);
  21.         return array('name', 'age'); // It must return a value of which the elements are the name of the properties returned.
  22.     }
  23.  
  24.     /**
  25.      * __wakeup
  26.      */
  27.     public function __wakeup() {
  28.         echo "It is called when the unserialize() method is called outside the class.<br>";
  29.         $this->name = 2;
  30.         $this->sex = 'Male';
  31.         // There is no need to return an array here.
  32.     }
  33. }
  34.  
  35. $person = new Person('John'); // Initially assigned.
  36. var_dump(serialize($person));
  37. var_dump(unserialize(serialize($person)));

代码运行结果如下:

  1. It is called when the serialize() method is called outside the class.
  2. string(58) "O:6:"Person":2:{s:4:"name";s:8:"5bCP5piO";s:3:"age";i:25;}"
  3. It is called when the unserialize() method is called outside the class.
  4. object(Person)#2 (3) { ["sex"]=> string(3) "Male" ["name"]=> int(2) ["age"]=> int(25) }


11. __toString()

使用echo方法直接打印对象时,将调用__toString()方法。

注意:此方法必须返回一个字符串,否则将在E_RECOVERABLE_ERROR级别上引发致命错误。而且您也不能在__toString()方法中抛出异常。

下面是相关的实例:

  1. <?php
  2. class Person
  3. {
  4.     public $sex;
  5.     public $name;
  6.     public $age;
  7.  
  8.     public function __construct($name="",  $age=25, $sex='Male')
  9.     {
  10.         $this->name = $name;
  11.         $this->age  = $age;
  12.         $this->sex  = $sex;
  13.     }
  14.  
  15.     public function __toString()
  16.     {
  17.         return  'go go go';
  18.     }
  19. }
  20.  
  21. $person = new Person('John'); // Initially assigned.
  22. echo $person;

运行代码结果如下:

  1. go go go

那么,如果在类中未定义__toString()方法怎么办?让我们尝试一下。

  1. <?php
  2. class Person
  3. {
  4.     public $sex;
  5.     public $name;
  6.     public $age;
  7.  
  8.     public function __construct($name="",  $age=25, $sex='Male')
  9.     {
  10.         $this->name = $name;
  11.         $this->age  = $age;
  12.         $this->sex  = $sex;
  13.     }
  14.  
  15. }
  16.  
  17. $person = new Person('John'); // Initially assigned.
  18. echo $person;

运行代码结果如下:

  1. Catchable fatal error: Object of class Person could not be converted to string in D:\phpStudy\WWW\test\index.php on line 18

显然,它在页面上报告了一个致命错误,PHP语法不支持这样的写法。


12. __invoke()

当您尝试以调用函数的方式调用对象时,__ invoke()方法将被自动调用。

注意:此功能仅在PHP 5.3.0及更高版本中有效。

下面是相关实例:

  1. <?php
  2. class Person
  3. {
  4.     public $sex;
  5.     public $name;
  6.     public $age;
  7.  
  8.     public function __construct($name="",  $age=25, $sex='Male')
  9.     {
  10.         $this->name = $name;
  11.         $this->age  = $age;
  12.         $this->sex  = $sex;
  13.     }
  14.  
  15.     public function __invoke() {
  16.         echo 'This is an object';
  17.     }
  18.  
  19. }
  20.  
  21. $person = new Person('John'); // Initially assigned.
  22. $person();

运行代码结果如下:

  1. This is an object

如果坚持使用对象作为方法(但未定义__invoke()方法),则将得到以下结果:

  1. Fatal error: Function name must be a string in D:\phpStudy\WWW\test\index.php on line 18


13.__set_state()

从PHP 5.1.0开始,在调用var_export()导出类代码时会自动调用__set_state()方法。

__set_state()方法的参数是一个包含所有属性值的数组,其格式为array('property'=> value,...)

在以下示例中,我们没有定义__set_state()方法:

  1. <?php
  2. class Person
  3. {
  4.     public $sex;
  5.     public $name;
  6.     public $age;
  7.  
  8.     public function __construct($name="",  $age=25, $sex='Male')
  9.     {
  10.         $this->name = $name;
  11.         $this->age  = $age;
  12.         $this->sex  = $sex;
  13.     }
  14.  
  15. }
  16.  
  17. $person = new Person('John'); // Initially assigned.
  18. var_export($person);

执行代码结果如下:

  1. Person::__set_state(array( 'sex' => 'Male', 'name' => 'John', 'age' => 25, ))

显然,对象的属性已打印。

现在让我们看看定义__set_state()方法的另一种情况:

  1. <?php
  2. class Person
  3. {
  4.     public $sex;
  5.     public $name;
  6.     public $age;
  7.  
  8.     public function __construct($name="",  $age=25, $sex='Male')
  9.     {
  10.         $this->name = $name;
  11.         $this->age  = $age;
  12.         $this->sex  = $sex;
  13.     }
  14.  
  15.     public static function __set_state($an_array)
  16.     {
  17.         $a = new Person();
  18.         $a->name = $an_array['name'];
  19.         return $a;
  20.     }
  21.  
  22. }
  23.  
  24. $person = new Person('John'); // Initially assigned.
  25. $person->name = 'Jams';
  26. var_export($person);

执行代码结果如下:

  1. Person::__set_state(array( 'sex' => 'Male', 'name' => 'Jams', 'age' => 25, ))


14. __clone()

在PHP中,我们可以使用clone关键字通过以下语法克隆对象:

  1. $copy_of_object = clone $object;

但是,使用clone关键字只是一个浅拷贝,因为所有引用的属性仍将指向原始变量。

如果在对象中定义了clone()方法,则将在复制生成的对象中调用clone()方法,该方法可用于修改属性的值(如有必要)。

下面是相关的示例:

  1. <?php
  2. class Person
  3. {
  4.     public $sex;
  5.     public $name;
  6.     public $age;
  7.  
  8.     public function __construct($name="",  $age=25, $sex='Male')
  9.     {
  10.         $this->name = $name;
  11.         $this->age  = $age;
  12.         $this->sex  = $sex;
  13.     }
  14.  
  15.     public function __clone()
  16.     {
  17.         echo __METHOD__."your are cloning the object.<br>";
  18.     }
  19.  
  20. }
  21.  
  22. $person = new Person('John'); // Initially assigned.
  23. $person2 = clone $person;
  24.  
  25. var_dump('persion1:');
  26. var_dump($person);
  27. echo '<br>';
  28. var_dump('persion2:');
  29. var_dump($person2);

运行代码结果如下:

  1. Person::__clone your are cloning the object.
  2. string(9) "persion1:" object(Person)#1 (3) { ["sex"]=> string(3) "Male" ["name"]=> string(6) "John" ["age"]=> int(25) }
  3. string(9) "persion2:" object(Person)#2 (3) { ["sex"]=> string(3) "Male" ["name"]=> string(6) "John" ["age"]=> int(25) }


15.__autoload()

__autoload()方法可以尝试加载未定义的类。

过去,如果要在程序文件中创建100个对象,则必须使用include()或require()来包含100个类文件,或者必须在同一类文件中定义100个类。 例如以下:

  1. /**
  2.  * file non_autoload.php
  3.  */
  4.  
  5. require_once('project/class/A.php');
  6. require_once('project/class/B.php');
  7. require_once('project/class/C.php');
  8. .
  9. .
  10. .
  11.  
  12. if (ConditionA) {
  13.     $a = new A();
  14.     $b = new B();
  15.     $c = new C();
  16.     // …
  17. } else if (ConditionB) {
  18.     $a = newA();
  19.     $b = new B();
  20.     // …
  21. }

那么,如果我们使用__autoload()方法呢?

  1. /**
  2.  * file autoload_demo.php
  3.  */
  4. function  __autoload($className) {
  5.     $filePath = project/class/{$className}.php”;
  6.     if (is_readable($filePath)) {
  7.         require($filePath);
  8.     }
  9. }
  10.  
  11. if (ConditionA) {
  12.     $a = new A();
  13.     $b = new B();
  14.     $c = new C();
  15.     // …
  16. } else if (ConditionB) {
  17.     $a = newA();
  18.     $b = new B();
  19.     // …
  20. }

当PHP引擎第一次使用类A时,如果未找到类A,则autoload方法将被自动调用,并且类名称“ A”将作为参数传递。因此,我们在autoload()方法中需要做的是根据类名找到相应的类文件,然后将其包含在内。如果找不到该文件,则php引擎将抛出异常。


16. __debugInfo()

当执行 var_dump() 方法时,__debugInfo() 方法会被自动调用。如果 __debugInfo() 方法未被定义,那么 var_dump 方法或打印出这个对象的所有属性。

举例说明:

  1. <?php
  2. class C {
  3.     private $prop;
  4.  
  5.     public function __construct($val) {
  6.         $this->prop = $val;
  7.     }
  8.  
  9.     /**
  10.      * @return array
  11.      */
  12.     public function __debugInfo() {
  13.         return [
  14.             'propSquared' => $this->prop ** 2,
  15.         ];
  16.     }
  17. }
  18.  
  19. var_dump(new C(42));

执行结果:

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

注意:__debugInfo() 方法应该在 PHP 5.6.0 及以上版本中使用。


总结

以上就是我所了解的 PHP 魔术方法,其中常用的包括 __set() 还有 __get() 和 __autoload()。如果你还有其他疑问,可以从 PHP 官方网站获得更多帮助。


原文地址:https://www.tutorialdocs.com/article/16-php-magic-methods.html

译文地址:https://learnku.com/php/t/40919


本文网址:https://www.zztuku.com/detail-12034.html
站长图库 - 带你吃透16个PHP魔术方法
申明:如有侵犯,请 联系我们 删除。

评论(0)条

您还没有登录,请 登录 后发表评论!

提示:请勿发布广告垃圾评论,否则封号处理!!

    编辑推荐