PHP unserialize не работает с незакодированными символами?

$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));
};

person FFish    schedule 17.05.2010    source источник


Ответы (13)


Причина, по которой 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, который более стандартизирован.

person Alix Axel    schedule 17.05.2010
comment
json_encode: эта функция работает только с данными в кодировке UTF-8... php.net /manual/en/function.json-encode.php - person giorgio79; 27.06.2012
comment
и в случае, когда вы используете serialize(), а unserialize() все еще терпит неудачу, проверьте свой носитель данных. то есть mysql, который вы должны хранить как двоичный файл или большой двоичный объект. Если вы сохраните текст в mysql, он не будет обрабатывать ваши многобайтовые символы. - person Dev Null; 25.03.2016
comment
Также будьте осторожны при переключении между средами php. Я столкнулся с проблемами кодирования на локальном компьютере перед сохранением в базе данных, а затем попытался десериализовать на рабочем сервере. Настройка количества символов для символов решила проблему. - person Coyote6; 19.04.2016
comment
Вероятно, это также ответ на проблему, которая возникла у меня около двух лет назад и на которую я так и не нашел ответа. stackoverflow .com/questions/30289218/ - person Marc van Nieuwenhuijzen; 10.11.2016

Я знаю, что это было опубликовано год назад, но у меня просто есть эта проблема, и я наткнулся на это, и на самом деле я нашел для нее решение. Этот фрагмент кода работает как шарм!

Идея проста. Это просто помогает вам, пересчитывая длину многобайтовых строк, как указано @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

Проверено на моей машине, и это работает как шарм!!

person Lionel Chan    schedule 28.04.2011
comment
в моем случае проблема была в кодировке базы данных, поэтому я потерял часть своих данных в ???, но эта функция помогает мне заставить код работать даже с этим, спасибо - person llamerr; 27.04.2012
comment
Только что избавил меня от огромной головной боли! Спасибо. - person Damien Roche; 04.12.2012
comment
+1 за эту очень полезную работу. Я также протестировал его, и у меня он работает с данными UTF-8 с французским акцентом (PHP 5.3 на моем сервере). - person Sébastien; 11.04.2013
comment
@Sébastien Посмотрите еще один ответ здесь, который указывает на то, что с этим подходом могут быть проблемы;) - person Lionel Chan; 12.04.2013
comment
Хороший улов, я бы не заметил. +1 и для @Joe-Hong. Есть ли способ проверить и исправить это? - person Sébastien; 12.04.2013
comment
Обратите внимание, что модификатор e уходит, пора переключиться на preg_replace_callback. - person Alix Axel; 03.07.2013
comment
В дополнение к удалению модификатора e это приведет к сбою для любой сериализованной строки, содержащей конец поиска регулярного выражения (;) - person Doug Kress; 18.09.2013
comment
Эта функция просто спасла мой день! Спасибо, что поделился! - person Jerome Bohg; 14.08.2014
comment
Я написал ниже, что ваша функция изменена для работы с PHP 5.5. Спасибо за ваш полезный вклад. - person David; 13.01.2015
comment
На самом деле регулярное выражение неверно, так как сама строка может включать в себя шаблон, не связанный со схемой сериализации. Например. Сериализованная часть ...s:28:"some "quotes"; in the middle";... после того, как ваша функция вернет ...s:13:"some \"quotes"; in the middle";.... Это одна из причин, по которой были созданы сериализации. - person Slavik Meltser; 08.04.2017
comment
УДИВИТЕЛЬНО @lionel-chan Я так напрягся, думая, что все данные испорчены. Вы спасли мне жизнь......спасибо большое......:):):). ЭТО ПОКАЗЫВАЕТ БЫЛО ПРАВИЛЬНЫМ ПРИНЯТЫЙ ОТВЕТ - person Parag; 18.02.2018

Ответ Lionel Chan изменен для работы с PHP >= 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);
}    

Этот код использует preg_replace_callback как preg_replace с модификатор /e устарел начиная с PHP 5.5.

person David    schedule 13.01.2015
comment
Мне пришлось использовать эту версию, чтобы предотвратить появление строк HTML в закодированных массивах с неправильно экранированными двойными кавычками в несериализованных строках. - person fideloper; 02.11.2015
comment
Миллион благодарностей @David. Я боролся с преобразованием этой функции уже много дней! - person Ifedi Okonkwo; 27.11.2017

Проблема, как указала Аликс, связана с кодировкой.

До PHP 5.4 внутренней кодировкой для PHP была ISO-8859-1, эта кодировка использует один байт для некоторых символов, которые в Юникоде являются многобайтовыми. В результате многобайтовые значения, сериализованные в системе 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);
person lafka    schedule 24.05.2012

В ответ на @Lionel выше, на самом деле предложенная вами функция 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 - это путь, как упоминалось другими, ИМХО

Примечание. Я публикую это как новый ответ, так как не знаю, как ответить напрямую (новое здесь).

person Joe Hong    schedule 02.04.2012
comment
Вскоре вы сможете отвечать комментариями. Продолжайте вносить свой вклад! Ура~ - person Andrew Kozak; 02.04.2012
comment
Хорошо знать. Есть ли решение? - person Tyler Collier; 04.12.2020

Не используйте сериализацию/десериализацию PHP, если другой конец не является PHP. Он не предназначен для переносимого формата - например, он даже включает символы ascii-1 для защищенных ключей, с которыми вы не хотите иметь дело в javascript (хотя это будет работать отлично, это просто очень уродливо).

Вместо этого используйте переносимый формат, например JSON. XML тоже подойдет, но JSON имеет меньше накладных расходов и более удобен для программиста, поскольку вы можете легко разобрать его в простую структуру данных вместо того, чтобы иметь дело с XPath, деревьями DOM и т. д.

person ThiefMaster    schedule 17.05.2010
comment
Не говоря уже о том, что десериализация из ненадежных источников может привести к выполнению произвольного кода. - person L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o&#x; 18.05.2010
comment
К сожалению, выбор был навязан нам чужой работой. Это особенно распространено при импорте данных из старого проекта/системы, в которых сериализация уже хорошо зарекомендовала себя. - person Adambean; 09.10.2020

Еще одна небольшая вариация, которая, надеюсь, кому-то поможет... Я сериализовал массив, а затем записывал его в базу данных. При извлечении данных операция десериализации завершилась неудачно.

Оказывается, поле длинного текста базы данных, в которое я писал, использовало latin1, а не UTF8. Когда я переключил его, все заработало, как и планировалось.

Спасибо всем выше, кто упомянул кодировку символов и направил меня на правильный путь!

person Mike    schedule 12.04.2013

Я бы посоветовал вам использовать javascript для кодирования как json, а затем использовать json_decode для десериализации.

person Artefacto    schedule 17.05.2010
comment
при этом $ser = 'a:2:{i:0;s:5:héllö;i:1;s:5:wörld;}'; var_dump (десериализовать ($ ser)); отлично работает со мной. Что вы имеете в виду под неудачей? Вызов unserialize() не работает? - person Artefacto; 18.05.2010

мы можем разбить строку на массив:

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

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

Сериализация:

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);
}
person sNICkerssss    schedule 17.10.2016

этот работал для меня.

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);
}
person Paolo Josef Abadesco    schedule 12.10.2017

В моем случае проблема заключалась в концах строк (вероятно, какой-то редактор изменил мой файл с DOS на Unix).

Я собрал эти адаптивные обертки:

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;
}
person Vittorio Zamparella    schedule 13.04.2018

Это решение сработало для меня:

$unserialized = unserialize(utf8_encode($st));
person Артур Димерчан    schedule 17.09.2020