PHP serialize進(jìn)行序列化工作的完全指南
介紹
如果你和我一樣,第一次在 PHP 中看到序列化字符串時(shí)會覺得很困惑。我當(dāng)時(shí)在做一個(gè) Laravel 項(xiàng)目,想搞清楚將任務(wù)推送到隊(duì)列時(shí)到底發(fā)生了什么。我發(fā)現(xiàn)一些數(shù)據(jù)被序列化了,但不知道為什么以及怎么工作的。不過在我花時(shí)間研究序列化后,發(fā)現(xiàn)它其實(shí)沒那么復(fù)雜。
本文會介紹什么是序列化以及工作原理。然后會說明如何使用 PHP 的內(nèi)置序列化函數(shù),讓你能在應(yīng)用中序列化和反序列化數(shù)據(jù)。最后會講如何編寫測試來確保序列化代碼正常工作。
讀完這篇文章,你應(yīng)該能理解什么是序列化,并且能放心地在項(xiàng)目中使用。
什么是序列化
序列化就是把變量、對象或數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換成字符串格式的過程。這種字符串格式能表示原始數(shù)據(jù),方便存儲或傳輸。反過來,反序列化(在 PHP 中通常叫"unserialization")就是把序列化的數(shù)據(jù)轉(zhuǎn)換回原來的形式。
序列化很重要,常用于把數(shù)據(jù)存儲到緩存、數(shù)據(jù)庫或文件中。
數(shù)據(jù)可以序列化成很多格式,比如 JSON、XML,甚至二進(jìn)制格式(比如 gRPC API 用的 Protocol Buffers)。不過這篇文章主要講 PHP 的內(nèi)置序列化函數(shù)。
舉個(gè)例子,如果你用過 Laravel,應(yīng)該注意到這個(gè)框架在把任務(wù)推送到隊(duì)列時(shí)會序列化數(shù)據(jù)。比如下面這個(gè) Laravel 中被推到隊(duì)列的待處理任務(wù)(為了好看,分了行并去掉了一些屬性):
{
"uuid": "3d05be68-8cd0-4c3a-8d05-71e86871713a",
"data": {
"commandName": "App\\Jobs\\SendOneTimePassword",
"command": "O:28:\"App\\Jobs\\SendOneTimePassword\"
:1:{s:15:\"oneTimePassword\";s:6:\"123456\";}"
}
}
在這個(gè)待處理任務(wù)的 JSON 例子中,data.command 屬性是個(gè)序列化字符串,代表一個(gè) App\Jobs\SendOneTimePassword 任務(wù)。隊(duì)列工作器拿到這個(gè)任務(wù)時(shí),會把序列化字符串反序列化,創(chuàng)建 App\Jobs\SendOneTimePassword 類的實(shí)例來處理。如果現(xiàn)在看不懂也沒關(guān)系,后面會有更多例子來解釋。
PHP 中的序列化如何工作
PHP 中可以用 serialize 和 unserialize 函數(shù)來序列化和反序列化數(shù)據(jù)。
serialize 函數(shù)接受要序列化的數(shù)據(jù),返回字符串格式。unserialize 函數(shù)接受序列化的數(shù)據(jù),返回原來的數(shù)據(jù)結(jié)構(gòu)。
看看怎么在 PHP 中序列化和反序列化不同類型的數(shù)據(jù):
序列化字符串
序列化字符串很簡單,直接傳給 serialize 函數(shù):
$serialized = serialize('Hello');
這將返回一個(gè)序列化字符串:
s:5:"Hello";
這乍看起來有點(diǎn)奇怪,但一旦你注意到模式,你會發(fā)現(xiàn)它并不像看起來那么可怕。我們的序列化數(shù)據(jù)遵循格式:data_type:string_length:string;。
所以在上面序列化字符串的情況下,s 代表字符串并表示反序列化數(shù)據(jù)時(shí)的數(shù)據(jù)類型,5 是字符串的長度。
然后我們可以將該序列化字符串傳遞給 unserialize 函數(shù)以獲取原始字符串:
$string = unserialize('s:5:"Hello";');
序列化整數(shù)和浮點(diǎn)數(shù)
我們也可以在 PHP 中序列化整數(shù)和浮點(diǎn)數(shù)。以下是序列化整數(shù)的方法:
serialize(123);
這將返回一個(gè)序列化字符串:
i:123;
你可能已經(jīng)注意到結(jié)構(gòu)與我們之前看到的序列化字符串略有不同。整數(shù)使用格式 data_type:data; 進(jìn)行序列化。注意這里我們沒有像字符串那樣的大小。在這種情況下,序列化數(shù)據(jù)的數(shù)據(jù)類型是 i 表示整數(shù)。
同樣,我們可以序列化浮點(diǎn)數(shù):
serialize(123.45);
這將返回一個(gè)序列化字符串:
d:123.45;
這個(gè)結(jié)構(gòu)類似于整數(shù)序列化,但數(shù)據(jù)類型是 d 表示雙精度浮點(diǎn)數(shù)。
序列化布爾值
我們也可以在 PHP 中序列化布爾值。例如,我們可以序列化 true:
serialize(true);
這將返回一個(gè)序列化字符串,其中 b 作為數(shù)據(jù)類型,1(表示 true)作為值:
b:1;
同樣,我們可以序列化 false:
serialize(false);
這將返回一個(gè)序列化字符串,其中 b 作為數(shù)據(jù)類型,0(表示 false)作為值:
"b:0;"
序列化數(shù)組
我們可以這樣在 PHP 中序列化數(shù)組:
serialize([1,2,3]);
這將返回一個(gè)序列化字符串:
a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}
現(xiàn)在,你可能已經(jīng)注意到這比我們已經(jīng)看過的其他序列化數(shù)據(jù)要復(fù)雜一些。讓我們分解一下。
字符串具有 data_type:size:{key_data_type:key_data;value_data_type:value_data;...} 的結(jié)構(gòu)。在這種情況下,data_type 是 a 表示數(shù)組,size 是 3,因?yàn)閿?shù)組有 3 個(gè)元素。
如果我們?nèi)缓蟛榭?{ } 內(nèi)的數(shù)據(jù),我們可以看到鍵由 i 表示整數(shù),值也由 i 表示整數(shù)。通過將它們分成新行來可視化結(jié)構(gòu)可能會有所幫助:
i:0;i:1;
i:1;i:2;
i:2;i:3;
作為另一個(gè)例子,讓我們看看序列化的字符串?dāng)?shù)組可能是什么樣子。我們可以序列化以下數(shù)組:
serialize(['a','b','c']);
這將返回一個(gè)序列化字符串:
a:3:{i:0;s:1:"a";i:1;s:1:"b";i:2;s:1:"c";}
正如我們在上面的序列化字符串中看到的,鍵仍然由 i 表示,而值由 s 表示字符串。為了幫助可視化結(jié)構(gòu),我們可以將數(shù)據(jù)分成新行:
i:0;s:1:"a";
i:1;s:1:"b";
i:2;s:1:"c";
同樣,我們也可以序列化關(guān)聯(lián)數(shù)組:
serialize(['a' => 'A', 'b' => 'B', 'c' => 'C']);
這將返回一個(gè)序列化字符串:
a:3:{s:1:"a";s:1:"A";s:1:"b";s:1:"B";s:1:"c";s:1:"C";}
正如我們所看到的,結(jié)構(gòu)與我們已經(jīng)看過的序列化數(shù)組非常相似。但是,在這種情況下,鍵由 s 表示字符串。為了幫助可視化結(jié)構(gòu),我們可以將數(shù)據(jù)分成新行:
s:1:"a";s:1:"A";
s:1:"b";s:1:"B";
s:1:"c";s:1:"C";
序列化枚舉
我們也可以在 PHP 中序列化枚舉。作為一個(gè)基本示例,假設(shè)我們有以下表示博客文章狀態(tài)的枚舉:
namespace App\Enums;
enum PostStatus: string
{
case Published = 'published';
case Draft = 'draft';
case Pending = 'in_review';
}
讓我們想象然后創(chuàng)建此枚舉的新實(shí)例并像這樣序列化它:
serialize(PostStatus::Published);
這將返回一個(gè)序列化字符串:
E:30:"App\Enums\PostStatus:Published";
序列化枚舉的結(jié)構(gòu)是 data_type:size:"enum_type:enum_value";。在這種情況下,數(shù)據(jù)類型由 E 表示,大小是 30,因?yàn)轭惷?App\Enums\PostStatus,枚舉值是 Published。
序列化對象
到目前為止,我們已經(jīng)介紹了序列化如何適用于基本數(shù)據(jù)類型,如字符串、整數(shù)、浮點(diǎn)數(shù)、布爾值、數(shù)組和枚舉。但是對象呢?
默認(rèn)情況下,除了少數(shù)內(nèi)置 PHP 類之外,所有對象都是可序列化的。
為了解釋對象序列化的工作原理,讓我們以一個(gè)基本的 App\User 類為例,它包含三個(gè)公共屬性:
namespace App;
class User
{
public function __construct(
public string $name,
public string $email,
public string $apiToken,
) { }
}
我們將創(chuàng)建此類的新實(shí)例并序列化它:
$user = new User(
name: 'Ash Allen',
email: 'mail@ashallendesign.co.uk',
apiToken: 'secret',
);
serialize($user);
這將返回一個(gè)序列化字符串:
O:8:"App\User":3:{s:4:"name";s:9:"Ash Allen";s:5:"email";s:
25:"mail@ashallendesign.co.uk";s:8:"apiToken";s:6:"secret";}
讓我們分解序列化對象的結(jié)構(gòu)。我們有以下結(jié)構(gòu):
data_type:class_name_size:class_name:property_count:{
property_name_type:property_name_size:property_name;
property_value_type:property_value_size:property_value;
...
}
因此,從這個(gè)結(jié)構(gòu)中,我們可以看到數(shù)據(jù)類型是 O 表示對象,類名大小是 8,類名是 App\User,屬性計(jì)數(shù)是 3,因?yàn)閷ο笥?3 個(gè)屬性。然后我們可以看到 { } 內(nèi)的每個(gè)序列化屬性。
然后我們可以將此序列化字符串傳遞給 unserialize 函數(shù)以獲取原始對象:
$serialized = 'O:8:"App\User":3:{s:4:"name";s:9:"Ash Allen'
.'";s:5:"email";s:25:"mail@ashallendesign.co.uk";s:8:'
.'"apiToken";s:6:"secret";}';
$user = unserialize($serialized);
這將返回 App\User 類的實(shí)例,每個(gè)屬性都像原始對象一樣設(shè)置。
屬性可見性
序列化對象時(shí),屬性的可見性很重要,因?yàn)樗鼤绊懛祷氐淖址?/p>
讓我們更新我們的 App\User 類以具有公共、受保護(hù)和私有屬性:
namespace App;
class User
{
public function __construct(
public string $name,
protected string $email,
private string $apiToken,
) { }
}
然后我們將創(chuàng)建此類的新實(shí)例并序列化它:
$user = new User(
name: 'Ash Allen',
email: 'mail@ashallendesign.co.uk',
apiToken: 'secret',
);
serialize($user);
這將返回一個(gè)序列化字符串:
O:8:"App\User":3:{s:4:"name";s:9:"Ash Allen";s:8:
"\0*\0email";s:25:"mail@ashallendesign.co.uk";s:18:
"\0App\User\0apiToken";s:6:"secret";}
字符串格式與我們之前的序列化對象非常相似。但是,email 和 apiToken 屬性的名稱略有不同。
當(dāng) PHP 序列化對象時(shí),它將為屬性名添加前綴以指示屬性的可見性。受保護(hù)的屬性由 * 前綴指示,私有屬性由類名前綴指示。所以我們可以看到,我們有 \0*\0email 和 \0App\User\0apiToken(\0 表示空字節(jié)),而不是 email 和 apiToken。
讓我們將序列化字符串分成新行以幫助可視化結(jié)構(gòu):
s:4:"name";s:9:"Ash Allen";
s:8:"\0*\0email";s:25:mail@ashallendesign.co.uk"
s:18:"\0App\User\0apiToken";
這意味著通過查看序列化對象,我們可以確定屬性的可見性。
序列化包含其他對象的對象
有時(shí)你可能需要序列化包含另一個(gè)對象的對象。我們將快速看一下包含另一個(gè)對象的序列化對象可能是什么樣子。
假設(shè)我們有一個(gè)簡單的 App\ValueObjects\Address 類:
namespace App\ValueObjects;
class Address
{
public function __construct(
public int $number,
public string $postalCode,
) { }
}
然后我們假設(shè)我們的 App\User 類有一個(gè) App\ValueObjects\Address 對象作為屬性。我們可能想要創(chuàng)建一個(gè)新對象并序列化它:
$user = new User(
name: 'Ash Allen',
email: 'mail@ashallendesign.co.uk',
apiToken: 'secret',
address: new Address('18', 'SW1A 2AA'),
);
serialize($user);
這將導(dǎo)致如下序列化字符串:
O:8:"App\User":4:{s:4:"name";s:9:"Ash Allen";s:5:"email";
s:25:"mail@ashallendesign.co.uk";s:8:"apiToken";s:6:
"secret";s:7:"address";O:24:"App\ValueObjects\Address":2:
{s:6:"number";i:18;s:10:"postalCode";s:8:"SW1A 2AA";}}
讓我們將此對象的內(nèi)容分解到單獨(dú)的行上:
s:4:"name";s:9:"Ash Allen";
s:5:"email";s:25:"mail@ashallendesign.co.uk";
s:8:"apiToken";s:6:"secret";
s:7:"address";O:24:"App\ValueObjects\Address":2:{
s:6:"number";i:18;
s:10:"postalCode";s:8:"SW1A 2AA";
}
正如我們在這里看到的,App\ValueObjects\Address 對象只是作為 App\User 對象的屬性序列化。
反序列化時(shí)的錯(cuò)誤處理
處理嘗試反序列化無效數(shù)據(jù)時(shí)可能發(fā)生的任何錯(cuò)誤很重要。根據(jù)你嘗試反序列化的無效數(shù)據(jù),PHP 8.3 將發(fā)出 E_WARNING 或拋出 \Exception 或 \Error。
例如,讓我們看看這個(gè)無效的序列化字符串,它對字符串 hello 的長度為 10 而不是預(yù)期的 5:
unserialize('s:10:"hello";');
如果我們在 PHP 8.3 中運(yùn)行此代碼,將發(fā)出 E_WARNING,錯(cuò)誤消息如下:
Warning: unserialize(): Error at offset 2 of 13 bytes in
/www/serialization.php on line 3
為了處理警告以便我們可以在代碼中捕獲和處理它們,我們可以使用 set_error_handler 函數(shù)設(shè)置自定義錯(cuò)誤處理程序。這將允許我們捕獲警告并將它們作為異常拋出。
為此,我們首先創(chuàng)建一個(gè)新的 App\Services\Serializer 類,如下所示:
declare(strict_types=1);
namespace App\Services;
final readonly class Serializer
{
public function unserialize(string $serialized): mixed
{
try {
set_error_handler(static function (
$severity, $message, $file, $line
) {
throw new \ErrorException(
$message, 0, $severity, $file, $line
);
});
$result = unserialize($serialized);
} finally {
restore_error_handler();
}
return $result;
}
}
在此類中,我們添加了一個(gè)接受序列化字符串的 unserialize 方法。然后我們覆蓋錯(cuò)誤處理程序,以便我們可以捕獲任何警告并將它們作為異常拋出。然后我們嘗試反序列化數(shù)據(jù)。如果發(fā)出警告,它將作為異常拋出。然后我們在 finally 塊內(nèi)將錯(cuò)誤處理程序恢復(fù)到其原始狀態(tài),無論反序列化是否成功都會運(yùn)行。假設(shè)成功,我們?nèi)缓蠓祷胤葱蛄谢臄?shù)據(jù)。
然后我們可以使用此類來反序列化數(shù)據(jù):
use App\Services\Serializer;
$result = (new Serializer())->unserialize(
serialized: 's:10:"hello";'
);
運(yùn)行上述代碼將導(dǎo)致拋出 \ErrorException,消息如下:
unserialize(): Error at offset 2 of 13 bytes
或者我們可以運(yùn)行以下代碼:
use App\Services\Serializer;
$result = (new Serializer())->unserialize(
serialized: 's:5:"hello";'
);
這將導(dǎo)致返回字符串 hello。
目前有一個(gè) RFC部分實(shí)現(xiàn),旨在改進(jìn)反序列化數(shù)據(jù)時(shí)的錯(cuò)誤處理。RFC 包含一個(gè)提案,從 PHP 9.0 開始,改變 unserialize 的行為,使其拋出 \UnserializationFailedException 而不是發(fā)出 E_WARNING。因此,如果實(shí)現(xiàn)了這一點(diǎn),我們不需要覆蓋錯(cuò)誤處理程序來捕獲警告并將它們作為異常拋出,就像我們上面所做的那樣。
在 PHP 中定義序列化邏輯
正如我們在上面已經(jīng)看到的,PHP 默認(rèn)提供序列化和反序列化對象的能力。
但是,有時(shí)你可能想要為對象定義自定義序列化邏輯。這可能有幾個(gè)原因,例如在序列化之前加密敏感數(shù)據(jù),或者你可能需要在反序列化對象時(shí)執(zhí)行一些額外的邏輯。
值得慶幸的是,PHP 提供了兩個(gè)魔術(shù)方法,你可以使用它們來定義如何序列化和反序列化對象:__serialize 和 __unserialize。
為了了解這可能如何工作,讓我們看一個(gè)例子。堅(jiān)持我們之前的 App\User 類,假設(shè)我們想要在序列化對象之前加密 apiToken 屬性,并在反序列化對象時(shí)解密它。這可能是因?yàn)槲覀儗⑿蛄谢瘮?shù)據(jù)存儲在緩存或隊(duì)列中,所以我們想要確保數(shù)據(jù)在受到威脅時(shí)是安全的。
為了本文的目的,我們將假設(shè)我們有兩個(gè)可以調(diào)用來加密和解密數(shù)據(jù)的函數(shù):encrypt 和 decrypt。我們現(xiàn)在不需要擔(dān)心這些函數(shù)的實(shí)現(xiàn),我們只是假設(shè)它們存在。如果你們中的任何人是 Laravel 開發(fā)者,你們可能會認(rèn)識這些函數(shù),因?yàn)樗鼈兌茧S Laravel 一起提供。
讓我們更新我們的 App\User 類以包含 __serialize 和 __unserialize 方法,然后討論正在做什么:
declare(strict_types=1);
namespace App;
class User
{
public function __construct(
public string $name,
public string $email,
public string $apiToken,
) { }
public function __serialize(): array
{
return [
'name' => $this->name,
'email' => $this->email,
'apiToken' => encrypt($this->apiToken),
];
}
public function __unserialize(array $data): void
{
$this->name = $data['name'];
$this->email = $data['email'];
$this->apiToken = decrypt($data['apiToken']);
}
}
在 __serialize 方法中,我們返回要序列化的屬性數(shù)組。我們在返回之前加密 apiToken 屬性。這意味著當(dāng)我們對對象調(diào)用 serialize 時(shí),apiToken 屬性將被加密。
讓我們創(chuàng)建 App\User 類的新實(shí)例并序列化它:
$user = new User(
name: 'Ash Allen',
email: 'mail@ashallendesign.co.uk',
apiToken: 'secret',
);
$serialized = serialize($user);
序列化字符串可能如下所示(為簡潔起見縮短了加密字符串):
O:8:"App\User":3:{s:4:"name";s:9:"Ash Allen";s:5:"email";s:
25:"mail@ashallendesign.co.uk";s:8:"apiToken";s:200:
"eyJpdiI6Ikx0N3BDQwYzcwMzE1NGQy...sdfsfsfdssInRhZyI6IiJ9";}
正如我們所看到的,apiToken 屬性現(xiàn)在已加密,沒有加密密鑰就無法解密數(shù)據(jù)。
現(xiàn)在,如果我們想從序列化字符串創(chuàng)建 App\User 類的實(shí)例,我們可以對字符串調(diào)用 unserialize,將調(diào)用 __unserialize 方法。此 __unserialize 方法接受序列化數(shù)據(jù)的數(shù)組,因此我們可以分配每個(gè)屬性并解密 apiToken 屬性。
測試你的序列化代碼
就像應(yīng)用程序的任何其他部分一樣,如果你正在自定義對象的序列化和反序列化方式,你可能會想要為序列化邏輯編寫測試。這是確保你的序列化代碼按預(yù)期工作并且你可以捕獲任何錯(cuò)誤的好方法。
例如,讓我們看看我們剛剛看過的前面的例子。如果我們意外地從 apiToken 屬性的 __unserialize() 函數(shù)中刪除了 decrypt() 函數(shù)調(diào)用,會發(fā)生什么?這將導(dǎo)致我們擁有一個(gè)具有加密令牌而不是我們期望的原始未加密值的對象。
你的測試可以根據(jù)你的喜好進(jìn)行深入和嚴(yán)格。讓我們看看我們可以編寫的一個(gè)簡單測試,以確保 App\User 對象可以被序列化然后反序列化:
declare(strict_types=1);
namespace Tests\Feature\User;
use App\User;
use Illuminate\Foundation\Testing\TestCase;
use PHPUnit\Framework\Attributes\Test;
final class SerializeTest extends TestCase
{
// ...
#[Test]
public function serialize_and_unserialize_works(): void
{
$user = new User(
name: 'Ash Allen',
email: 'mail@ashallendesign.co.uk',
apiToken: '1234567890',
);
$serialized = serialize($user);
$unserializedUser = unserialize($serialized);
// 斷言我們剛剛構(gòu)建的用戶與我們最初序列化的用戶相同。
$this->assertEquals($user, $unserializedUser);
}
}
在上面的測試中,我們創(chuàng)建了 App\User 的實(shí)例,序列化它,然后反序列化它。然后我們斷言我們剛剛構(gòu)建的用戶與我們最初序列化的用戶相同。這是一個(gè)簡單的測試,可以讓我們確信我們的序列化代碼按預(yù)期工作。
但是,如果我們刪除 encrypt 和 decrypt 函數(shù)調(diào)用,測試仍然會通過,即使我們可能沒有像預(yù)期的那樣加密和解密 apiToken 屬性。
如果你更喜歡對測試更嚴(yán)格一些,你可以編寫兩個(gè)更多的測試來確保 apiToken 屬性按預(yù)期被加密和解密。
我們將編寫測試,就像它們是 Laravel 應(yīng)用程序的一部分,并且 encrypt 和 decrypt 函數(shù)只是使用 encrypter 鍵從服務(wù)容器解析 Illuminate\Contracts\Encryption\Encrypter 接口的實(shí)例。但是如果你不熟悉 Laravel,這并不重要。你只需要知道我們正在模擬 encrypt 和 decrypt 函數(shù)調(diào)用的底層類,這樣我們就可以在測試中硬編碼預(yù)期的加密值并對其進(jìn)行斷言。
我們要編寫的第一個(gè)測試是確保在序列化用戶對象時(shí) apiToken 屬性被加密:
declare(strict_types=1);
namespace Tests\Feature\User;
use App\User;
use Illuminate\Foundation\Testing\TestCase;
use Mockery\MockInterface;
use PHPUnit\Framework\Attributes\Test;
final class SerializeTest extends TestCase
{
// ...
#[Test]
public function user_object_can_be_serialized(): void
{
// 模擬加密器,這樣我們就可以嚴(yán)格測試用戶對象的序列化。
$this->mock('encrypter', function ($mock): void {
$mock->shouldReceive('encrypt')
->once()
->withArgs(['1234567890', true])
->andReturn('encrypted');
});
$user = new User(
name: 'Ash Allen',
email: 'mail@ashallendesign.co.uk',
apiToken: '1234567890',
);
$serialized = serialize($user);
$expectedString = 'O:8:"App\User":3:{s:4:"name";s:'.
'9:"Ash Allen";s:5:"email";s:25:"'.
'mail@ashallendesign.co.uk";s:8:"apiToken";s:'.
'9:"encrypted";}';
// 斷言序列化字符串正是我們期望的。
$this->assertSame(
expected: $expectedString,
actual: $serialized,
);
}
}
在測試中,我們首先模擬加密器,這樣我們就可以在測試中硬編碼預(yù)期的加密值。在這種情況下,我們期望 apiToken 屬性的值為 1234567890,當(dāng)加密它時(shí),我們將返回字符串 encrypted。然后我們創(chuàng)建 App\User 類的新實(shí)例并序列化它。然后我們斷言序列化字符串正是我們期望的。
然后我們可以編寫另一個(gè)測試來確保在反序列化用戶對象時(shí) apiToken 屬性被解密:
declare(strict_types=1);
namespace Tests\Feature\User;
use App\User;
use Illuminate\Foundation\Testing\TestCase;
use Mockery\MockInterface;
use PHPUnit\Framework\Attributes\Test;
final class SerializeTest extends TestCase
{
// ...
#[Test]
public function user_string_can_be_unserialized(): void
{
// 模擬加密器,這樣我們就可以嚴(yán)格測試用戶對象的序列化。
$this->mock('encrypter', function ($mock): void {
$mock->shouldReceive('decrypt')
->once()
->withArgs(['encrypted', true])
->andReturn('1234567890');
});
$serialized = 'O:8:"App\User":3:{s:4:"name";s:9:"'.
'Ash Allen";s:5:"email";s:25:"'.
'mail@ashallendesign.co.uk";s:8:"apiToken";s:'.
'9:"encrypted";}';
$user = unserialize($serialized);
$this->assertInstanceOf(User::class, $user);
$this->assertSame('Ash Allen', $user->name);
$this->assertSame(
'mail@ashallendesign.co.uk',
$user->email
);
$this->assertSame('1234567890', $user->apiToken);
}
}
在上面的測試中,我們模擬了加密器,這樣我們就可以在測試中硬編碼預(yù)期的解密值。然后我們反序列化序列化字符串并斷言用戶對象是 App\User 的實(shí)例,并且屬性符合我們的預(yù)期。
結(jié)論
在本文中,我們了解了什么是序列化以及它是如何工作的。我們探索了如何使用 PHP 的內(nèi)置序列化函數(shù)在 PHP 應(yīng)用程序中序列化和反序列化數(shù)據(jù)。我們還討論了如何編寫測試來確保你的序列化代碼按預(yù)期工作。
到此這篇關(guān)于PHP serialize進(jìn)行序列化工作的完全指南的文章就介紹到這了,更多相關(guān)PHP serialize序列化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
PHP實(shí)現(xiàn)數(shù)字補(bǔ)零功能的2個(gè)函數(shù)介紹
這篇文章主要介紹了PHP實(shí)現(xiàn)數(shù)字補(bǔ)零功能的2個(gè)函數(shù)介紹,需要的朋友可以參考下2014-05-05
PHP中的閉包function()?use()?{}使用場景和技巧
由于存在函數(shù)內(nèi)部不能訪問全局作用的,所以就需要一種可以引入上一級作用域的語法結(jié)構(gòu),可以通過use使用函數(shù)聲明時(shí)所在作用域的變量的值。php的閉包可能不常用,但是在某些場合之下還是可以考慮用php的閉包來實(shí)現(xiàn)某些功能的。2022-12-12
php讀取csv數(shù)據(jù)保存到數(shù)組的方法
這篇文章主要介紹了php讀取csv數(shù)據(jù)保存到數(shù)組的方法,通過封裝的類文件實(shí)現(xiàn)這一功能,是對csv文件操作的實(shí)用技巧,需要的朋友可以參考下2015-01-01
php簡單解析mysqli查詢結(jié)果的方法(2種方法)
這篇文章主要介紹了php簡單解析mysqli查詢結(jié)果的方法,結(jié)合實(shí)例形式簡單列舉了查詢結(jié)果存入對象與數(shù)組的兩種實(shí)現(xiàn)方法,需要的朋友可以參考下2016-06-06

