PHP反序列化失败了非编码字符?

时间:2010-05-17 22:52:21

标签: php character encode serialization

$ser = 'a:2:{i:0;s:5:"héllö";i:1;s:5:"wörld";}'; // fails
$ser2 = 'a:2:{i:0;s:5:"hello";i:1;s:5:"world";}'; // works
$out = unserialize($ser);
$out2 = unserialize($ser2);
print_r($out);
print_r($out2);
echo "<hr>";

但为什么呢? 我应该在序列化之前进行编码吗?怎么样?

我使用Javascript将序列化字符串写入隐藏字段,而不是PHP的$ _POST
在JS中我有类似的东西:

function writeImgData() {
    var caption_arr = new Array();
    $('.album img').each(function(index) {
         caption_arr.push($(this).attr('alt'));
    });
    $("#hidden-field").attr("value", serializeArray(caption_arr));
};

14 个答案:

答案 0 :(得分:51)

我知道这张贴就像一年前一样,但我只是遇到了这个问题而遇到了这个问题,事实上我找到了解决方案。这段代码就像魅力一样!

背后的想法很简单。它只是通过重新计算上面@Alix发布的多字节字符串的长度来帮助你。

一些修改应该适合您的代码:

/**
 * Mulit-byte Unserialize
 *
 * UTF-8 will screw up a serialized string
 *
 * @access private
 * @param string
 * @return string
 */
function mb_unserialize($string) {
    $string = preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $string);
    return unserialize($string);
}

来源:http://snippets.dzone.com/posts/show/6592

在我的机器上测试过,它就像魅力一样!

答案 1 :(得分:50)

unserialize()失败的原因:

$ser = 'a:2:{i:0;s:5:"héllö";i:1;s:5:"wörld";}';

是因为héllöwörld的长度是错误的,因为PHP本身并没有正确处理多字节字符串:

echo strlen('héllö'); // 7
echo strlen('wörld'); // 6

但是,如果您尝试unserialize()以下正确的字符串:

$ser = 'a:2:{i:0;s:7:"héllö";i:1;s:6:"wörld";}';

echo '<pre>';
print_r(unserialize($ser));
echo '</pre>';

有效:

Array
(
    [0] => héllö
    [1] => wörld
)

如果使用PHP serialize(),它应该正确计算多字节字符串索引的长度。

另一方面,如果你想使用多种(编程)语言的序列化数据,你应该忘记它并转向类似JSON的东西,这更加标准化。

答案 2 :(得分:24)

修改

Lionel Chan回答以使用PHP&gt; = 5.5:

function mb_unserialize($string) {
    $string2 = preg_replace_callback(
        '!s:(\d+):"(.*?)";!s',
        function($m){
            $len = strlen($m[2]);
            $result = "s:$len:\"{$m[2]}\";";
            return $result;

        },
        $string);
    return unserialize($string2);
}    

自PHP 5.5起,此代码将 preg_replace_callback 用作preg_replace with the /e modifier is obsolete

答案 3 :(得分:8)

问题是 - 指出的 - 与编码有关。

在PHP 5.4之前,PHP的内部编码是ISO-8859-1,此编码对unicode为多字节的某些字符使用单个字节。结果是在UTF-8系统上序列化的多字节值将无法在ISO-8859-1系统上读取。

避免此类问题可确保所有系统都使用相同的编码:

mb_internal_encoding('utf-8');
$arr = array('foo' => 'bár');
$buf = serialize($arr);

您可以使用utf8_(encode|decode)进行清理:

// Set system encoding to iso-8859-1
mb_internal_encoding('iso-8859-1');
$arr = unserialize(utf8_encode($serialized));
print_r($arr);

答案 4 :(得分:2)

回复上面的@Lionel,实际上如果序列化字符串本身包含char序列";(引号后跟分号),你提出的函数mb_unserialize()将无法工作。 谨慎使用。例如:

$test = 'test";string'; 
// $test is now 's:12:"test";string";'
$string = preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $test);
print $string; 
// output: s:4:"test";string";  (Wrong!!)

正如其他人所说,JSON是要走的路,恕我直言

注意:我将此作为新答案发布,因为我不知道如何直接回复(这里是新的)。

答案 5 :(得分:1)

当另一端不是PHP时,使用PHP序列化/反序列化。它并不是一种可移植的格式 - 例如,它甚至包含受保护键的ascii-1字符,这是你想要在javascript中处理的任何内容(即使它可以完美地工作,它只是非常难看)。

相反,请使用 JSON 等可移植格式。 XML也可以完成这项工作,但JSON的开销更少,程序员更友好,因为您可以轻松地将其解析为简单的数据结构,而不必处理XPath,DOM树等。

答案 6 :(得分:1)

这里还有一个小小的变化,希望有助于某人...我正在序列化一个数组,然后将其写入数据库。在检索数据时,反序列化操作失败。

事实证明,我写的数据库longtext字段是使用latin1而不是UTF8。当我把它切换成一切按计划工作时。

感谢上面所有提到字符编码的人,让我走上正轨!

答案 7 :(得分:0)

我建议你使用javascript编码为json,然后使用json_decode来反序列化。

答案 8 :(得分:0)

我们可以将字符串分解为数组:

$finalArray = array();
$nodeArr = explode('&', $_POST['formData']);

foreach($nodeArr as $value){
    $childArr = explode('=', $value);
    $finalArray[$childArr[0]] = $childArr[1];
}

答案 9 :(得分:0)

序列化:

foreach ($income_data as $key => &$value)
{
    $value = urlencode($value);
}
$data_str = serialize($income_data);

解序列化:

$data = unserialize($data_str);
foreach ($data as $key => &$value)
{
    $value = urldecode($value);
}

答案 10 :(得分:0)

这个对我有用。

function mb_unserialize($string) {
    $string = mb_convert_encoding($string, "UTF-8", mb_detect_encoding($string, "UTF-8, ISO-8859-1, ISO-8859-15", true));
    $string = preg_replace_callback(
        '/s:([0-9]+):"(.*?)";/',
        function ($match) {
            return "s:".strlen($match[2]).":\"".$match[2]."\";"; 
        },
        $string
    );
    return unserialize($string);
}

答案 11 :(得分:0)

在我的情况下,问题出在行结尾(可能是某些编辑器将我的文件从DOS更改为Unix)。

我把这些apadtive包装器放在一起:

function unserialize_fetchError($original, &$unserialized, &$errorMsg) {
    $unserialized = @unserialize($original);
    $errorMsg = error_get_last()['message'];
    return ( $unserialized !== false || $original == 'b:0;' );  // "$original == serialize(false)" is a good serialization even if deserialization actually returns false
}

function unserialize_checkAllLineEndings($original, &$unserialized, &$errorMsg, &$lineEndings) {
    if ( unserialize_fetchError($original, $unserialized, $errorMsg) ) {
        $lineEndings = 'unchanged';
        return true;
    } elseif ( unserialize_fetchError(str_replace("\n", "\n\r", $original), $unserialized, $errorMsg) ) {
        $lineEndings = '\n to \n\r';
        return true;
    } elseif ( unserialize_fetchError(str_replace("\n\r", "\n", $original), $unserialized, $errorMsg) ) {
        $lineEndings = '\n\r to \n';
        return true;
    } elseif ( unserialize_fetchError(str_replace("\r\n", "\n", $original), $unserialized, $errorMsg) ) {
        $lineEndings = '\r\n to \n';
        return true;
    } //else
    return false;
}

答案 12 :(得分:0)

此解决方案对我有用:

$unserialized = unserialize(utf8_encode($st));

答案 13 :(得分:-1)

/**
 * MULIT-BYTE UNSERIALIZE
 *
 * UTF-8 will screw up a serialized string
 *
 * @param string
 * @return string
 */
function mb_unserialize($string) {
    $string = preg_replace_callback('/!s:(\d+):"(.*?)";!se/', function($matches) { return 's:'.strlen($matches[1]).':"'.$matches[1].'";'; }, $string);
    return unserialize($string);
}