美文网首页
PHP魔术方法使用

PHP魔术方法使用

作者: 爱折腾的傻小子 | 来源:发表于2020-12-22 10:22 被阅读0次

    支持魔术方法

    class MyClass
    {
        public function __construct() {}
    
        public function __destruct() {}
    
        public function __call() {}
    
        public function __callStatic() {}
    
        public function __get() {}
    
        public function __set() {}
    
        public function __isset() {}
    
        public function __unset() {}
    
        public function __sleep() {}
    
        public function __wakeup() {}
    
        public function __toString() {}
    
        public function __invoke() {}
    
        public function __set_state() {}
    
        public function __clone() {}
    
        public function __debuginfo() {}
    }
    

    __construct 构造方法

    • 当一个对象被实例化的时候会被首先调用
    • 在PHP框架种依赖注入以及中间件一般就是这种方法完成的
    • 类的构造方法是可以被子类继承和重写的
    <?php
    class A {
        // 构造方法
        public function __construct() {
            echo "This is A construct\n";
        }
    }
    
    class B extends A{
        // 调用父类构造方法,再调用自己的构造方法
        public function __construct() {
            parent::__construct();
            echo "This is B construct\n";
        }
    }
    
    class C extends A{
        // 重写构造方法,之调用自己的构造方法
        public function __construct() {
            echo "This is C construct";
        }
    }
    
    new A();  // This is C construct
    
    new B();  // This is A construct\n This is B construct\n 
    
    new C();  // This is C construct
    

    __destruct 析构方法

    • 在销毁一个类之前执行的一些操作或完成一些功能,比如说关闭文件、释放结果集等
    • 析构方法不能带有任何参数
    <?php
    class Person{
      public $name;        
      public $age;        
      public $sex;
    
      // 构造方法
      public function __construct($name = "", $sex = "男", $age = 22)
      {
        $this->name = $name;
        $this->sex = $sex;
        $this->age = $age;
      }
    
      // 成员方法
      public function say() 
      {
        echo "xxxxxxxxxxxxxxxxxxxx";
      }
    
      // 析构方法
      public function __destruct()
      {
        echo "我正在被注销,需要关闭些什么吗?";
      }
    }
    
    // 测试一
    $person = new Person("小明");
    $person->say();  // xxxxxxxxxxxxxxxxxxxx
    unset($person);  // 我正在被注销,需要关闭些什么吗?
    echo 123;        // 123
    
    // 测试二
    $person1 = new Person("小明");
    $person1->say();   // xxxxxxxxxxxxxxxxxxxx
    $person1 = null;   // 我正在被注销,需要关闭些什么吗?
    echo 123;          // 123
    

    __call 调用一个不可访问的方法时被调用。

    • 不可访问的方法:方法不存在或方法是私有的
    • 格式
    // 参数 $function_name 会自动接收不存在的方法名
    // 参数 $arguments 则以数组的方式接收不存在方法的多个参数
    public function __call(string $function_name, array $arguments)
    {
        // 方法体
    }
    
    • 避免当调用的方法不存在时产生错误,而意外的导致程序中止,可以使用 __call() 方法来避免
    • 该方法在调用的方法不存在时会自动调用,程序仍会继续执行下去
    <?php
    class Person
    {                            
      public function say()
      { 
         echo "Hello, world!";
      }            
    
      protected function isRed(){
         echo "yes is Red\n";
      }
    
      /**
       * 声明此方法用来处理调用对象中不存在的方法
       */
      public function __call($funName, $arguments)
      {
         echo "你所调用的函数:" . $funName . "(参数:" ;  // 输出调用不存在的方法名
         print_r($arguments); // 输出调用不存在的方法时的参数列表
         echo ")不存在!\n"; // 结束换行                     
       }                                         
    }
    
    $Person = new Person();           
    $Person->run("teacher"); 
    /*
    你所调用的函数:run(参数:Array
    (
        [0] => teacher
    )
    )不存在!
    */
    $Person->eat("小明", "苹果");            
    /*
    你所调用的函数:eat(参数:Array
    (
        [0] => 小明
        [1] => 苹果
    )
    )不存在!
    */
    $Person->say();  // Hello, world!
    $Person->isRed();  // 改方法是存在但是是私有的
    /*
    你所调用的函数:isRed(参数:Array
    (
    )
    )不存在!
    */
    

    __callStatic 静态调用一个不可访问的方法时被调用

    • 改方法的使用和__call基本一致,唯一区别就是调用形式不同
    <?php
    
    class Person
    {
      // 成员方法
      public function say()
      {
        echo "xoxoxoxoxooxox";
      }  
    
      // 私有静态方法 
      private static function s()
      {
        echo "1s1";
      }
    
      // __callStatic 魔术方法
      public static function __callStatic($funName, $arg)
      {
        echo "你所调用的静态方法:" . $funName . "(参数:" ;  // 输出调用不存在的方法名
        print_r($arguments); // 输出调用不存在的方法时的参数列表
        echo ")不存在!\n"; // 结束换行
      }
    }
    
    $Person = new Person();  
    $Person::run("teacher");     // 你所调用的静态方法:run(参数:)不存在!
    Person::run('weee');         // 你所调用的静态方法:run(参数:)不存在!
    $Person::eat("小明", "苹果"); // 你所调用的静态方法:eat(参数:)不存在!
    $Person::s();                // 你所调用的静态方法:s(参数:)不存在!
    Person::s();                 // 你所调用的静态方法:s(参数:)不存在!
    $Person->say();              // xoxoxoxoxooxox
    

    __get 获得一个类的成员变量时调用

    • 类的成员属性被设定为 private 后,如果我们试图在外面调用它则会出现“不能访问某个私有属性”的错误
    • 在程序运行过程中,通过它可以在对象的外部获取私有成员属性的值
    <?php
    
    class Person
    {
      private $name;
      private $age;
    
      public function __construct($name="", $age=1)
      {
          $this->name = $name;
          $this->age = $age;
      }
    
      // 在直接获取属性值时自动调用一次,以属性名作为参数传入并处理
      // $propertyName 不存在的属性名称
      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("小明", 60);   
    echo "姓名:" . $Person->name . "";   // 姓名:小明
    echo "年龄:" . $Person->age . "";    // 年龄:50
    

    __set 设置一个类的成员变量时调用

    • __set( $property, $value ) 用来设置私有属性, 给一个未定义的属性赋值时,此方法会被触发
    • 传递的参数是被设置的属性名和值
    <?php
    
    class Person
    {
        private $name;
        private $age;
    
        public function __construct($name="",  $age=25)
        {
            $this->name = $name;
            $this->age  = $age;
        }
    
        // 模式方法 __set
        // $property 被设置的属性 
        // $value 设置的值
        public function __set($property, $value) 
        {
          if ($property=="age")
            {
                if ($value > 150 || $value < 0) {
                    return;
                }
            }
            $this->$property = $value;
        }
    
        // 成员方法
        public function say()
        {
          echo "我叫".$this->name.",今年".$this->age."岁了";
        }
    }
    
    $Person=new Person("小明", 25);
    $Person->say();     // 我叫小明,今年25岁了
    $Person->name = "小红";
    $Person->age = 16;
    $Person->age = 160; 
    $Person->say();     // 我叫小红,今年16岁了
    

    __isset 当对不可访问属性调用isset()或empty()时调用

    • isset()函数使用规则:
      • 传入一个变量作为参数,如果传入的变量存在则传回true,否则传回false
    • 那么如果在一个对象外面使用isset()这个函数去测定对象里面的成员是否被设定可不可以用它呢?
      • 如果对象里面成员是公有的,我们就可以使用这个函数来测定成员属性
      • 如果是私有的成员属性,这个函数就不起作用了,原因就是因为私有的被封装了,在外部不可见(可以使用__isset魔术方法)
    • 当在类外部使用isset()函数来测定对象里面的私有成员是否被设定时,就会自动调用类里面的__isset()方法了帮我们完成这样的操作
    • 当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用
    <?php
    
    class Person
    {
        public $sex;
        private $name;
        private $age;
    
        public function __construct($name="",  $age=25, $sex='男')
        {
            $this->name = $name;
            $this->age  = $age;
            $this->sex  = $sex;
        }
    
        // $content 
        public function __isset($content) 
        {
          echo "当在类外部使用isset()函数测定私有成员{$content}时,自动调用\n";
          echo  isset($this->$content)."\n";
        }
    
        // 成员方法
        public function say()
        {
          echo "我叫".$this->name.",今年".$this->age."岁了";
        }
    }
    
    $person = new Person("小明", 25); // 初始赋值
    
    echo isset($person->sex),"\n";    // 1
    
    echo isset($person->name),"\n"; // 这里打印的是空串 魔术方法没有返回值的原因
    /*
    当在类外部使用isset()函数测定私有成员name时,自动调用
    1
    
    */
    echo isset($person->age),"\n";// 这里打印的是空串 魔术方法没有返回值的原因
    /*
    当在类外部使用isset()函数测定私有成员age时,自动调用
    1
    
    */
    

    __unset 当对不可访问属性调用unset()时被调用

    • unset()这个函数的作用是删除指定的变量且传回true,参数为要删除的变量
    • 那么如果在一个对象外部去删除对象内部的成员属性用unset()函数可以吗?
      • 如果一个对象里面的成员属性是公有的,就可以使用这个函数在对象外面删除对象的公有属性
      • 如果对象的成员属性是私有的,我使用这个函数就没有权限去删除。
    class Person
    {
        public $sex;
        private $name;
        private $age;
    
        public function __construct($name="",  $age=25, $sex='男')
        {
            $this->name = $name;
            $this->age  = $age;
            $this->sex  = $sex;
        }
    
        // $content 
        public function __unset($content) 
        {
          echo "当在类外部使用unset()函数来删除私有成员时自动调用的\n";
          echo  isset($this->$content);
        }
    
        // 成员方法
        public function say()
        {
          echo "我叫".$this->name.",今年".$this->age."岁了";
        }
    }
    
    $person = new Person("小明", 25); // 初始赋值
    unset($person->sex);
    unset($person->name);   
    /*
    当在类外部使用unset()函数来删除私有成员时自动调用的
    1
    */
    unset($person->age);
    /*
    当在类外部使用unset()函数来删除私有成员时自动调用的
    1
    */
    

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

    • serialize() 函数会检查类中是否存在一个魔术方法 __sleep()
    • 如果存在,则该方法会优先被调用,然后才执行序列化操作
    • 此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组
    • 如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误
    • __sleep() 不能返回父类的私有成员的名字。这样做会产生一个 E_NOTICE 级别的错误。可以用 Serializable 接口来替代。
    • __sleep() 方法常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。
    <?php
    
    class Person
    {
        public $sex;
        public $name;
        public $age;
    
        public function __construct($name="",  $age=25, $sex='男')
        {
            $this->name = $name;
            $this->age  = $age;
            $this->sex  = $sex;
        }
    
        // $content 
        public function __sleep() 
        {
          echo "当在类外部使用serialize()时会调用这里的__sleep()方法\n";
          $this->name = base64_encode($this->name);
          return array('name', 'age'); // 这里必须返回一个数值,里边的元素表示返回的属性名称
        }
    
        // 成员方法
        public function say()
        {
          echo "我叫".$this->name.",今年".$this->age."岁了";
        }
    }
    
    $person = new Person('小明'); // 初始赋值
    echo serialize($person);
    /*
    当在类外部使用serialize()时会调用这里的__sleep()方法
    O:6:"Person":2:{s:4:"name";s:8:"5bCP5piO";s:3:"age";i:25;}
    */
    

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

    • 如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。
    • __wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
    <?php
    
    class Person
    {
        public $sex;
        public $name;
        public $age;
    
        public function __construct($name="",  $age=25, $sex='男')
        {
            $this->name = $name;
            $this->age  = $age;
            $this->sex  = $sex;
        }
    
        // $content 
        public function __sleep() 
        {
          echo "当在类外部使用serialize()时会调用这里的__sleep()方法\n";
          $this->name = base64_encode($this->name);
          return array('name', 'age'); // 这里必须返回一个数值,里边的元素表示返回的属性名称
        }
        
        public function __wakeup() {
    
            echo "当在类外部使用unserialize()时会调用这里的__wakeup()方法";
    
            $this->name = 2;
    
            $this->sex = '男';
    
            // 这里不需要返回数组
        }
    
        // 成员方法
        public function say()
        {
          echo "我叫".$this->name.",今年".$this->age."岁了";
        }
    }
    
    $person = new Person('小明'); // 初始赋值
    var_dump(serialize($person));
    /*
    当在类外部使用serialize()时会调用这里的__sleep()方法
    string(58) "O:6:"Person":2:{s:4:"name";s:8:"5bCP5piO";s:3:"age";i:25;}"
    */
    var_dump(unserialize(serialize($person)));
    /*
    当在类外部使用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() 方法中抛出异常。这么做会导致致命错误。
    <?php
    
    class Person
    {
        public $sex;
        public $name;
        public $age;
    
        public function __construct($name="",  $age=25, $sex='男')
        {
            $this->name = $name;
            $this->age  = $age;
            $this->sex  = $sex;
        }
    
        public function __toString()
        {
            return '这里必须返回一个字符串';
        }
    }
    
    $person = new Person('小明'); // 初始赋值
    
    echo $person;   // 这里必须返回一个字符串
    
    <?php
    
    class Person
    {
        public $sex;
        public $name;
        public $age;
    
        public function __construct($name="",  $age=25, $sex='男')
        {
            $this->name = $name;
            $this->age  = $age;
            $this->sex  = $sex;
        }
    }
    
    $person = new Person('小明'); // 初始赋值
    
    echo $person;   // 这里必须返回一个字符串
    /*
    Catchable fatal error: Object of class Person could not be converted to string in /usercode/file.php on line 19
    */
    

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

    • 当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
    <?php
    
    class Person
    {
        public $sex;
        public $name;
        public $age;
    
        public function __construct($name="",  $age=25, $sex='男')
        {
            $this->name = $name;
            $this->age  = $age;
            $this->sex  = $sex;
        }
        
        public function __invoke() 
        {
            echo '这可是一个对象哦';
        }
    }
    
    $person = new Person('小明'); // 初始赋值
    $person();   // 这可是一个对象哦
    
    <?php
    
    class Person
    {
        public $sex;
        public $name;
        public $age;
    
        public function __construct($name="",  $age=25, $sex='男')
        {
            $this->name = $name;
            $this->age  = $age;
            $this->sex  = $sex;
        }  
    }
    
    $person = new Person('小明'); // 初始赋值
    $person();
    /*
    Fatal error: Uncaught Error: Function name must be a string in /usercode/file.php:18
    Stack trace:
    #0 {main}
      thrown in /usercode/file.php on line 18
    */
    

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

    • 当调用 var_export() 导出类时,此静态方法会被自动调用。
    • 本方法的唯一参数是一个数组,其中包含按 array('property' => value, ...) 格式排列的类属性。
    <?php
    
    class Person
    {
        public $sex;
        public $name;
        public $age;
    
        public function __construct($name="",  $age=25, $sex='男')
        {
            $this->name = $name;
            $this->age  = $age;
            $this->sex  = $sex;
        }
    }
    
    $person = new Person('小明'); // 初始赋值
    var_export($person);
    /*
    Person::__set_state(array(
       'sex' => '男',
       'name' => '小明',
       'age' => 25,
    ))
    */
    
    <?php
    
    class Person
    {
        public $sex;
        public $name;
        public $age;
    
        public function __construct($name="",  $age=25, $sex='男')
        {
            $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('小明'); // 初始赋值
    $person->name = '小红';
    var_export($person);
    /*
    Person::__set_state(array(
       'sex' => '男',
       'name' => '小红',
       'age' => 25,
    ))
    */
    

    __clone

    • 使用关键字 clone 来克隆对象
    • 如果想在克隆后改变原对象的内容,需要在类中添加一个特殊的 __clone() 方法来重写原本的属性和方法
    • __clone() 方法只会在对象被克隆的时候自动调用
    <?php
    
    class Person
    {
        private $sex;
        private $name;
        private $age;
    
        // 构造方法
        public function __construct($name="",  $age=25, $sex='男')
        {
            $this->name = $name;
            $this->age  = $age;
            $this->sex  = $sex;
        }
        
        // 成员方法
        public function say()
        {
            echo "我的名字叫:".$this->name;
            echo " 我的年龄是:".$this->age."<br />";
        }
        
        public function __clone()
        {
            $this->name = "我是假的".$this->name;
            $this->age = 30;
        }
    }
    
    $p1 = new Person("张三", 20);
    $p1->say();         // 我的名字叫:张三 我的年龄是:20
    $p2 = clone $p1;
    $p2->say();         // 我的名字叫:我是假的张三 我的年龄是:30
    
    • 单例类的加强:禁止克隆
      • 在PHP中,为防止对单例类对象的克隆来打破单例类的上述实现形式,通常还为其提供一个空的私有 (private修饰的)__clone()方法。
    <?php
    
    class SingetonBasic
    {
        // 静态变量要私有化,防止类外修改,保存对象实例
        private static $instance;
        
        // 构造函数私有化,类外不能直接新建对象
        private function __construct(){}
        
        // 禁止使用关键字clone
        private function __clone(){}
        
        // 外部开放方法创建对象
        public static function getInstance() 
        {
            if (! self::$instance instanceof self) {
                self::$instance = new self();
            }
            
            return self::$instance;
        }
    }
    
    $a = SingetonBasic::getInstance();
    $b = SingetonBasic::getInstance();
    var_dump($a === $b);  //结果为:boolean true   a和b指向的是同一个对象
    
    $c = clone $a;  // 报错
    /*
    Fatal error: Uncaught Error: Call to private SingetonBasic::__clone() from context '' in /usercode/file.php:29 Stack trace: #0 {main} thrown in /usercode/file.php on line 29
    */
    
    • 浅克隆:只是克隆对象中的非对象非资源数据,即对象中属性存储的是对象类型,则会出现克隆不完全
    <?php
    
    class B
    {
        public $val = 10;
    }
    
    class A
    {
        public $val = 20;
        public $b;
        
        public function __construct()
        {
            $this->b = new B();
        }
    }
    
    $oa = new A();
    $ob = clone $oa;
    $oa->val = 30;
    $oa->b->val = 40;
    echo "<pre>";
    var_dump($oa);
    /*
    object(A)#1 (2) {
      ["val"]=>
      int(30)
      ["b"]=>
      object(B)#2 (1) {
        ["val"]=>
        int(40)
      }
    }
    */
    var_dump($ob);
    /*
    object(A)#3 (2) {
      ["val"]=>
      int(20)
      ["b"]=>
      object(B)#2 (1) {
        ["val"]=>
        int(40)          // 这里本应该是10 结果出现40导致克隆不完全 $this->b 还用引用 A对象
      }
    }
    */
    
    • 深克隆:一个对象的所有属性数据都彻底的复制,需要使用魔术方法__clone(),并在里面实现深度克隆
    <?php
    
    class B
    {
        public $val = 10;
    }
    
    class A
    {
        public $val = 20;
        public $b;
        
        public function __construct()
        {
            $this->b = new B();
        }
        
        public function __clone()
        {
            $this->b = clone $this->b;
        }
    }
    
    $oa = new A();
    $ob = clone $oa;
    $oa->val = 30;
    $oa->b->val = 40;
    echo "<pre>";
    var_dump($oa);
    /*
    object(A)#1 (2) {
      ["val"]=>
      int(30)
      ["b"]=>
      object(B)#2 (1) {
        ["val"]=>
        int(40)
      }
    }
    */
    var_dump($ob);
    /*
    object(A)#3 (2) {
      ["val"]=>
      int(20)
      ["b"]=>
      object(B)#4 (1) {
        ["val"]=>
        int(10)
      }
    }
    */
    

    __debuginfo 打印所需调试信息

    • 该方法在PHP 5.6.0及其以上版本才可以用,如果你发现使用无效或者报错,请查看你的版本
    
    class C 
    {
        private $prop;
        
        public function __construct($val)
        {
            $this->prop = $val;
        }
        
        public function __debugInfo()
        {
            return [
                'propSquared' => $this->prop ** 2,
            ];
        }
    }
    
    var_dump(new C(42));
    /*
    object(C)#1 (1) { ["propSquared"]=> int(1764) }
    */
    

    相关文章

      网友评论

          本文标题:PHP魔术方法使用

          本文链接:https://www.haomeiwen.com/subject/pflrnktx.html