1. 程式人生 > 程式設計 >如何使用Serializable介面來自定義PHP中類的序列化

如何使用Serializable介面來自定義PHP中類的序列化

關於php中的物件序列化這件事兒,之前我們在很早前的文章中已經提到過 __sleep() 和 __weakup() 這兩個魔術方法。今天我們介紹的則是另外一個可以控制序列化內容的方式,那就是使用 Serializable 介面。它的使用和上述兩個魔術方法很類似,但又稍有不同。

Serializable介面

class A implements Serializable {
    private $data;
    public function __construct(){
        echo '__construct',PHP_EOL;
        $this->data = "This is Class A";
    }

    public function serialize(){
        echo 'serialize',PHP_EOL;
        return serialize($this->data);
    }

    public function unserialize($data){
        echo 'unserialize',PHP_EOL;
        $this->data = unserialize($data);
    }

www.cppcns.com
public function __destruct(){ echo '__destruct',PHP_EOL; } public function __weakup(){ echo '__weakup',PHP_EOL; } public function __sleep(){ echo '__destruct',PHP_EOL; } } $a = new A(); $aSerialize = serialize($a); var_dump($aSerialize); // "C:1:"A":23:{s:15:"This is Class A";}" $a1 = unserialize($aSerialize); var_dump($a1);

這段程式碼就是使用 Serializable 介面來進行序列化處理的,注意一點哦,實現了 Serializable 介面的類中的 __sleep() 和 __weakup() 魔術方法就無效了哦,序列化的時候不會進入它們。

Serializable 這個介面需要實現的是兩個方法,serialize() 方法和 unserialize() 方法,是不是和那兩個魔術方法完全一樣。當然,使用的方式也是一樣的。

在這裡,我們多普及一點序列化的知識。物件序列化只能序列化它們的屬性,不能序列化他們方法。如果當前能夠找到對應的類模板,那麼可以還原出這個類的方法來,如果沒有定義過這個類的模板,那麼還原出來的類是沒有方法只有屬性的。我們通過這段程式碼中的序列化字串來分析:

    http://www.cppcns.com
  • "C:",指的是當前資料的型別,這個我面後面還會講,實現 Serializable 介面的物件序列化的結果是 C: ,而沒有實現這個介面的物件序列化的結果是 O:
  • "A:",很明顯對應的是類名,也就是類的::class
  • "{xxx}",物件結構和jsON一樣,也是用的花括號

各種型別的資料進行序列化的結果

下面我們再來看下不同型別序列化的結果。要知道,在PHP中,我們除了控制代碼型別的資料外,其他標量型別或者是陣列、物件都是可以序列化的,它們在序列化字串中是如何表示的呢?

$int = 110;
$string = '110';
$bool = FALSE;
$null = NULL;
$array = [1,2,3];

var_dump(serialize($int)); // "i:110;"
var_dump(serialize($string)); // "s:3:"110";"
var_dump(serialize($bool)); // "b:0;"
var_dump(serialize($null)); // "N;"
var_dump(serialize($array)); // "a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}"

上面的內http://www.cppcns.com容還是比較好理解的吧。不過我們還是一一說明一下:

  • 數字型別:i:<值>
  • 字串型別:s:<長度>:<值>
  • 布林型別:b:<值:0或1>
  • NULL型別:N;
  • 陣列:a:<長度>:<內容>

物件在使用Serializable介面序列化時要注意的地方
http://www.cppcns.com

接下來,我們重點講講物件型別,上面已經提到過,實現 Serializable 介面的物件序列化後的標識是有特殊情況的。上方序列化後的字串開頭型別標識為 "C:",那麼我們看看不實現 Serializable 介面的物件序列化後是什麼情況。

// 正常物件型別序列化的結果
class B {
    private $data = "This is Class B";

}
$b = new B();
$bSerialize = serialize($b);

var_dump ($bSerialize); // "O:1:"B":1:{s:7:"Bdata";s:15:"This is Class B";}"
var_dump($bSerialize);
var_dump(u程式設計客棧nserialize("O:1:\"B\":1:{s:7:\"\0B\0data\";s:15:\"This is Class B\";}"));

// object(B)#4 (1) {
//     ["data":"B":private]=>string(15) "This is Class B"
// }

果然,它開頭的型別標識是 "O:"。那麼我們可以看出,"C:" 很大的概率指的是當前序列化的內容是一個類型別,不是一個物件型別。它們之間其實並沒有顯著的差異,包括官方文件上也沒有找到特別具體的說明。如果有過這方面的研究或者有相關資料的同學可以評論留言一起討論哈。

此外,如果我們手動將一個物件的 "O:" 轉成 "C:" 會怎麼樣呢?

// 把O:替換成C:
var_dump(unserialize(str_replace('O:','C:',$bSerialize))); // false

抱歉,無法還原了。那麼我們反過來,將上面 A 類也就是實現了 Serializable 介面的序列化字串中的 "C:" 轉成 "O:" 呢?

// Warning: Erroneous data format for unserializing 'A'
var_dump(unserialize(str_replace('C:','O:',$aSerialize))); // false

嗯,會提示一個警告,然後同樣也無法還原了。這樣看來,我們的反序列化還是非常智慧的,有一點點的不同都無法進行還原操作。

未定義類的反序列化操作

最後,我們來看看未定義類的情況下,直接反序列化一個物件。

// 模擬一個未定義的D類
var_dump(unserialize("O:1:\"D\":2:{s:7:\"\0D\0data\";s:15:\"This is Class D\";s:3:\"int\";i:220;}"));

// object(__PHP_Incomplete_Class)#4 (3) {
//     ["__PHP_Incomplete_Class_Name"]=>string(1) "D"
//     ["data":"D":private]=>string(15) "This is Class D"
//     ["int"]=>int(220)
// }

// 把未定義類的O:替換成C:
var_dump(unserialize(str_replace('O:',"O:1:\"D\":2:{s:7:\"\0D\0data\";s:15:\"This is Class D\";s:3:\"int\";i:220;}"))); // false

從程式碼中,我們可以看出,"C:" 型別的字串依然無法反序列化成功。劃重點哦,如果是C:開頭的序列化字串,一定需要是定義過的且實現了 Serializable 介面的類 才能反序列化成功。

另外,我們可以發現,當序列化字串中的模板不存在時,反序列化出來的類的類名是 __PHP_Incomplete_Class_Name 類,不像有類模板的反序列化成功直接就是正常的類名。

總結

其實從以上各種來看,個人感覺如果要儲存資料或者傳遞資料的話,序列化並不是最好的選擇。畢竟包含了型別以及長度後將使得格式更為嚴格,而且反序列化回來的內容如果沒有對應的類模板定義也並不是特別好用的,還不如直接使用 JSON 來得方便易讀。當然,具體情況具體分析,我們還是要結合場景來選擇合適的使用方式。

測試程式碼:

github.com/zhangyue050…

以上就是如何使用Serializable介面來自定義PHP中類的序列化的詳細內容,更多關於自定義PHP中類的序列化的資料請關注我們其它相關文章!