web-dev-qa-db-fra.com

PHP unserialize échoue avec des caractères non codés?

$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>";

Mais pourquoi?
Devrais-je encoder avant de sérialiser que? Comment?

J'utilise Javascript pour écrire la chaîne sérialisée dans un champ caché, par rapport à $ _POST de PHP
Dans JS, j'ai quelque chose comme:

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));
};
27
FFish

La raison pour laquelle unserialize() échoue avec:

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

Cela est dû au fait que la longueur de héllö et wörld est incorrecte, car PHP ne gère pas correctement les chaînes multi-octets de manière native:

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

Toutefois, si vous essayez de unserialize() la chaîne correcte suivante:

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

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

Ça marche:

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

Si vous utilisez PHP serialize(), les longueurs des index de chaînes multi-octets doivent être correctement calculées.

D'autre part, si vous souhaitez utiliser des données sérialisées dans plusieurs langages (de programmation), vous devez les oublier et passer à quelque chose comme JSON, qui est beaucoup plus standardisé.

50
Alix Axel

Je sais que cela a été posté il y a environ un an, mais j'ai simplement ce problème et je le rencontre, et en fait, j'ai trouvé une solution à ce problème. Ce morceau de code fonctionne comme un charme!

L'idée derrière est facile. Cela vous aide simplement en recalculant la longueur des chaînes multi-octets telle que publiée par @Alix ci-dessus.

Quelques modifications devraient convenir à votre code:

/**
 * 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);
}

Source: http://snippets.dzone.com/posts/show/6592

Testé sur ma machine, et ça marche à merveille !!

50
Lionel Chan

Lionel Chan answer modifié pour fonctionner avec 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);
}    

Ce code utilise preg_replace_callback as preg_replace avec le modificateur/e est obsolète puisque PHP 5.5. 

24
David

Le problème est - comme l'a souligné Alix - lié au codage.

Jusqu'au PHP 5.4 l'encodage interne de PHP était ISO-8859-1, cet encodage utilise un seul octet pour certains caractères qui, dans l'unicode, sont multi-octets. Il en résulte que les valeurs multi-octets sérialisées sur le système UTF-8 ne seront pas lisibles sur les systèmes ISO-8859-1. 

Les problèmes d'éviter de ce genre s'assurent que tous les systèmes utilisent le même encodage:

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

Vous pouvez utiliser utf8_(encode|decode) pour nettoyer:

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

En réponse à @Lionel ci-dessus, la fonction mb_unserialize () que vous avez proposée ne fonctionnera pas si la chaîne sérialisée contient elle-même la séquence "; (guillemet suivi d'un point-virgule). Utiliser avec précaution. Par exemple:

$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 est la voie à suivre, comme mentionné par d'autres, à mon humble avis

Remarque: je publie cette réponse comme une nouvelle réponse car je ne sais pas comment répondre directement (nouvelle ici). 

2
Joe Hong

Do not use PHP sérialisation/désérialisation lorsque l'autre extrémité n'est pas PHP. Il n’est pas censé être un format portable - par exemple, il inclut même des caractères ascii-1 pour les clés protégées, ce que vous ne voulez pas traiter en javascript (même si cela fonctionnerait parfaitement, c’est extrêmement moche).

À la place, utilisez un format portable tel queJSON. XML ferait aussi l'affaire, mais JSON a moins de frais généraux et est plus convivial pour les programmeurs, car vous pouvez facilement l'analyser dans une simple structure de données au lieu de devoir traiter avec XPath, les arbres DOM, etc.

1
ThiefMaster

Une autre légère variation ici qui, espérons-le, aidera quelqu'un ... J'étais en train de sérialiser un tableau puis de l'écrire dans une base de données. Lors de la récupération des données, l'opération de désérialisation a échoué. 

Il s'est avéré que le champ de base de données longtext dans lequel j'écrivais utilisait latin1 et non UTF8. Quand j'ai changé, tout a fonctionné comme prévu.

Merci à tous ceux qui ont mentionné plus haut l'encodage des caractères et qui m'ont mis sur la bonne voie!

1
Mike

Sérialiser:

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

Unserialize:

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

nous pouvons décomposer la chaîne en un tableau:

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

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

Dans mon cas, le problème était avec fins de lignes (il est probable qu'un éditeur a modifié mon fichier de DOS à Unix). 

Je mets ensemble ces emballages 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;
}
0

celui-ci a fonctionné pour moi.

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

Je vous conseillerais d’utiliser javascript pour encoder en json puis d’utiliser json_decode pour désérialiser.

0
Artefacto