PHP Conference Japan 2024

unserialize

(PHP 4, PHP 5, PHP 7, PHP 8)

unserialize 从存储的表示形式创建 PHP 值

描述

unserialize(字符串 $data, 数组 $options = []): 混合

unserialize() 接收单个序列化变量并将其转换回 PHP 值。

警告

无论 options 值的 allowed_classes 如何,都不要将不受信任的用户输入传递给 unserialize()。由于对象实例化和自动加载,反序列化可能导致代码被加载和执行,恶意用户可能利用这一点。如果您需要将序列化数据传递给用户,请使用安全、标准的数据交换格式,例如 JSON(通过 json_decode()json_encode())。

如果您需要反序列化外部存储的序列化数据,请考虑使用 hash_hmac() 进行数据验证。确保数据仅由您修改。

参数

data

序列化的字符串。

如果被反序列化的变量是对象,在成功重建对象后,PHP 将自动尝试调用 __unserialize()__wakeup() 方法(如果存在)。

注意: unserialize_callback_func 指令

当反序列化未定义的类时,将调用 unserialize_callback_func 指令中指定的回调。如果没有指定回调,则对象将被实例化为 __PHP_Incomplete_Class

options

作为关联数组提供给 unserialize() 的任何选项。

有效选项
名称 类型 描述
allowed_classes 混合 要么是应该接受的类名 数组false 表示不接受任何类,或者 true 表示接受所有类。如果定义了此选项并且 unserialize() 遇到一个不应被接受的类的对象,则该对象将被实例化为 __PHP_Incomplete_Class 省略此选项与将其定义为 true 相同:PHP 将尝试实例化任何类的对象。
max_depth 整数 反序列化期间允许的结构的最大深度,旨在防止堆栈溢出。默认深度限制为 4096,可以通过将 max_depth 设置为 0 来禁用。

返回值

返回转换后的值,可以是 布尔值整数浮点数字符串数组对象

如果传递的字符串不可反序列化,则返回 false 并发出 E_WARNING

错误/异常

对象在其反序列化处理程序中可能会抛出 Throwable

变更日志

版本 描述
8.3.0 现在在输入字符串具有未消耗的数据时发出 E_WARNING
8.3.0 现在在传递的字符串不可反序列化时发出 E_WARNING;以前发出的是 E_NOTICE
7.4.0 添加了 optionsmax_depth 元素来设置反序列化期间允许的结构的最大深度。
7.1.0 optionsallowed_classes 元素现在具有严格的类型,即,如果给定的不是 数组布尔值,则 unserialize() 返回 false 并发出 E_WARNING

示例

示例 #1 unserialize() 示例

<?php
// 在这里,我们使用 unserialize() 将会话数据从数据库中选择的字符串加载到
// $session_data 数组中。
// 此示例补充了使用 serialize() 描述的示例。

$conn = odbc_connect("webdb", "php", "chicken");
$stmt = odbc_prepare($conn, "SELECT data FROM sessions WHERE id = ?");
$sqldata = array($_SERVER['PHP_AUTH_USER']);
if (!
odbc_execute($stmt, $sqldata) || !odbc_fetch_into($stmt, $tmp)) {
// 如果执行或获取失败,则初始化为空数组
$session_data = array();
} else {
// 我们现在应该在 $tmp[0] 中拥有序列化数据。
$session_data = unserialize($tmp[0]);
if (!
is_array($session_data)) {
// 出现某些错误,初始化为空数组
$session_data = array();
}
}
?>

示例 #2 unserialize_callback_func 示例

<?php
$serialized_object
='O:1:"a":1:{s:5:"value";s:3:"100";}';

ini_set('unserialize_callback_func', 'mycallback'); // 设置您的回调函数

function mycallback($classname)
{
// 只需包含一个包含您的类定义的文件
// 您获得 $classname 来确定需要哪个类定义
}
?>

备注

警告

在发生错误以及反序列化序列化的 false 值时,都会返回 false。可以通过将 dataserialize(false) 进行比较或通过捕获发出的 E_NOTICE 来捕获此特殊情况。

参见

添加注释

用户贡献的注释 23条注释

me+phpnet at unreal4u dot com
7年前
关于`$options`数组的一些提醒,可以为某些人节省时间

假设您想安全起见,并且不允许反序列化任何对象……我的第一个想法是执行以下操作

<?php
$lol
= unserialize($string, false);
// 这将生成:
// Warning: unserialize() expects parameter 2 to be array, boolean given
?>

正确的做法如下
<?php
$lol
= unserialize($string, ['allowed_classes' => false]);
?>

希望对某些人有所帮助!
karsten at dambekalns dot de
4年前
请记住,`allowed_classes`不使用继承,即不允许接口,子类也不会通过检查。参见 https://3v4l.org/tdHfl
ErnestV
11年前
只是一个注释 - 如果序列化的字符串包含对无法实例化的类的引用(例如,是抽象的),PHP 将立即以致命错误终止。如果 `unserialize()` 语句之前带有“@”以避免日志中充斥警告或通知,则将完全无法知道脚本停止工作的原因。这让我花了几个小时……
daniel at fourstaples dot com
14年前
这是一个简单的函数,用于获取序列化字符串的类(即,如果反序列化,将返回的对象类型)

<?php
function get_serial_class($serial) {
$types = array('s' => 'string', 'a' => 'array', 'b' => 'bool', 'i' => 'int', 'd' => 'float', 'N;' => 'NULL');

$parts = explode(':', $serial, 4);
return isset(
$types[$parts[0]]) ? $types[$parts[0]] : trim($parts[2], '"');
}
?>

当我将序列化的对象保存到cookie时,我使用它来确保当我反序列化它时它是正确的类型。

类型名称与使用 `var_dump()` 时看到的格式/大小写相同。
hadley8899 at gmail dot com
4年前
对于收到此错误的人

PHP Notice: unserialize(): Error at offset 191 of 285 bytes in ...

并且正在从数据库获取数据,请确保数据库设置为正确的编码,我的数据库设置为 latin1_swedish_ci,所有数据看起来都很好,事实上,当我将其复制到在线反序列化工具时,它工作正常。我将排序规则更改为 utf8mb4_unicode_ci,一切正常。
bjd
7年前
关于此处利用PHP7反序列化的讨论: https://media.ccc.de/v/33c3-7858-exploiting_php7_unserialize
chris at pollett dot org
9年前
当您序列化来自特定命名空间的类的对象时,命名空间将作为序列化的一部分被记录。如果您决定更改此命名空间的名称,则可能难以读取旧的序列化对象。例如,假设您已序列化类型为 foo\A 的对象,您将项目的命名空间更改为 goo,但否则保持 A 的类定义不变。您希望能够将对象反序列化为 goo\A,而反序列化只会创建一个部分对象。为了解决在类定义中没有嵌套对象的情况,您可以使用以下简单的重命名函数
/**
* 用于更改序列化 php 对象的命名空间(假设没有
* 嵌套子对象)
*
* @param string $class_name 带命名空间的新全限定名
* @param string $object_string 序列化的对象
*
* @return string 带新名称的序列化对象
*/
function renameSerializedObject($class_name, $object_string)
{
/* 对象名称长度的位数需要
小于 12 位数(可能更像是 4 位数)才能使其工作。
*/
$name_length = intval(substr($object_string, 2, 14));
$name_space_info_length = strlen("O:".$name_length.":") +
$name_length + 2; // 2 for quotes;
$object_string = 'O:' .
strlen($class_name) . ':"'. $class_name.'"' .
substr($object_string, $name_space_info_length);
return $object_string;
}
Ray.Paseur often uses Gmail
11年前
在类和对象文档中,有以下内容:为了能够反序列化()一个对象,需要定义该对象的类。

在PHP 5.3之前,这不是问题。但在PHP 5.3之后,由SimpleXML_Load_String()创建的对象无法序列化。尝试这样做会导致运行时错误,引发异常。如果您将此类对象存储在$_SESSION中,您将收到一个执行后错误,提示

Fatal error: Uncaught exception 'Exception' with message 'Serialization of 'SimpleXMLElement' is not allowed' in [no active file]:0 Stack trace: #0 {main} thrown in [no active file] on line 0

会话的全部内容将丢失。希望这能为某些人节省一些时间!

<?php // RAY_temp_ser.php
error_reporting(E_ALL);
session_start();
var_dump($_SESSION);
$_SESSION['hello'] = 'World';
var_dump($_SESSION);

// 用于测试数据的XML字符串
$xml = '<?xml version="1.0"?>
<families>
<parent>
<child index="1" value="Category 1">Child One</child>
</parent>
</families>'
;

// 创建对象(给出SimpleXMLElement)
$obj = SimpleXML_Load_String($xml);

// 将对象存储在会话中
$_SESSION['obj'] = $obj;
chris AT cmbuckley DOT co DOT uk
16年前
如注释中所述,在发生错误以及布尔值 false 的情况下,unserialize 返回 false。这是第一个提到的解决方案,不使用错误处理

<?php
function isSerialized($str) {
return (
$str == serialize(false) || @unserialize($str) !== false);
}

var_dump(isSerialized('s:6:"foobar";')); // bool(true)
var_dump(isSerialized('foobar')); // bool(false)
var_dump(isSerialized('b:0;')); // bool(true)
?>
m.m.j.kronenburg
8年前
您可以使用以下代码在PHP 5.3及更高版本中使用php 7反序列化函数。这添加了$option参数。

<?php

命名空间 {

/**
* PHP 5.3 及以上版本的 PHP 7 unserialize 函数。
* 添加了 $option 参数 (allowed_classes)。
* 详情请参考 php unserialize 手册。
**/
函数 php7_unserialize($str, $options = array())
{
if(
version_compare(PHP_VERSION, '7.0.0', '>='))
{ return
unserialize($str, $options); }

$allowed_classes = isset($options['allowed_classes']) ?
$options['allowed_classes'] : true;
if(
is_array($allowed_classes) || !$allowed_classes)
{
$str = preg_replace_callback(
'/(?=^|:)(O|C):\d+:"([^"]*)":(\d+):{/',
function(
$matches) use ($allowed_classes)
{
if(
is_array($allowed_classes) &&
in_array($matches[2], $allowed_classes))
{ return
$matches[0]; }
else
{
return
$matches[1].':22:"__PHP_Incomplete_Class":'.
(
$matches[3] + 1).
':{s:27:"__PHP_Incomplete_Class_Name";'.
serialize($matches[2]);
}
},
$str
);
}
unset(
$allowed_classes);
return
unserialize($str);
}

}
// 命名空间

命名空间 my_name_space
{
/**
* 在您的命名空间中使用新的 php7 unserialize 函数,无需
* 将所有 unserialize(...) 函数调用重命名为
* php7_unserialize(...).
**/
函数 unserialize($str, $options = array())
{ return
php7_unserialize($str, $options); }
}

?>
arbie samong
15 年前
__PHP_Incomplete_Class 对象详解

1. 首先注意输出结果。一个简单的例子

__PHP_Incomplete_Class 对象 (
[__PHP_Incomplete_Class_Name] => SomeObject1
[obj1property1] => somevalue1 [obj1property2] => __PHP_Incomplete_Class 对象 ( [__PHP_Incomplete_Class_Name] => SomeObject2 [obj2property1] => somevalue1 [obj2property2] => 数组 (
['key1'] => somevalue3, ['key2'] => somevalue4 ) ) )

2. 我们分析并分解它。
__PHP_Incomplete_Class 对象告诉你存在一个需要声明的对象。
__PHP_Incomplete_Class_Name 只是告诉你预期的类名。目前它只是一个属性。

所以我们有
a) 一个未知对象,其类名为 SomeObject1(第一个类)
b) 它有两个属性,分别是 obj1property1 和 obj2property2
c) obj2property2 本身是一个类名为 SomeObject2 的对象(第二个类)
d) SomeObject2 有两个属性,obj2property1 和 obj2property2
e) obj2property2 是一个包含两个元素的数组

3. 现在我们了解了结构,我们将根据它创建类定义。我们现在只创建属性,方法不是必需的。

<?php
SomeObject1 {
public
$obj1property1;
public
$obj1property2;
}
SomeObject2 {
public
$obj2property1;
public
$obj2property2;
}
?>

4. 使其能够访问您的脚本,它将解决 __PHP_Incomplete_Class 对象问题(就输出而言)。现在你将拥有

SomeObject1 ( [obj1property1] => somevalue1 [obj1property2] => SomeObject2 ( [obj2property1] => somevalue1 [obj2property2] => 数组 ( ['key1'] => somevalue3, ['key2'] => somevalue4 ) ) )

你会注意到,__PHP_Incomplete_Class 对象消失了,取而代之的是类名。属性 __PHP_Incomplete_Class_Name 也被删除了。

5. 至于数组属性 obj2property2,我们可以直接访问它,并假设它是一个数组并循环遍历它

<?php

// 这将是 SomeObject1
$data = unserialize($serialized_data);

// 这将是 SomeObject2
$data2 = $data->obj1property2();

foreach(
$data2->obj2property2 as $key => $value):
print
$key.' : '. $value .'<br>';
endforeach;

?>

输出
key1 : somevalue3
key2 : somevalue4

就是这样。您可以为给定的属性添加更多方法到类声明中,前提是您保留原始输出作为数据类型的基础。
BenBE at omorphia dot de
17 年前
当尝试序列化或反序列化递归数组或其他链接数据时,您可能会发现未公开的 R 数据类型非常有用。

如果您想要一个与使用以下代码生成的数组类似的数组:
<?
$a = array();
$a[0] =& $a;
?>
序列化后,您可以使用类似这样的字符串存储它
<?
$a = unserialize("a:1:{i:0;R:1;}");
?>

这两个来源都将使 $a 保持一个在索引 0 中自引用的数组。

R 的参数是从 1 开始的序列化字符串创建的子变量的索引。
chris at colourlovers dot com
13 年前
任何人在序列化存储在其内部的 SimpleXMLElement 对象的数据时遇到问题,请查看以下内容

这将遍历 $data,查找任何是 SimpleXMLElement 实例的子项,并将对它们运行 ->asXML(),将其转换为字符串并使其可序列化。其他数据将保持不变。

<?php
函数 exportNestedSimpleXML($data) {
if (
is_scalar($data) === false) {
foreach (
$data as $k => $v) {
if (
$v instanceof SimpleXMLElement) {
$v = str_replace("&#13;","\r",$v->asXML());
} else {
$v = exportNestedSimpleXML($v);
}

if (
is_array($data)) {
$data[$k] = $v;
} else if (
is_object($data)) {
$data->$k = $v;
}
}
}

return
$data;
}

$data = array (
"baz" => array (
"foo" => new stdClass(),
"int" => 123,
"str" => "asdf",
"bar" => new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><foo>bar</foo>'),
)
);

var_dump($data);
/*array(1) {
["baz"]=>
array(4) {
["foo"]=>
object(stdClass)#3 (0) {
}
["int"]=>
int(123)
["str"]=>
string(4) "asdf"
["bar"]=>
object(SimpleXMLElement)#4 (1) {
[0]=>
string(3) "bar"
}
}
}*/

var_dump(exportNestedSimpleXML($data));
/*array(1) {
["baz"]=>
array(4) {
["foo"]=>
object(stdClass)#3 (0) {
}
["int"]=>
int(123)
["str"]=>
string(4) "asdf"
["bar"]=>
string(54) "<?xml version="1.0" encoding="UTF-8"?>
<foo>bar</foo>
"
}
}
*/
?>
double at dumpit dot com
17 年前
这个小程序会检查序列化字符串是否格式正确。

PHP < 6,因为我听说在这个PHP内部函数中会进行更改,
也许它可以很容易地为此进行编辑。

<?php

function wd_check_serialization( $string, &$errmsg )
{

$str = 's';
$array = 'a';
$integer = 'i';
$any = '[^}]*?';
$count = '\d+';
$content = '"(?:\\\";|.)*?";';
$open_tag = '\{';
$close_tag = '\}';
$parameter = "($str|$array|$integer|$any):($count)" . "(?:[:]($open_tag|$content)|[;])";
$preg = "/$parameter|($close_tag)/";
if( !
preg_match_all( $preg, $string, $matches ) )
{
$errmsg = '不是序列化字符串';
return
false;
}
$open_arrays = 0;
foreach(
$matches[1] AS $key => $value )
{
if( !empty(
$value ) && ( $value != $array xor $value != $str xor $value != $integer ) )
{
$errmsg = '未定义的数据类型';
return
false;
}
if(
$value == $array )
{
$open_arrays++;
if(
$matches[3][$key] != '{' )
{
$errmsg = '预期打开标签';
return
false;
}
}
if(
$value == '' )
{
if(
$matches[4][$key] != '}' )
{
$errmsg = '预期关闭标签';
return
false;
}
$open_arrays--;
}
if(
$value == $str )
{
$aVar = ltrim( $matches[3][$key], '"' );
$aVar = rtrim( $aVar, '";' );
if(
strlen( $aVar ) != $matches[2][$key] )
{
$errmsg = '字符串长度不匹配';
return
false;
}
}
if(
$value == $integer )
{
if( !empty(
$matches[3][$key] ) )
{
$errmsg = '意外数据';
return
false;
}
if( !
is_integer( (int)$matches[2][$key] ) )
{
$errmsg = '预期整数';
return
false;
}
}
}
if(
$open_arrays != 0 )
{
$errmsg = '数组设置错误';
return
false;
}
return
true;
}

?>
Are Pedersen
18年前
请注意,如果在同时使用32位和64位服务器的服务器集群中使用serialize/unserialize,可能会得到意外的结果。

例如:如果在64位系统上序列化值为2147483648的整数,然后在32位系统上反序列化它,则会得到-2147483648的值。这是因为32位系统上的整数不能超过2147483647,所以会溢出。
[email protected]
15 年前
在处理包含“\r”的字符串时,长度似乎没有被正确计算。以下方法解决了我的问题

<?php
// 从$unserialized字符串中删除\r字符
$unserialized = str_replace("\r","",$unserialized);

// 然后反序列化()
unserialize($unserialized);
?>
[email protected]
12年前
我遇到了unserialize()偏移量错误。

如果您遇到类似问题,请使用以下步骤

$auctionDetails = preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $dataArr[$i]['auction_details'] );
$auctionDetails = unserialize($auctionDetails);
匿名用户
21天前
请注意,PHP 7和PHP 8中unserialize()的工作方式略有不同。

在PHP 8中,未去除空格的字符串会在类似于以下内容的位置发出警告:

PHP Warning: unserialize(): Extra data starting at offset 721 of 722 bytes in /tmp/a.php on line 4

所以像这样:

$s = 's:3:"bar";'."\n"
unserialize($s); # 会发出警告
unserialize(trim($s)); # 不会发出警告
Chris Hayes ([email protected])
20年前
回复之前关于必须在使用unserialize之前包含对象定义的帖子。有一种解决方法。

当序列化对象时,字符串的第一部分实际上是类的名称。当反序列化未知对象时,这将作为属性保留。因此,如果再次对其进行序列化,则会获得与序列化原始对象时完全相同的字符串。基本上,为了简洁起见……

如果您使用

$_SESSION['my_object'] = unserialize(serialize($_SESSION['my_object']))

那么即使会话最初将其加载为stdClass类型的对象,您也会得到正确类型的对象。
OscarZarrus
2年前
对于那些正在寻找有效处理有争议的“FALSE”的解决方案的人,可以使用此函数,如果字符串不可反序列化,则会抛出异常,而不是“FALSE”。反之,它返回反序列化的变量。
<?php
/**
* @param string $serializedString
* @param array $options
* @return mixed
* @throws Exception
*/
function UnSerialize(string $serializedString, array $options = []) {
$_unserialized = @unserialize($serializedString, $options);
if (
$serializedString === serialize(false) || $_unserialized !== false){
return
$_unserialized;
}
throw new
Exception("不可反序列化的字符串");

}

?>
[email protected]
21年前
一个小提示
如果将序列化的对象存储在会话中,则必须在初始化(session_start())会话之前包含该类。
匿名用户
5年前
如果serialize()是答案,那么你几乎肯定问错了问题。

JSON 广泛可用。它唯一不做的就是使序列化极其危险的那件事。只需要一个狡猾的黑客将精心设计的有效负载传递给所谓的“安全”序列化调用,例如数据库驱动程序就会被恶意代码覆盖。

正常地重新创建对象。使用实际数据和源文件,而不是使用序列化。否则就是懒惰,几乎等同于恶意。
MBa
13 年前
检查字符串是否已序列化

$blSerialized=(@unserialize($sText)||$sText=='b:0;');
To Top