PHP基礎教程十三之反射、物件序列化

黄舟
發布: 2023-03-06 09:04:02
原創
1468 人瀏覽過

本節講解的內容

  • #物件的複製

  • 物件的遍歷

  • 物件的序列化與反序列化

  • #內建標準類別的使用

  • traits的使用

  • 類別和物件的相關函數

  • #PHP反射機制

##前言

PHP的物件導向是一個重要的知識點,它的想法貫穿我們所發展的整個流程。在物件導向中還有一些知識點是需要我們去了解的,物件克隆的特徵以及物件的遍歷,物件的序列化和反序列化,如果你想寫一個PHP的框架,那麼你對PHP的反射也是要掌握的。

物件的複製

當我們建立一個物件後,就會在記憶體中分配一個空間,物件名稱指向這個空間,前面我們講過物件的賦值,當一個物件把裡面的資料修改了,另一個物件的資料也會跟著變化,因為賦值是相當於把物件識別碼賦值了一份,而克隆並不是這樣的。

<?php

class Person{
    public $name;
    public $age;
    public $sex;
    public function __construct($name,$age,$sex){
        $this -> name = $name;
        $this -> age = $age;
        $this -> sex = $sex;
    }

    public function getName(){
        return $this -> name;
    }

    public function getAge(){
        return $this -> age;
    }

    public function getSex(){
        return $this -> sex;
    }
}

$a = new Person(&#39;宋江&#39;,&#39;45&#39;,&#39;男&#39;);
echo &#39;<pre class="brush:php;toolbar:false">&#39;;

$b = clone $a;
$b -> name = &#39;武松&#39;;
var_dump($a);
var_dump($b);
登入後複製

結果

物件複製的語法:
$新对象 = clone $就对象;
登入後複製

從上面的結果可以看出來,一個物件的資料變化,並不會影響到另外一個物件的資料。物件克隆會產生一個全新的物件。可以理解如下:


在PHP的魔術方法中有一個魔術方法和PHP的克隆有關的__clone(),在複製完成時,如果在類別裡面定義了魔術方法__clone()方法,則新建立也就是複製產生的物件會呼叫這個__clone()方法,在這個方法中如果有需要可以對屬性做一些變動。

如果不想一個物件進行克隆可以在內部把魔術方法__clone()方法定義成私有的,這時如果在進行克隆,會提示
Call to private Peoson::__clone() from context &#39;&#39;
登入後複製

物件的遍歷

在PHP中物件也是可以遍歷的,物件的遍歷可以理解成把某個物件的屬性和值,遍歷顯示出來。遍歷使用到foreach這個迴圈體。基本語法:

foreach(对象名 as $key => $val){
    //$key是属性名,$val是属性值
}
登入後複製

<?php

    class Person{
    public $name;
    public $age;
    private $sex;
    public function __construct($name,$age,$sex){
        $this -> name = $name;
        $this -> age = $age;
        $this -> sex = $sex;
    }
}

$person = new Person(&#39;孙悟空&#39;,256,&#39;男&#39;);

foreach ($person as $key => $value) {
    echo $key . &#39; = &#39; . $value . &#39;<br>&#39;;
}
.......结果........
name = 孙悟空
age = 256
登入後複製

物件的遍歷只能把屬性的修飾符是public的遍歷出來,protected和private不能再類外訪問,所以在類別外進行物件的遍歷,用這兩個修飾符修飾的屬性是取不出來的,就像上面程式碼的sex屬性一樣。

物件的序列化和反序列化


在PHP中當程式執行完一個文件,就會自動的釋放內存,我們在文件中創建的對象,變數等,都會消失,如果我們在一個文件創建了一個對象,在另外一個對像中想要使用這個對象,就可以使用到對象的序列化(serialize())和反序列化(unserialize()) 。

對象的序列化和反序列化可以理解成把對象轉換成字串保存在一個文件中,在用到對象時把文件進行反序列化得到對象,對像是不能直接保存在文件中的。

示意圖:

a.php文件,建立一個物件並儲存:#​​##

<?php

    class Person{
    public $name;
    public $age;
    private $sex;
    public function __construct($name,$age,$sex){
        $this -> name = $name;
        $this -> age = $age;
        $this -> sex = $sex;
    }
}

$person = new Person(&#39;孙悟空&#39;,256,&#39;男&#39;);
//使用file_put_contents()函数把将一个字符串写入文件 
file_put_contents(&#39;D:person.txt&#39;, serialize($person));
登入後複製

上面的程式碼file_put_contents()函數就是把一個字串寫入到一個檔案中。

上面的程式碼執行完可以看到在D盤有一個person.txt文件,裡面是一個轉換成字串的物件。

b.php文件,使用到a.php檔案中的物件person

<?php

    class Person{
        public $name;
        public $age;
        private $sex;
        public function __construct($name,$age,$sex){
            $this -> name = $name;
            $this -> age = $age;
            $this -> sex = $sex;
        }
    }
    //通过file_get_contents这个方法把一个文件读取到字符串。
    $str = file_get_contents(&#39;D:person.txt&#39;);
    //进行反序列化。
    $person = unserialize($str);
    echo &#39;<pre class="brush:php;toolbar:false">&#39;;
    var_dump($person);
    ......结果......
    object(Person)#1 (3) {
      ["name"]=>
      string(9) "孙悟空"
      ["age"]=>
      int(256)
      ["sex":"Person":private]=>
      string(3) "男"
    }
登入後複製

在b.php中透過file_get_contents()這個方法把一個檔案讀取到字串,然後透過unserialize()反序列化,但是這樣反序列化得到的對像不是person對象,所以在文件中把person類的聲明貼上複製過來,自動轉換成person對象。

物件的序列化和反序列化可以讓多個檔案共用一個物件。

PHP內建標準類


有時我們希望把一些數據,以物件的屬性的方式存儲,同時我們又不想定義一個類,可以考慮使用PHP內置標準類stdClass,這是用系統提供的一個虛擬的類,並不需要我們定義就可以直接使用。

<?php

    $person = new StdClass();
    $person -> name = &#39;孙悟空&#39;;
    $person -> age = 524;
    $person -> sex = &#39;男&#39;;
    echo &#39;<pre class="brush:php;toolbar:false">&#39;;
    var_dump($person);
    ......结果......
    object(stdClass)#1 (3) {
      ["name"]=>
      string(9) "孙悟空"
      ["age"]=>
      int(524)
      ["sex"]=>
      string(3) "男"
    }
登入後複製

在上面程式碼中我們可以看出來,並沒有定義stdClass這個類別就能使用。 #########Traits的使用#########Traits 是一種為類似 PHP 的單一繼承語言而準備的程式碼重複使用機制。 Trait 為了減少單繼承語言的限制,使開發人員能夠自由地在不同層次結構內獨立的類別中復用方法集。可以理解為在traits中定義一段程式碼區塊,可以在不同的類別中使用。 ###### 圖解: #########

traits使用语法:

trait 自定义名称{
    //额外定义的代码
}
在类中使用格式
use 自定义的名称;
登入後複製

代码:

<?php
    //使用trait,自定义名称
    trait my_code{
        public function minus($num1,$num2){
            return $num1 - $num2;
        }
    }

    class A{
        public function plus($num1,$num2){
            return $num1 + $num2;
        }
    }

    class B extends A{
        //使用trait定义的代码块
        use my_code;
    }

    class C extends A{
        use my_code;
    }

    class D extends A{

    }

    $b = new B();
    $c = new C();
    $d = new D();
    echo $b -> plus(1,2);
    echo &#39;<br>&#39;;
    echo $b -> minus(2,1);
    echo &#39;<br>&#39;;

    echo $d -> plus(1,2);
    echo &#39;<br>&#39;;
    echo $d -> minus(2,1);
    ......结果......
    3
    1
    3
    Fatal error: Call to undefined method D::minus() in D:\mywamp\Apache24\htdocs\zendstudio\yunsuanfu\staits.php on line 36
登入後複製

在上面代码中类D没有使用trait代码,所以在使用minus方法时,出现错误。

当我们在trait中写的函数和父类的函数一样时,以trait代码为准,即trait代码的优先级高于继承的类。

类与对象相关函数

在PHP中系统提供了一系列的函数来判断类和对象的。从帮助文档中可以看到好多函数:

在这里我们只对里面的几个函数进行了解。

<?php

    class Person{
        public $name;
        public $age;
        private $sex;
        public function __construct($name,$age,$sex){
            $this -> name = $name;
            $this -> age = $age;
            $this -> sex = $sex;
        }

        public function showInfo(){
            echo &#39;名字是:&#39; . $this -> name . &#39;,年龄是:&#39; . $this -> age . &#39;,性别是:&#39; . $this -> sex . &#39;<br>&#39;; 
        }
    }

    //判断是否创建了对象,没有创建返回true,创建返回false。
    if(class_exists(&#39;Person&#39;)){
        $person = new Person(&#39;唐僧&#39;,25,&#39;男&#39;);
        //返回对象的类名
        echo &#39;类名是: &#39; . get_class($person) . &#39;<br>&#39;;
        //判断方法是否存在。
        if(method_exists($person, &#39;showInfo&#39;)){
            $person -> showInfo();
        }else{
            echo &#39;该方法不存在&#39;;
        }
        //判断属性是否存在
        if(property_exists($person,&#39;name&#39;)){
            echo $person -> name;
        }else{
            echo &#39;属性不存在&#39;;
        }
    }else{
        echo &#39;类不存在&#39;;
    }
    ......结果......
    类名是: Person
    名字是:唐僧,年龄是:25,性别是:男
    唐僧
登入後複製

使用类和对象函数,可以保证我们代码的完整性,对出错信息进行及时的捕获输出。

PHP反射机制

在很多编程语言中都有反射这种概念,反射简单理解就是通过类,获取里面的属性,方法,甚至注释也可以,不管属性和方法的访问修饰符是什么类型都可以获取到。

在PHP 5中具有完整的反射API,添加了对类、接口、函数、方法和扩展进行反向工程的能力。此外,反射 API 提供了方法来取出函数、类和方法中的文档注释。而我们在开发中一般是使用不到反射的,但是在某些情况下使用反射可以更好的处理问题,比如我们需要我们写框架底层,扩展功能,管理大量的未知类。

定义一个类,通过反射创建对象和调用里面的方法:

<?php

    class Dog{

        public $name;
        protected $age;
        private $food;

        public function __construct($name, $age, $food){

            $this->name = $name;
            $this->age = $age;
            $this->food = $food;
        }

        public function cry($sound){

            echo &#39;<br> &#39; . $this->name . &#39; 叫声是.&#39; . $sound;
        }

    }

    //使用反射完成对象的创建和方法调用
    //1. 创建一个反射对象
    $reflect_obj = new ReflectionClass(&#39;Dog&#39;);

    //2. 通过反射对象创建Dog对象实例
    $dog = $reflect_obj->newInstance(&#39;大黄狗&#39;, 4, &#39;排骨&#39;);
    echo &#39;<pre class="brush:php;toolbar:false">&#39;;
    var_dump($dog);

    //3. 调用方法-使用代理方式.
    $reflect_method_cry = $reflect_obj->getMethod(&#39;cry&#39;);
    echo &#39;<pre class="brush:php;toolbar:false">&#39;;
    var_dump($reflect_method_cry);
    //4. 代理调用cry
    $reflect_method_cry->invoke($dog, &#39;汪汪&#39;);
登入後複製

结果:


在上面代码中,我们通过new创建了一个反射的对象,在反射对象里面通过newInstance()方法得到类的对象。获取里面的方法可以使用反射对象的getMethod()方法,返回来的是一个方法对象ReflectionMethod类,通过里面的invoke()方法执行该方法。这里只是基本的介绍,可以查找帮助文档了解更多的反射对象和方法。

反射实现TP控制器调度

需求:

有一个类IndexAction,其中的方法和访问控制修饰符是不确定的,
1. 如果index 方法是public,可以执行 _before_index.
2. 如果存在_before_index 方法,并且是public的,执行该方法
3. 执行test方法
4. 再判断有没有_after_index方法,并且是public的,执行该方法
登入後複製

代码:

<?php

    class IndexAction{
        public function index(){
            echo &#39;index<br>&#39;;
        }

        public function _before_index(){
            echo &#39;_before_index方法执行 <br>&#39;;
        }

        public function test($data){
            echo &#39;data : &#39;  . $data . &#39;<br>&#39;;
        }

        public  function _after_index(){
            echo &#39;_after_index方法执行<br>&#39;;
        }
    }

    if(class_exists(&#39;IndexAction&#39;)){
        //创建对象
        $reflectionClass = new ReflectionClass(&#39;IndexAction&#39;);
        //判断index是否存在
        if($reflectionClass -> hasMethod(&#39;index&#39;)){

            //获取index方法对象
            $reflec_index_method = $reflectionClass -> getMethod(&#39;index&#39;);
            //判断修饰符是否是public
            if($reflec_index_method -> isPublic()){
                //判断是否有_before_index方法
                if($reflectionClass -> hasMethod(&#39;_before_index&#39;)){
                    $reflec_before_method = $reflectionClass -> getMethod(&#39;_before_index&#39;);
                    //判断是否是public
                    if($reflec_before_method -> isPublic()){
                        $reflec_before_method -> invoke($reflectionClass -> newInstance());
                        //调用test()方法
                        $reflectionClass -> getMethod(&#39;test&#39;) -> invoke($reflectionClass -> newInstance(),&#39;这是test的数据&#39;);
                        //判断是否有_after_index方法
                        if($reflectionClass -> hasMethod(&#39;_after_index&#39;)){
                            $reflec_after_method = $reflectionClass -> getMethod(&#39;_after_index&#39;);
                            //判断是否是public
                            if($reflec_after_method -> isPublic()){
                                //执行_after_index方法
                                $reflec_after_method -> invoke($reflectionClass -> newInstance());

                            }else{
                                echo &#39;_after_index不是public修饰的&#39;;
                            }

                        }else{
                            echo &#39;没有_after_index方法&#39;;
                        }


                    }else{
                        echo &#39;_before_index修饰符不是public&#39;;
                    }

                }else{
                    echo &#39;没有_before_index方法&#39;;
                }


            }else{
                echo &#39;index方法不是public修饰&#39;;
            }


        }else{
            echo &#39;index方法不存在&#39;;
        }

    }else{
        echo &#39;类名不存在&#39;;
    }
    ......结果.......
    _before_index方法执行 
    data : 这是test的数据
    _after_index方法执行
登入後複製

在上面的代码中可以看到我们不停地在判断类中有没有某个方法,是不是public修饰,然后执行,我们可以利用封装的思想,把一些共性的特征抽取出来写成一个函数。从而对我们的代码进行优化。

优化的代码:

<?php

    class IndexAction{
        public function index(){
            echo &#39;index<br>&#39;;
        }

        public function _before_index(){
            echo &#39;_before_index方法执行 <br>&#39;;
        }

        public function test($data){
            echo &#39;data : &#39;  . $data . &#39;<br>&#39;;
        }

        public  function _after_index(){
            echo &#39;_after_index方法执行<br>&#39;;
        }
    }

    if(class_exists(&#39;IndexAction&#39;)){
        $reflectionClass = new ReflectionClass(&#39;IndexAction&#39;);
        //创建IndexAction对象。
        $indexAction = $reflectionClass -> newInstance();

        execute($reflectionClass,$indexAction,&#39;index&#39;);
        execute($reflectionClass,$indexAction,&#39;_after_index&#39;);
        execute($reflectionClass,$indexAction,&#39;test&#39;,&#39;test使用的数据&#39;);
        execute($reflectionClass,$indexAction,&#39;_after_index&#39;);
    }else{
        echo &#39;没有IndexAction方法&#39;;
    }

    //封装的函数
    /**
     * [execute description]对反射的封装。
     * @param  [type]  $reflect_obj [description]反射对象
     * @param  [type]  $worker      [description]类对象
     * @param  [type]  $name        [description]方法的名字
     * @param  [type]  $canshu      [description]方法的参数
     * @return boolean              [description]
     */
    function execute($reflect_obj,$indexAction,$name,$param = &#39;&#39;){
        if($reflect_obj-> hasMethod($name)){
            $method = $reflect_obj->getMethod($name);
        if($method->isPublic()){
            $method->invoke($indexAction,$param); 
        }else{
            echo $name . &#39;不是public&#39;;
        }
        }else{
            echo $name . &#39;方法不存在&#39;;
        }
    }
    ......结果.....
    index
    _after_index方法执行
    data : test使用的数据
    _after_index方法执行
登入後複製

可以看到进行功能的封装,可以简化我们的代码,同时代码看起来更加的清晰。

总结

PHP的面向对象的内容到这里算是讲完了,在开发中利用面向对象的思想进行开发是一定要掌握的技能。同时也要对面向对象进行深度的了解。

 以上就是PHP,反射、对象序列化的内容,更多相关内容请关注PHP中文网(www.php.cn)!


來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!