ホームページ >バックエンド開発 >PHPチュートリアル >PHPシリアル化シリアル化パターンの詳細説明

PHPシリアル化シリアル化パターンの詳細説明

WBOY
WBOYオリジナル
2016-06-20 13:01:161113ブラウズ

1.はじめに

PHP (PHP 3.05 以降) は、ストレージ オブジェクトのシリアル化および逆シリアル化関数のセット (シリアル化、シリアル化解除) を提供します。しかし、PHPでは
マニュアルではこれら 2 つの機能の説明は使い方のみで、シリアル化された結果の形式については説明がありません。したがって、このペアは他の言語での PHP を完成させます
メソッドのシリアル化という点では、さらに面倒です。他の言語で完成したPHPもいくつか集めましたが
シリアル化シーケンスですが、これらの実装は完了していません。より複雑なオブジェクトをシリアル化または逆シリアル化すると、エラーが発生します。そこで、PHPについての記事を書くことにしました
シリアル化形式に関する詳細なドキュメント (つまり、このドキュメント)。他の言語で完成した PHP を書くことができるようになります。
プログラムをシリアル化するときに、より完全なリファレンスを得ることができます。この記事に書かれている内容は、逐次テストを書いたり、PHPのソースコードを読んだりして得られたものなので、100%保証することはできません
すべての内容が正しいことを保証しますが、不明な場所については、記事内で明確に指摘し、皆様に知っていただけるよう最善を尽くします。サプリメントを提供して完了することができます。

2.概要

PHP シリアル化後のコンテンツは単純なテキスト形式ですが、大文字と小文字と空白 (スペース、復帰、改行など) が区別され、文字列はバイト単位 (おそらく 8
) ビット文字)、したがって、より適切なステートメントは PHP
です。 シリアル化されたコンテンツはバイト ストリーム形式です。したがって、他の言語で実装される場合、実装される言語の文字列はバイト格納形式ではなく、Unicode
であると想定されます。 保存形式の観点から言えば、シリアル化されたコンテンツは文字列として保存するのには適しておらず、バイト ストリーム オブジェクトまたはバイト配列として保存する必要があります。そうしないと、PHP とのデータ交換時にエラーが発生します。

PHP では、さまざまな種類のデータをマークするためにさまざまな文字が使用されます。Yahoo 開発サイトが提供する「Using Serialized PHP with Yahoo! Web Services」という記事に、すべての文字マークとその意味が記載されています。

ワードラップ: ブレークワード" bgColor=#ddedfb>

a - 配列
b - ブール値
d - ダブル
i - 整数
o - 共通オブジェクト
r 参照
s - 文字列
C - カスタム オブジェクト
O - クラス
N - null
R - ポインター参照
U - ユニコード文字列

N は NULL を表し、b、d、i、s は 4 つのスカラー型を表します。現在、他の言語によって完成された PHP シリアル化シーケンスは、これらの型のシリアル化と逆シリアル化を完了していますが、いくつかの疑問があります。一部の実装では s (文字列) の補完。

a と O は、最も一般的に使用される複合型です。他の言語のほとんどの実装では、A のシリアル化と逆シリアル化が完了していますが、O については、PHP4 のオブジェクトのシリアル化形式のみが実装されていますが、サポートは提供されていません。 PHP 5 の拡張オブジェクトシリアル化形式。

r と R はそれぞれ、オブジェクト参照とポインター参照を表します。これら 2 つのラベルを持つデータは、後で詳しく説明します。 2 つの記号は他の単語の完了を認識しません。

C は PHP5 で導入されました。これは、他の言語ではあまり使用されないため、必要ありませんが、後で詳しく説明します。

U は PHP6 で導入され、Unicode でエンコードされた文字列を表します。 PHP6ではUnicode

が提供されているので 文字列を保存する機能があるため、文字列をシリアル化するためにこの形式が提供されますが、このタイプは PHP5、PHP4
どちらもサポートされておらず、これら 2 つのバージョンが現在主流であるため、他の言語がこの型を実装する場合、シリアル化を実行するためにこれを使用することはお勧めできませんが、逆シリアル化プロセスは完了できます。後でその形式もマークします
明るい。

先頭に o がありますが、これは私がまだ理解していない唯一のデータ型インジケーターです。このフラグはオブジェクトをシリアル化するために PHP3 で導入されましたが、PHP4 では O

になりました。 交換されました。 PHP3 のソースコードを見ると、o のシリアル化と逆シリアル化は配列 a と基本的に同じであることがわかります。ただし、PHP4、PHP5、および PHP6 では
ソースコードのシリアライズ部分には痕跡がありませんが、これら複数のバージョンのデシリアライズシーケンスのソースコードでは処理されていますが、何に処理されているかはわかりません。したがって、当面はこれ以上の説明は省略する。

3. NULL 型とスカラー型のシリアル化

NULL 型とスカラー型のシリアル化は最も簡単で、準拠する型のシリアル化の基礎となります。多くの PHP 開発者はコンテンツのこの部分に精通していると思います。コンテンツのこの部分を以前に習得したと思われる場合は、この章を直接スキップできます。

3.1. NULL のシリアル化

PHP では、

NULL は次のようにシリアル化されます:

N;

3.2.ブール値データのシリアル化

boolean 型データは次のようにシリアル化されます:

b:;

は 0 または 1 です。ブール値データが false の場合、 は 0、それ以外の場合は 1 です。

3.3.整数データのシリアル化

integer 型データ (整数) は次のようにシリアル化されます:

i:;

ここで、 は -2147483648 から
までの整数です。 2147483647。数値の前にプラス記号またはマイナス記号を付けることができます。シリアル化された数値がこの範囲を超える場合、整数型ではなく浮動小数点数型にシリアル化されます。シリアル番号がこの範囲を超えると仮定します
(この問題は、PHP 自体がシリアル化する場合には発生しません)、逆シリアル化すると、期待された値が返されません。

3.4.二重データのシリアル化

double 型データ (浮動小数点数) は次のようにシリアル化されます:

d:;

は浮動小数点数であり、その範囲は PHP と同じです
浮動小数点数の範囲は同じです。整数モード、浮動小数点数モード、科学的手法モードで表現できます。無限の数値をシリアル化すると、 d80b5def5ed1be6e26d91c2709f14170 は
になります。 INF (シリアル化が無限に大きいと仮定すると、d80b5def5ed1be6e26d91c2709f14170) は -INF です。 PHP を超えるシリアル化された番号範囲
表現できる最大値は、逆シリアル化中に無限大 (INF) に返されます。シリアル化された数値範囲が PHP が表現できる最小精度を超える場合、逆シリアル化中に 0 が返されます。

3.5.文字列型データのシリアル化

string 型のデータ (文字列) は次のようにシリアル化されます:

s::"";

ここで、 の長さです。
これは負ではない整数であり、数値の前に正の符号 (+) を付けることができます。 8487820b627113dd990f63dd2ef215f3 は文字列値で、ここでの各文字は半角文字であり、その範囲は ASCII コード 0 ~
と同じです。 255文字対応。各文字は元の文字の意味を表し、エスケープ文字はありません。8487820b627113dd990f63dd2ef215f3 の両側の引用符 ("") は必須ですが、
にはカウントされません。 の間。ここでの 8487820b627113dd990f63dd2ef215f3 はバイト ストリームに相当し、
このバイト ストリーム内のバイト数です。

4.複合型の簡単なシリアル化

PHP には配列とオブジェクトの 2 種類の複合型があります。この章では主に、簡単な状況におけるこれら 2 種類のデータのシリアル化形式を紹介します。複合型のネストされた定義を含むオブジェクトのシリアル化形式とカスタムのシリアル化メソッドについては、後の章で詳しく説明します。

4.1.配列のシリアル化

配列 は通常、次のようにシリアル化されます:

a::{...}


ここで、

は配列要素の数を表します。

配列の添字を表します。... は、添字に対応する配列要素の値を表します。

添字タイプは整数または文字列のみにすることができます。シリアル化後の形式は、整数および文字列データのシリアル化後の形式と似ています。

配列要素の値は任意の型にすることができ、そのシリアル化された形式は、対応する型のシリアル化された形式と似ています。

4.2.オブジェクトのシリアル化

オブジェクト

は通常、
としてシリアル化されます。
O::" 名前>"::{ 名前 2>... いいえ>}

ここで、 はオブジェクトのクラス名 の文字列長を表します。

オブジェクト内のフィールドの数を表します。これらのフィールドには、オブジェクトが配置されているクラスとその祖先クラスの var、public、protected、および private

が含まれます。 宣言されたフィールド。ただし、static および const で宣言された静的フィールドは除きます。つまり、インスタンス フィールドのみが必要です。

、…… n>は各フィールドのフィールド名を表し、、....

value n> はフィールド名に対応するフィールド値を示します。

フィールド名は文字列型で、シリアル化後の形式は文字列データのシリアル化後の形式と同様です。

フィールド値は任意の型にすることができ、そのシリアル化された形式は、対応する型のシリアル化された形式と似ています。

しかし、フィールド名のシリアル化は、宣言された可視性と関連しています。以下ではフィールド名のシリアル化に焦点を当てましょう。

4.3.オブジェクトフィールド名のシリアル化

var と public で宣言されたフィールドはどちらも public フィールド であるため、フィールド名のシリアル化形式は似ています。パブリック フィールドのフィールド名は、宣言時のフィールド名に従ってシリアル化されますが、シリアル化されたフィールド名には、宣言時の変数接頭辞記号 $ は含まれません。

protected 宣言されたフィールドは維持フィールド であり、宣言されたクラスおよびクラスのサブクラスでは表示されますが、クラスのオブジェクト インスタンスでは表示されません。したがって、維持されるフィールドのフィールド名がシリアル化されると、フィールド名の前に が付きます。 private によって宣言されたフィールドは個別のフィールド であり、宣言されたクラスでのみ表示され、クラスのサブクラスやクラスのオブジェクト インスタンスでは表示されません。したがって、個々のフィールドのフィールド名がシリアル化されると、フィールド名の前に

が付きます。

フィールド名が文字列としてシリアル化される場合、文字列値にはその可視性に基づいたプレフィックスが含まれます。文字列の長さには、追加されたプレフィックスの長さも含まれます。で 1 音 : PHP マニュアルではフィールドをプロパティと呼んでいますが、実際には、PHP 5 で導入された __set および __get で定義されたオブジェクト メンバーをプロパティと呼ぶ方が適切です。 を使用しているため __set および __get で定義されたオブジェクト メンバーの動作は、他の言語および PHP

のプロパティと一貫しています。 マニュアルで説明されているプロパティは、実際には他の言語 (C# など) ではフィールドと呼ばれます。混乱を避けるために、ここではプロパティではなくフィールドとも呼ばれます。

5.ネストされた複合型のシリアル化

前の章では、複合型の簡単なシリアル化について説明しましたが、簡単な配列やオブジェクトも本質的に簡単であることがわかります。しかし、自分自身を含むオブジェクトや配列、または A に B が含まれる、B に A が含まれるオブジェクトや配列が見つかった場合、PHP はそのようなオブジェクトや配列をどのようにシリアル化すればよいでしょうか?この章では、この場合のシリアル化方法について説明します。



5.1.オブジェクト参照とポインタ参照

PHP では、スカラー型データは値によって渡されますが、複合型データ (オブジェクトと配列) は参照によって渡されます。ただし、複合型データの参照渡しと、& 記号を使用した明確な参照渡しは、前者がオブジェクト参照であるのに対し、後者はポインタ参照であるという違いがあります。

オブジェクト参照とポインタ参照を説明する前に、いくつかの例を見てみましょう。

この例の出力は次のとおりです: O:11:"サンプルクラス":1:{s:5:"値";r:1;} O:11:"サンプルクラス":1:{s:5:"値";R:1;}

変数 $a の値フィールドの値が r:1 にシリアル化され、$b の値フィールドの値が R:1 にシリアル化されていることに気づくでしょう。

しかし、
<?php
echo "<pre class="brush:php;toolbar:false">";
class SampleClass {
var $value;
}
$a = new SampleClass();
$a->value = $a;

$b = new SampleClass();
$b->value = &$b;

echo serialize($a);
echo "\n";
echo serialize($b);
echo "\n";
echo "
"; ?>オブジェクト参照とポインタ参照の違い

は何でしょうか?

次の例を見てみましょう:


一般の人々は気づくでしょう、そして結果はあなたの予想を超えるかもしれません:

オブジェクト(サンプルクラス)#1 (1) {

["値"]=> int(1) }

int(1)

$a->value の値を変更すると $a->value の値のみが変更されますが、$b->value の値を変更すると $b 自体が変更されます。これがオブジェクト参照とポインターの違いです。違いを参照してください。
echo "<pre class="brush:php;toolbar:false">";
class SampleClass {
var $value;
}
$a = new SampleClass();
$a->value = $a;

$b = new SampleClass();
$b->value = &$b;

$a->value = 1;
$b->value = 1;

var_dump($a);
var_dump($b);
echo "
";


残念ながら、PHP は配列をシリアル化するときに間違いを犯しました。配列自体もオブジェクト参照によって渡されますが、PHP はシリアル化中にこれを忘れたようです。
を参照してください。

結果は次のとおりです:

1


元の配列をシリアル化および逆シリアル化すると、配列構造が変更されていることに気づくでしょう。 $a["value"]["value"][1] の元の値 1 は、逆シリアル化後に失われます。

その理由は何ですか?シリアル化後の出力を見てみましょう:

echo "<pre class="brush:php;toolbar:false">";
$a = array();
$a[1] = 1;
$a["value"] = $a;

echo $a["value"]["value"][1];
echo "\n";
$a = unserialize(serialize($a));
echo $a["value"]["value"][1];
echo "
";

結果は次のとおりです:

a:2:{i:1;i:1;s:5:"値";a:2:{i:1;i:1;s:5:"値";N;}}

シリアル化後、$a["value"]["value"] がオブジェクト参照ではなく NULL になることがわかりました。


言い換えると、

PHP はオブジェクトをシリアル化するときにオブジェクト参照タグ (r) のみを生成します。すべてのスカラー型および配列 (NULL を含む) をシリアル化する場合、オブジェクト参照は生成されません。ただし、& 記号を使用して明示的に参照が行われた場合は、シリアル化時にポインタ参照マーク (R) にシリアル化されます。
$a = array();
$a[1] = 1;
$a["value"] = $a;
echo serialize($a);

5.2.

マークの後の数字を引用符で囲みます

上記の例でわかるように、オブジェクト参照 (r) とポインター参照 (R) の形式は次のとおりです。 r:; R:;

この

は何なのか、誰もが疑問に思っているはずです。このセクションでは、この質問について詳しく説明します。

这个 d80b5def5ed1be6e26d91c2709f14170 容易的说,就是所援用的对象在序列化串中第一次呈现的位置,但是这个位置不是指字符的位置,而是指对象(这里的对象是泛指一切类型的量,而不只限于对象类型)的位置。

我想群众能够还不是很明白,那么我来举例标明一下:

class ClassA {
var $int;
var $str;
var $bool;
var $obj;
var $pr;
}

$a = new ClassA();
$a->int = 1;
$a->str = "Hello";
$a->bool = false;
$a->obj = $a;
$a->pr = &$a->str;

echo serialize($a);

这个例子的结果是:

O:6:"ClassA":5:{s:3:"int";i:1;s:3:"str";s:5:"Hello";s:4:"bool";b:0;s:3:"obj";r:1;s:2:"pr";R:3;}

在这个例子中,最先序列化的对象是 ClassA 的一个对象,那么给它编号为 
1,接下来要序列化的是这个对象的多个成员,第一个被序列化的成员是 int 字段,那它的编号就为 2,接下来被序列化的成员是 
str,那它的编号就是 3,依此类推,到了 obj 成员时,它觉察该成员以前被序列化了,并且编号为 1,因而它被序列化时,就被序列化成了 
r:1; ,在接下来被序列化的是 pr 成员,它觉察该成员实践上是指向 str 成员的一个援用,而 str 成员的编号为 3,因而,pr 
就被序列化为 R:3; 了。

PHP 是如何来编号被序列化的对象的呢?实践上,PHP 
在序列化时,最先树立一个空表,然后每个被序列化的对象在被序列化之前,都须要先计算该对象的 Hash 值,然后判别该 Hash 
值能无法以前出如今该表中了,假设没有呈现,就把该 Hash 值添加到这个表的开头,前往添加成功。假设呈现了,则前往添加失败,但是在前往失败前先判别该对象能无法是一个援用(用 & 符号定义的援用),假设不是则也把 Hash 值添加到表后(虽然前往的是添加失败)。假设前往失败,则同时前往上一次呈现的位置。

在添加 Hash 值到表中之后,假设添加失败,则判别添加的是一个援用照旧一个对象,假设是援用,则前往 R 标示,假设是对象,则前往 r 标示。由于失败时,会同时前往上一次呈现的位置,因而,R 和 r 标示后面的数字,就是这个位置。

5.3.对象援用的反序列化

PHP 在反序列化处理对象援用时很有意思,假设反序列化的字符串不是 PHP 的 serialize() 自身生成的,而是人为构造或许用其它言语生成的,即便对象援用指向的不是一个对象,它也可以正确地依照对象援用所指向的数据执行反序列化。比如:

echo "<pre class="brush:php;toolbar:false">";
class StrClass {
var $a;
var $b;
}

$a = unserialize('O:8:"StrClass":2:{s:1:"a";s:5:"Hello";s:1:"b";r:2;}');

var_dump($a);
echo "
";

运转结果:
object(StrClass)#1 (2) {
["a"]=>
string(5) "Hello"
["b"]=>
string(5) "Hello"
}

群众会觉察,上面的例子反序列化后,$a->b 的值与 $a->a 的值是一样的,虽然 $a->a 
不是一个对象,而是一个字符串。因而假设群众用其它言语来完成序列化的话,不用需非要把 string 
作为标量类型来处理,即便依照对象援用来序列化拥有类似字符串内容的复合类型,用 PHP 
一样可以正确的反序列化。这样可以更节省序列化后的内容所占用的空间。

6.自定义对象序列化

6.1.PHP 4 中自定义对象序列化

PHP 4 中提供了 __sleep 和 __wakeup 这两个方法来自定义对象的序列化。不过这两个函数并不改动对象序列化的格式,影响的仅仅是被序列化字段的个数。关于它们的简介,在 PHP 手册中写的还算比拟细致。这里就不再多做简介了。

6.2.PHP 5 中自定义对象序列化

PHP 5 中添加了接口(interface)功用。PHP 5 自身提供了一个 Serializable 
接口,假设用户在自己定义的类中完成了这个接口,那么在该类的对象序列化时,就会被依照用户完成的方式去执行序列化,并且序列化后的标示不再是 
O,而改为 C。C 标示的格式如下:

C:931b1e3793557b039b98068c5e568253:"39312d935ffcf6a91fab9fca05b9db5e":49cea61ee0835c68d8f78ee6392f7ea7:{1d029f6197b5a3eb8a3fdf0a088ddf55}

其中 931b1e3793557b039b98068c5e568253 表示类名 39312d935ffcf6a91fab9fca05b9db5e 的长度,48de136c3ebc7d1ebd66e7c77ef2c6aa 表示自定义序列化数据 1d029f6197b5a3eb8a3fdf0a088ddf55 的长度,而自定义的序列化数据 1d029f6197b5a3eb8a3fdf0a088ddf55 
是完全的用户自己定义的格式,与 PHP 序列化格式可以完全没关,这局部数据由用户自己完成的序列化和反序列化接口方法来维护。

Serializable 接口中定义了 2 个方法,serialize() 和 unserialize($data),
两个方法不会被直接调用,而是在调用 PHP 序列化函数时,被自动调用。其中 serialize 函数没有参数,它的前往值就是 
1d029f6197b5a3eb8a3fdf0a088ddf55 的内容。而 unserialize($data) 有一个参数 $data,这个参数的值就是 1d029f6197b5a3eb8a3fdf0a088ddf55 
的内容。这样群众应该就明白了,实践上接口中 serialize 方法就是让用户来自己序列化对象中的内容,序列化后的内容格式,PHP 
并不关怀,PHP 只担任把它充填到 1d029f6197b5a3eb8a3fdf0a088ddf55 中,等到反序列化时,PHP 只担任取出这局部内容,然后传给用户完成的 
unserialize($data) 接口方法,让用户自己去反序列化这局部内容。

下面举个容易的例子,来标明 Serializable 接口的运用:

class MyClass implements Serializable
{
public $member;

function MyClass()
{
$this->member = 'member value';
}

public function serialize()
{
return wddx_serialize_value($this->member);
}

public function unserialize($data)
{
$this->member = wddx_deserialize($data);
}
}
$a = new MyClass();
echo serialize($a);
echo "\n";
print_r(unserialize(serialize($a)));

因而假设想用其它言语来完成 PHP 序列化中的 C 标示的话,也须要提供一种这样的机制,让用户自定义类时,可以自己在反序列化时处理 1d029f6197b5a3eb8a3fdf0a088ddf55 内容,否则,这些内容就无法被反序列化了。

7.Unicode 字符串的序列化

好了,开头再谈谈 PHP 6 中关于 Unicode 字符串序列化的疑问吧。

说真话,我不如何喜好把字符串搞成双字节 Unicode 这种编码的东西。javascript中也是用这样的字符串,因而在处理字节流的东西时,反而十分的不简约。C# 
虽然也是用这种方式来编码字符串,不过还好的是,它提供了周到的编码转换机制,并且提供这种字符串到字节流(实践上是到字节数组)的转换,所以处理起来还
算是可以。但是关于不熟识这个的人来说,转来转去就是个费事。

PHP 6 之前不时是按字节来编码字符串的,到了 PHP 6 突然冒出个 Unicode 编码的字符串来,虽然是可选的,但仍然让人觉得十分不温馨,假设配置不当,老的顺序兼容性都成疑问。

当然加了这个东西现在,许多老的与字符串相关的函数都执行了修正。序列化函数也不例外。因而,PHP 6 中添加了专门的 Unicode 字符串序列化标示 U。PHP 6 中对 Unicode 字符串的序列化格式如下:

U:d82af2074b26fcfe177e947839b5d381:"f340306611092f8aebcd456c5feed22f";

这里 d82af2074b26fcfe177e947839b5d381 是指原 Unicode String 的长度,而不是 f340306611092f8aebcd456c5feed22f 的长度,由于 f340306611092f8aebcd456c5feed22f 是阅历编码现在的字节流了。

但是尚有一点要留意,d82af2074b26fcfe177e947839b5d381 虽然是原 Unicode String 
的长度,但是也不是只它的字节数,当然也不完全是指它的字符数,确切的说是之它的字符单位数。由于 Unicode String 中采用的是 
UTF16 编码,这种编码方式运用 16 位来表示一个字符的,但是并不是一切的都是可以用 16 位表示的,因而有些字符须要两个 16 
位来表示一个字符。因而,在 UTF16 编码中,16 
位字符算作一个字符单位,一个实践的字符能够就是一个字符单位,也有能够由两个字符单位组成。因而, Unicode String 
中字符数并不总是等于字符单位数,而这里的 d82af2074b26fcfe177e947839b5d381 指的就是字符单位数,而不是字符数。

那 f340306611092f8aebcd456c5feed22f 又是怎样被编码的呢?实践上,它的编码也很容易,关于编码小于 128 的字符(但不包括 
\),依照单个字节写入,关于大于 128 的字符和 \ 字符,则转化为 16 进制编码的字符串,以 \ 
作为开头,后面四个字节区分是这个字符单位的 16 进制编码,顺序依照由高位到低位陈列,也就是第 16-13 
位所对应的16进制数字字符(abcdef 这多个字母是小写)作为第一个字节,第 12-9 位作为第二个字节,第 8-5 
位作为第三个字节,开头的第 4-1 位作为第四个字节。顺次编码下来,得到的就是 30b90ff9f9c55624ab74093eb10eef82 的内容了。

我以为关于其他言语来说,没有必要完成这种序列化方式,由于用这种方式序列化的内容,关于现在的主流 PHP 服务器来说都是不支持的,不过倒是可以完成它的反序列化,这样未来即便跟 PHP 6 执行数据交流,也可以够相互读懂了。


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。