PHP基礎教程十三之反射、對象序列化

本節講解的內容

  • 對象的克隆
  • 對象的遍歷
  • 對象的序列化和反序列化
  • 內置標準類的使用
  • 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('宋江','45','男');
echo '<pre>';

$b = clone $a;
$b -> name = '武松';
var_dump($a);
var_dump($b);

結果

對象克隆的語法:

$新對象 = clone $就對象;

從上面的結果中可以看出來,一個對象的數據變化,並不會影響到另外一個對象的數據。對象克隆會生成一個全新的對象。可以理解如下:

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

如果不想一個對象進行克隆可以在內部把魔術方法__clone()方法定義成私有的,這時如果在進行克隆,會提示

Call to private Peoson::__clone() from context ''

對象的遍歷

在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('孫悟空',256,'男');

foreach ($person as $key => $value) {
    echo $key . ' = ' . $value . '<br>';
}
.......結果........
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('孫悟空',256,'男');
//使用file_put_contents()函數把將一個字符串寫入文件 
file_put_contents('D:person.txt', 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('D:person.txt');
    //進行反序列化。
    $person = unserialize($str);
    echo '<pre>';
    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 = '孫悟空';
    $person -> age = 524;
    $person -> sex = '男';
    echo '<pre>';
    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 '<br>';
    echo $b -> minus(2,1);
    echo '<br>';

    echo $d -> plus(1,2);
    echo '<br>';
    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 '名字是:' . $this -> name . ',年齡是:' . $this -> age . ',性別是:' . $this -> sex . '<br>'; 
        }
    }

    //判斷是否創建了對象,沒有創建返回true,創建返回false。
    if(class_exists('Person')){
        $person = new Person('唐僧',25,'男');
        //返回對象的類名
        echo '類名是: ' . get_class($person) . '<br>';
        //判斷方法是否存在。
        if(method_exists($person, 'showInfo')){
            $person -> showInfo();
        }else{
            echo '該方法不存在';
        }
        //判斷屬性是否存在
        if(property_exists($person,'name')){
            echo $person -> name;
        }else{
            echo '屬性不存在';
        }
    }else{
        echo '類不存在';
    }
    ......結果......
    類名是: 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 '<br> ' . $this->name . ' 叫聲是.' . $sound;
        }

    }

    //使用反射完成對象的創建和方法調用
    //1. 創建一個反射對象
    $reflect_obj = new ReflectionClass('Dog');

    //2. 通過反射對象創建Dog對象實例
    $dog = $reflect_obj->newInstance('大黃狗', 4, '排骨');
    echo '<pre>';
    var_dump($dog);

    //3. 調用方法-使用代理方式.
    $reflect_method_cry = $reflect_obj->getMethod('cry');
    echo '<pre>';
    var_dump($reflect_method_cry);
    //4. 代理調用cry
    $reflect_method_cry->invoke($dog, '汪汪');

結果:


在上面代碼中,我們通過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 'index<br>';
        }

        public function _before_index(){
            echo '_before_index方法執行 <br>';
        }

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

        public  function _after_index(){
            echo '_after_index方法執行<br>';
        }
    }

    if(class_exists('IndexAction')){
        //創建對象
        $reflectionClass = new ReflectionClass('IndexAction');
        //判斷index是否存在
        if($reflectionClass -> hasMethod('index')){

            //獲取index方法對象
            $reflec_index_method = $reflectionClass -> getMethod('index');
            //判斷修飾符是否是public
            if($reflec_index_method -> isPublic()){
                //判斷是否有_before_index方法
                if($reflectionClass -> hasMethod('_before_index')){
                    $reflec_before_method = $reflectionClass -> getMethod('_before_index');
                    //判斷是否是public
                    if($reflec_before_method -> isPublic()){
                        $reflec_before_method -> invoke($reflectionClass -> newInstance());
                        //調用test()方法
                        $reflectionClass -> getMethod('test') -> invoke($reflectionClass -> newInstance(),'這是test的數據');
                        //判斷是否有_after_index方法
                        if($reflectionClass -> hasMethod('_after_index')){
                            $reflec_after_method = $reflectionClass -> getMethod('_after_index');
                            //判斷是否是public
                            if($reflec_after_method -> isPublic()){
                                //執行_after_index方法
                                $reflec_after_method -> invoke($reflectionClass -> newInstance());

                            }else{
                                echo '_after_index不是public修飾的';
                            }

                        }else{
                            echo '沒有_after_index方法';
                        }


                    }else{
                        echo '_before_index修飾符不是public';
                    }

                }else{
                    echo '沒有_before_index方法';
                }


            }else{
                echo 'index方法不是public修飾';
            }


        }else{
            echo 'index方法不存在';
        }

    }else{
        echo '類名不存在';
    }
    ......結果.......
    _before_index方法執行 
    data : 這是test的數據
    _after_index方法執行

在上面的代碼中可以看到我們不停地在判斷類中有沒有某個方法,是不是public修飾,然後執行,我們可以利用封裝的思想,把一些共性的特徵抽取出來寫成一個函數。從而對我們的代碼進行優化。

優化的代碼:

<?php

    class IndexAction{
        public function index(){
            echo 'index<br>';
        }

        public function _before_index(){
            echo '_before_index方法執行 <br>';
        }

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

        public  function _after_index(){
            echo '_after_index方法執行<br>';
        }
    }

    if(class_exists('IndexAction')){
        $reflectionClass = new ReflectionClass('IndexAction');
        //創建IndexAction對象。
        $indexAction = $reflectionClass -> newInstance();

        execute($reflectionClass,$indexAction,'index');
        execute($reflectionClass,$indexAction,'_after_index');
        execute($reflectionClass,$indexAction,'test','test使用的數據');
        execute($reflectionClass,$indexAction,'_after_index');
    }else{
        echo '沒有IndexAction方法';
    }

    //封裝的函數
    /**
     * [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 = ''){
        if($reflect_obj-> hasMethod($name)){
            $method = $reflect_obj->getMethod($name);
        if($method->isPublic()){
            $method->invoke($indexAction,$param); 
        }else{
            echo $name . '不是public';
        }
        }else{
            echo $name . '方法不存在';
        }
    }
    ......結果.....
    index
    _after_index方法執行
    data : test使用的數據
    _after_index方法執行

可以看到進行功能的封裝,可以簡化我們的代碼,同時代碼看起來更加的清晰。

總結

PHP的面向對象的內容到這裏算是講完了,在開發中利用面向對象的思想進行開發是一定要掌握的技能。同時也要對面向對象進行深度的瞭解。

發佈了38 篇原創文章 · 獲贊 28 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章