序列化與反序列化

php序列化與反序列化:

把複雜的數據類型壓縮到一個字符串中

serialize() 把變量和它們的值編碼成文本形式
unserialize() 恢復原先變量 

通過序列化我們可以將一些模塊化的數據使用字符串的形式存儲在數據庫或session等,可以減少創建衆多繁瑣的數據表字段,當然序列化爲字符串存儲會增加額外的空間,應合理的設計和應用。

對象的序列化和反序列化作用就不再贅述,php中序列化的結果是一個php自定義的字符串格式,有點類似json.

我們在任何語言中設計對象的序列化和反序列化都需要解決幾個問題

把某個對象序列化之後,序列化的結果有自描述的功能(從序列化的結果中知道這個對象的具體類型,

知道類型還不夠,當然還需要知道這個類型所對應具體的值).

序列化時的權限控制,可以自定義序列化字段等,例如golang中的做的就非常方便.

時間性能問題:在某些性能敏感的場景下,對象序列化就不能拖後腿,例如:高性能服務(我經常使用protobuf來序列化).

空間性能問題:序列化之後的結果不能太長,比如內存中一個int對象,序列化之後數據長度變成了10倍int的長度,那這個序列化算法是有問題的.

本文僅僅從php代碼角度來解釋php中序列化和反序列化的過程.,記住一點序列化和反序列化操作的僅僅是對象的數據,這一點有面向對象開發經驗的都應該容易理解.

1.序列化serialize和反序列化方法unserialize

php原生提供了對象序列化功能,不像c++ ……^_^. 用起來也非常簡單,就兩個接口

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

class fobnn

{

 public $hack_id;

 private $hack_name;

 public function __construct($name,$id)

 {

  $this->hack_name = $name;

  $this->hack_id = $id;

 }

 public function print()

 {

  echo $this->hack_name.PHP_EOL;

 }

}

$obj = new fobnn('fobnn',1);

$obj->print();

$serializedstr = serialize($obj); //通過serialize接口序列化

echo $serializedstr.PHP_EOL;;

$toobj = unserialize($serializedstr);//通過unserialize反序列化

$toobj->print();

1

2

3

fobnn

O:5:"fobnn":2:{s:7:"hack_id";i:1;s:16:"fobnnhack_name";s:5:"fobnn";}

fobnn

看到第二行的輸出,這個字符串就是序列化的結果,這個結構其實很容讀懂,可以發現是通過對象名稱/成員名稱來映射的,當然不同訪問權限的成員序列化之後的標籤名稱略有不同.

根據我上面講到的3個問題,那麼我們可以來看看

1.自描述功能

O:5:"fobnn":2 其中o就表示了object類型,且類型名稱爲fobnn, 採用這種格式,後面的2表示了有2個成員對象.

關於成員對象,其實也是同一套子描述,這是一個遞歸的定義.

自描述的功能主要是通過字符串記錄對象和成員的名稱來實現.

2.性能問題

php序列化的時間性能本文就不分析了,詳見後面,但序列化結果其實類似json/bson定義的協議,有協議頭,協議頭說明了類型,協議體則說明了類型所對應的值,並不會對序列化結果進行壓縮.

2.反序列化中的魔術方法

對應上述說的第二個問題,其實php中也有解決方法,一種是通過魔術方法,第二種則是自定義序列化函數.先來介紹下魔術方法 __sleep和__wakeup

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

class fobnn

{

 public $hack_id;

 private $hack_name;

 public function __construct($name,$id)

 {

  $this->hack_name = $name;

  $this->hack_id = $id;

 }

 public function print()

 {

  echo $this->hack_name.PHP_EOL;

 }

 public function __sleep()

 {

  return array("hack_name");

 }

 public function __wakeup()

 {

  $this->hack_name = 'haha';

 }

}

$obj = new fobnn('fobnn',1);

$obj->print();

$serializedstr = serialize($obj);

echo $serializedstr.PHP_EOL;;

$toobj = unserialize($serializedstr);

$toobj->print();

1

2

3

fobnn

O:5:"fobnn":1:{s:16:"fobnnhack_name";s:5:"fobnn";}

haha

在序列化之前會先調用__sleep返回的是一個需要序列化的成員名稱數組,通過這樣我們就可以控制需要序列化的數據,案例中我只返回了hack_name,可以看到結果中只序列化了hack_name成員.

在序列化完成之後,會跳用__wakeup 在這裏我們可以做一些後續工作,例如重連數據庫之類的.

3.自定義Serializable接口

1

2

3

4

interface Serializable {

abstract public string serialize ( void )

abstract public void unserialize ( string $serialized )

}

通過這個接口我們可以自定義序列化和反序列化的行爲,這個功能主要可以用來自定義我們的序列化格式.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

class fobnn implements Serializable

{

 public $hack_id;

 private $hack_name;

 public function __construct($name,$id)

 {

  $this->hack_name = $name;

  $this->hack_id = $id;

 }

 public function print()

 {

  echo $this->hack_name.PHP_EOL;

 }

 

 public function __sleep()

 {

  return array('hack_name');

 }

 

 public function __wakeup()

 {

  $this->hack_name = 'haha';

 }

 

 public function serialize()

 {

  return json_encode(array('id' => $this->hack_id ,'name'=>$this->hack_name ));

 }

 

 public function unserialize($var)

 {

  $array = json_decode($var,true);

  $this->hack_name = $array['name'];

  $this->hack_id = $array['id'];

 }

}

$obj = new fobnn('fobnn',1);

$obj->print();

$serializedstr = serialize($obj);

echo $serializedstr.PHP_EOL;;

$toobj = unserialize($serializedstr);

$toobj->print();

?

1

2

3

fobnn

C:5:"fobnn":23:{{"id":1,"name":"fobnn"}}

fobnn

當使用了自定義序列化接口之後,我們的魔術方法就沒用了.

4.PHP動態類型和PHP反序列化

既然上文中提到的自描述功能,那麼序列化結果中保存了對象的類型,且php是動態類型語言,那麼我們就可以來做個簡單的實驗.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

class fobnn

{

 public $hack_id;

 public $hack_name;

 public function __construct($name,$id)

 {

  $this->hack_name = $name;

  $this->hack_id = $id;

 }

 public function print()

 {

  var_dump($this->hack_name);

 }

}

$obj = new fobnn('fobnn',1);

$obj->print();

$serializedstr = serialize($obj);

echo $serializedstr.PHP_EOL;;

$toobj = unserialize($serializedstr);

$toobj->print();

$toobj2 = unserialize("O:5:\"fobnn\":2:{s:7:\"hack_id\";i:1;s:9:\"hack_name\";i:12345;}");

$toobj2->print();

我們修改hack_name反序列化的結果爲int類型, i:12345

1

2

3

4

string(5) "fobnn"

O:5:"fobnn":2:{s:7:"hack_id";i:1;s:9:"hack_name";s:5:"fobnn";}

string(5) "fobnn"

int(12345)

可以發現,對象成功序列化回來了!並且可以正常工作!. 當然php的這種機制提供了靈活多變的語法,但也引入了安全風險. 後續繼續分析php序列化和反序列化特性帶來的安全問題.

以上就是我們整理的關於PHP序列化和反序列化原理的全部知識內容,感謝你對腳本之家的支持。

本篇轉載自:https://www.jb51.net/article/132838.htm

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