HP是最好的编程语言。对于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. __construct()

PHP构造方法是对象创建之后自动调用的第一个方法。任何类都有构造方法。如果没有显式定义,那么类会有个默认的构造方法,该方法没有参数,方法体为空。

1) 构造方法的用法

构造函数通常用来执行初始化工作,如在创建对象时设置成员变量的初始值。

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

function __constrct([参数列表]){
   方法体 // 通常用于设置成员变量的初始值
}

注意:同一个类只能有一个构造方法,因为PHP不支持构造方法重载。

完整的示例如下:

<?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;
       }  
   }

不使用任何参数创建对象$Person1。

$Person1 = new Person();
echo $Person1->say(); //输出: Name:,Sex:Male,Age:22

使用参数"James"创建对象$Person2。

$Person2 = new Person("Jams");
echo $Person2->say(); // 输出: Name: Jams, Sex: Male, Age: 22

使用三个参数创建$Person3。

$Person3 = new Person ("Jack", "Male", 25);
echo $Person3->say(); // 输出: Name: Jack, Sex: Male, Age: 25

2. __destruct()

现在我们知道了构造方法,那么相对的就是析构方法。

析构方法可以在对象销毁之前执行一些操作,如关闭文件、清空结果集,等等。

析构方法是PHP5引入的新特性。

析构方法的声明格式与构造方法 __construct() 类似,就是说__destruct()也以双下划线开头,其名称也是固定的。

1) 析构方法的声明格式

function __destruct()
{
   // 方法体
}

注意:析构方法不能带任何参数。

2) 析构方法的用法

一般来说,PHP中析构方法并不是太常用。在类中它是可选的,通常用于在对象销毁之前执行某些清理工作。

下面的例子演示了如何使用析构方法:

<?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("John");
unset($Person); // 销毁上面创建的$Person对象

以上程序的输出结果为:

Well, my name is John

3. __call()

该方法有两个参数。第一个参数$function_name自动接收未定义方法的名称,第二个参数$arguments以数组的方式接收该方法调用的多个参数。

1) __call()方法的用法

function __call(string $function_name, array $arguments)
{
   // 方法体
}

程序中调用未定义的方法时,__call()方法会自动被调用。

示例如下:

<?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("John", "apple");            
$Person->say();

输出结果如下:

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

4. __callStatic()

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

__callStatic()的用法与__call()类似。示例如下:

<?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("John", "apple");
$Person->say();

输出结果如下:

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

5. __get()

当试图访问外部对象的私有属性时,程序会抛出异常并结束执行。但我们可以使用__get()方法来解决这个问题。它能在对象外部取得对象的私有方法。示例如下:

<?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("John", 60);   // 用Person类初始化对象,并通过构造方法给属性赋初始值
echo "Name:" . $Person->name . "<br>";   // 访问私有属性时, __get() 方法会自动被调用,这样就能间接取得属性值
echo "Age:" . $Person->age . "<br>";    // __get() 方法自动被调用,并返回不同的值

输出结果如下:

Name: John
Age: 50

6. __set()

__set($property, $value) 方法用来设置对象的私有属性。当试图设置对象中未定义的属性时,就会触发__set()方法,调用参数为被设置的属性名和属性值。

示例代码如下:

<?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("John", 25); // 注意下面的代码会改变初始值
$Person->name = "Lili";     // "name" 属性成功赋值。如果没有 __set() 方法,程序就会抛出异常
$Person->age = 16; // "age" 属性成功赋值
$Person->age = 160; // 160 是个非法值,所以赋值失败
$Person->say();  // 输出:My name is Lili, I'm 16 years old.

下面是输出结果:

My name is Lili, I'm 16 years old

7. __isset()

在介绍__isset()方法之前,我先介绍下issset()方法。isset()方法主要用于判断某个变量是否被设置。

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

  • 如果参数是公有属性,那么可以利用isset()方法判断属性是否被设置;

  • 如果参数是私有属性,isset()方法将无法使用。

那么,是否有办法判断私有属性被设置呢?当然,只需要在类里定义__isset()方法,就可以在对象外部利用isset()方法判断某个私有属性是否被设置了。

对未定义或没有权限访问的属性调用isset()或empty()时,就会调用__isset()方法。示例如下:

<?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("John", 25); // 赋初始值
echo isset($person->sex),"<br>";
echo isset($person->name),"<br>";
echo isset($person->age),"<br>";

输出结果如下:

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

8. __unset()

与__isset()类似,在未定义或无权限访问的属性上调用unset()方法时会触发__unset()方法。示例如下:

<?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("John", 25); // 赋初始值
unset($person->sex),"<br>";
unset($person->name),"<br>";
unset($person->age),"<br>";

输出结果如下:

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

9. __sleep()

serialize()方法会检查类中是否存在__sleep()魔术方法。如果存在,就会调用该方法来执行序列化操作。

__sleep()方法通常用来在保存数据之前指定哪些属性需要被序列化。如果对象中包含一些完全不需要序列化的巨大对象,__sleep()就能派上用场了。

具体用法请参考以下代码:

<?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('John'); // Initially assigned.
echo serialize($person);
echo '<br/>';

输出结果如下:

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

10. __wakeup()

与__sleep()方法相对的就是__wakeup()方法,常用来反序列化,如重建数据连接,或执行其他初始化操作等。

示例如下:

<?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('John'); // 赋初始值
var_dump(serialize($person));
var_dump(unserialize(serialize($person)));

输出结果如下:

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

11. __toString()

使用echo方法直接输出对象时会调用其__toString()方法。

注意:该方法必须返回字符串,否则会抛出"E_RECOVERABLE_ERROR"级别的异常。在__toString()方法中也不能抛出异常。

示例如下:

<?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('John'); // 赋初始值
echo $person;

返回结果如下:

go go go

如果类中没有定义__toString()会怎样?我们来试试看。

<?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('John'); // 赋初始值
echo $person;

返回结果如下:

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

可见,它会在页面上报告致命错误,说明这种用法不允许。

12. __invoke()

当试图用调用函数的方式调用对象时,就会自动调用其__invoke()方法。

注意:该功能只在PHP 5.3.0以及以上版本上有效。

示例如下:

<?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('John'); // 赋初始值
$person();

输出结果如下:

This is an object

如果在未定义__invoke()方法的情况下将对象作为函数使用,就会得到以下的结果:

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开始,__set_state()方法会在调用var_export()导出类代码时自动被调用。

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

下面的示例中没有定义__set_state()方法:

<?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('John'); // 赋初始值
var_export($person);

输出结果如下:

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

可见,输出结果是对象的属性。

下面来看看如果定义了__set_state()方法会怎样:

<?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('John'); // 赋初始值
$person->name = 'Jams';
var_export($person);

输出结果如下:

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

14. __clone()

PHP中可以使用clone关键字来复制对象,其格式如下:

$copy_of_object = clone $object;

但是,clone关键字只会进行浅复制,所有引用的属性依然会指向原来的变量。

如果对象里定义了__clone()方法,那么复制时就会调用__clone()方法,从而允许我们修改被复制的值(如果需要的话)。

示例如下:

<?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('John'); // 赋初始值
$person2 = clone $person;
var_dump('persion1:');
var_dump($person);
echo '<br>';
var_dump('persion2:');
var_dump($person2);

输出结果如下:

Person::__clone your are cloning the object.
string(9) "persion1:" object(Person)#1 (3) { ["sex"]=> string(3) "Male" ["name"]=> string(6) "John" ["age"]=> int(25) }
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个类,例如:

/**
* file non_autoload.php
*/
require_once('project/class/A.php');
require_once('project/class/B.php');
require_once('project/class/C.php');
.
.
.
if (ConditionA) {
   $a = new A();
   $b = new B();
   $c = new C();
   // …
} else if (ConditionB) {
   $a = newA();
   $b = new B();
   // …
}

那么使用__autoload()方法呢?

/**
* 文件 autoload_demo.php
*/
function  __autoload($className) {
   $filePath = “project/class/{$className}.php”;
   if (is_readable($filePath)) {
       require($filePath);
   }
}
if (ConditionA) {
   $a = new A();
   $b = new B();
   $c = new C();
   // …
} else if (ConditionB) {
   $a = newA();
   $b = new B();
   // …
}

当PHP引擎第一次使用类A时,如果A没有找到,就会调用__autoload方法,参数为类名"A"。然后我们需要在__autoload()方法中根据类名找到相应的类文件并包含该文件。如果文件没有找到,PHP引擎就会抛出异常。

16. __debugInfo()

执行var_dump()方法的时候会调用__debugInfo()方法。如果__debugInfo()没有定义,则var_dump()方法会输出对象中的所有属性。

示例如下:

<?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));

输出结果如下:

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

注意:__debugInfo()方法只能用于PHP 5.6.0及更高版本。

总结

上面介绍了16个PHP魔术方法,其中最常用的有__set()、__get()和__autoload()。如果你还有问题,可以从PHP官方网站上获得帮助。

原文:https://www.tutorialdocs.com/article/16-

PHP 16 个编程法则的更多相关文章

  1. PHP 必知的 16 个编程法则

    以双下划线(__)开头的方法称为魔术方法 -__construct():类的构造方法: -__destruct():类的析构方法: -__call($funName, $arguments):当访问未 ...

  2. Effective C++笔记 55条编程法则

    1.  视C++为一个语言联邦 C++高效编程守则视状况而变化,取决于你使用C++的哪一部分. 2.  尽量以const,enum.inline替代#define 1) 对于单纯常量,最好以const ...

  3. day_6.16网络编程

    单线程服务器select版: select ---->最多1024个 poll ----->解决了套接字上限的问题----->轮询检测 关于 sys模块: 通过fd找套接字 协程: ...

  4. 16 Windows编程——系统内置窗口子类型之edit、ComboBox、ownerbutton、listbox

    edit类型的子窗口 ES_MULTILINE:多行输入文本框 窗口的消息: WL_COMMAND: EN_CHANGE:当edit窗口内的文本内容改变的时候,edit子窗口给父窗口发送一个WL_CO ...

  5. Atitit.软件开发的几大规则,法则,与原则Principle v3

    Atitit.软件开发的几大规则,法则,与原则Principle  v31.1. 修改历史22. 设计模式六大原则22.1. 设计模式六大原则(1):单一职责原则22.2. 设计模式六大原则(2):里 ...

  6. java设计原则:16种原则

    一   类的设计原则   1 依赖倒置原则-Dependency Inversion Principle (DIP) 2 里氏替换原则-Liskov Substitution Principle (L ...

  7. Java面向对象16种原则

    一   类的设计原则   1 依赖倒置原则-Dependency Inversion Principle (DIP) 2 里氏替换原则-Liskov Substitution Principle (L ...

  8. 华为C语言编程规范

    DKBA华为技术有限公司内部技术规范DKBA 2826-2011.5C语言编程规范2011年5月9日发布 2011年5月9日实施华为技术有限公司Huawei Technologies Co., Ltd ...

  9. Cocos2d-x 3.0final手机游戏开发视频教程2014 - 自学编程 -(陆续更新中)

    内容: 非常多人问我:沈老师,要不要更新引擎版本号到3.0,更新这么快,以后会不会每一个月都有一次,好怕呀. 我说:无论你曾经是哪个版本号,3.0final是一个架构级别的升级,能够在新项目中果断升级 ...

随机推荐

  1. spark学习13(spark RDD)

    RDD及其特点 1)RDD(Resillient Distributed Dataset)弹性分布式数据集,是spark提供的核心抽象.它代表一个不可变.可分区.里面的元素可并行计算的集合 2)RDD ...

  2. VMware Workstation 12 增加磁盘容量 Windows Server 2012 系统

    1.安装虚拟机后,检查C盘容量大小,发现C盘现在的空间是59.9GB,如下图: 2.使用window+R键,出现运行窗口,输入‘cmd’——>‘cd C:\Program Files (x86) ...

  3. on绑定阻止冒泡失败

    使用zepto库,有如下dom <div id="J_parent"> <a href="#"> <span>点我有惊喜&l ...

  4. postman(谷歌) httprequester(火狐)

    http://www.cnblogs.com/s380774061/p/4624326.html @an http://www.tuicool.com/articles/67Rnaej 测试文档券栈 ...

  5. 移动端给img元素添加content: "";

    误给img原始添加 content: "";属性后发现在ios系统中图片是不会显示的android系统是正常的

  6. LeetCode第[19]题(Java):Remove Nth Node From End of List(删除链表的倒数第N个节点)

    题目:删除链表的倒数第N个节点 难度:Medium 题目内容: Given a linked list, remove the n-th node from the end of list and r ...

  7. socket 关于同一条TCP链接数据包到达顺序的问题

    转:http://blog.csdn.net/l1008610/article/details/52197602 以前作者也一直以为数据包先发的不一定先到,直到今天才意识这个问题的缺陷,数据包是不一定 ...

  8. Python 文本相似度分析

    环境 Anaconda3 Python 3.6, Window 64bit 目的 利用 jieba 进行分词,关键词提取 利用gensim下面的corpora,models,similarities ...

  9. Dib to Bitmap doesn't work in WPF

    一.Dib to Bitmap doesn't work in WPF 代码如下: protected byte[] BitmapFromDIB(IntPtr pDIB, IntPtr pPix) { ...

  10. Hadoop2.9下运行JAR包时System.out.println的输出日志

    根据博文——Hadoop日志存放路径详解中所述,Container日志包含ApplicationMaster日志和普通Task日志(关于其他类型的日志的详细说明请参考该博文,本文不再赘述) 所以可知, ...