PHP: serialize и JSON

15.10.2009

Спасибо Стальному и Виталию, благодаря им, бойцам невидимого фронта, бета-тестерам Daos выявился неприятный глюк PHP: иногда он не может распарсить свою же сериализованную строку. Особенно если в ней есть кавычки. А если в ней одинарные кавычки — пиши «пропало».

Причем, интерпретатор вообще молчит, никакой ошибки якобы, а массива после unserialize нет :( Убил не один час на отлов бага. Хорошо ещё, что наткнулся на эту проблему до начала продаж.

ОК, думаю, просто перейду на JSON. Только вот не смотря на то, что всё в UTF-8, эта зараза кодирует русские символы через жопу: \u0427\u0435\u0440\u0435\u0437 \u0436\u043e\u043f\u0443!

Кто знает, как побороть? Вроде бы мелочи, но неприятно. Или может быть альтернативный метод сериализации порекомендуете? В общем, всё плохо, смерть, депрессия, мрак, 666!

P.S. Если бы не этот глюк, в бомжеленте уже давно можно было бы покупать рекламу за бесценок через SMS.

Благодаря Жилинскому выяснилось, что ещё существуют сервера с версией PHP без JSON. В результате я «вспомнил» (на самом деле узнал) про функцию var_export. Эврика! Так что теперь никакой сериализации, настроечные данные хранятся прямо как массив в PHP-коде. Очень удобно.

Комментарии

  1. # Olegi4

    Помню вот так с Pipes пришлось мучаться – http://4.bp.blogspot.com/_IeA4aL6MxPw/SlEgav96cxI/AAAAAAAADcQ/B0WRzynQwZI/s1600/10.png
    Может подтолкнет к какой-нить идее.

  2. # Тормоз

    Некрасиво как-то :( Наверно оставлю в черезжопном формате, потому что из него нормально декодируется потом. Просто жаль, что сами файлы в JSON-формате нечитабельные получаются. Ну и ладно, всё равно их никто смотреть не будет, так, для отладки только.

    Кстати, ты там зря регулярки используешь, слишком ресурсоёмко. Это же простая замена строк.

  3. # Olegi4

    Некрасиво – факт. Но работает.
    Дык если надо каждый символ менять? Как там с заменой строк? Чего я не понимаю?

  4. # Тормоз

    В Yahoo Pipes есть модуль String Replace, он для этой задачи лучше подходит, я так думаю. Regex — когда нужны замены с непростыми правилами (по маске), а не просто одну строку на другую.

  5. # Olegi4

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

  6. # Тормоз

    Если что, его в loop оборачивать надо :)

  7. # Feo

    преобразовываем объект в строку:
    $str = urlencode(serialize($obj));
    и, соответственно, наоборот:
    $obj = unserialize(urldecode($str));

  8. # Тормоз

    А смысл?

  9. # Feo

    Смысл закодировать кавычки и прочие спецсимволы, для хранения/передачи данных.

    Пхп не может распарсить свою же сериализованную строку только из-за того, что она была изменена. Сам с этой проблемой сталкивался.

  10. # Роланд Чанишвили

    А можно поподробнее о глюке? Только что попробовал – нормально сериализация прошла, массивы в норме. Вот пример:

    <?php

    $arr = array(«один»=>«первый э‘лем\«ент», ‘второй’=>‘втор\‘ой «элемент»’);
    $ser = serialize($arr);
    $uns = unserialize($ser);
    echo ’1)’.print_r($arr,true);
    echo ‘<br />2)’.print_r($ser,true);
    echo ‘<br />3)’.print_r($uns,true);
    ?>
    Код поламался, кавычки в комментах глючат.

  11. # Тормоз

    Feo, каким образом она могла быть изменена? Я её не менял :) Обёртка из urlencode/urldecode ничего не даёт, IMHO.

    Роланд, глюк почему-то не на каждой версии PHP проявляется и ещё не на каждом массиве. Говорю же, трудноуловимая какая-то хрень. В любом случае я уже переделал на JSON и с serialize больше рисковать не буду. Жаль время.

  12. # valodzka: 

    YAML сам по себе поддерживает русский нормально, вот только не все парсеры об этом знают

  13. # Feo

    Ну ты потом что с сериализованной строкой делаешь? Записываешь ее куда нибудь?

  14. # Flint: 

    Перед десериализацией поможет такая конструкция:
    $content = stripcslashes($content);

    После нее все проблемы исчезают. Работаю с данной конструкцией уже не первую сотню прилаг =)

  15. # janso

    Feo совершенно прав. Видимо сериализованная строка меняется на каком-то этапе.

    Надо просто дополнительно энкодить каким-либо способом ее.

  16. # Миша

    никогда еще не слышал что бы ascii-представление unicode-строки называли «через жопу»

  17. # Flint: 

    UPD: действительно, когда у вас скрипт работает в UTF-8 кодировке, при записи в файл либо БД, для «защиты» сериализованного массива ко всем специальным символам добавляется обратный слеш. Наприме \» или \’.
    Соответственно, после чтения данной строки необходимо попросить PHP убрать данные слешы описанной выше командой.

    Так что все с функцией сериализации в PHP отлично. Работает как часы =)

  18. # Тормоз

    Так, господа Feo и Janso, давайте без суеверий только и прочей метафизики :)

    Поясняю. Я храню настройки в файлах. Файл примерно такой:

    <?php
    $arr = ‘a:12{s:4:«pass»…
    …serialize data…
    …d:9;}’;
    $set = unserialize($arr);

    Этот файл потом инклюдится и в $set по задумке массив настроек. Так оно и было у меня на локальном сервере, так работает до сих пор здесь на ads.brokenbrake.biz и у других бета-тестеров.

    А на некоторых серверах не работает, пустая $set получается. То есть файлы тупо скопировали вместе со всем дистрибутивом, ничего не меняя, и всё. Не работает.

    Где и что здесь изменилось, на каком этапе? Может быть я чего-то не понимаю, конечно… надеюсь, вы мне объясните :) Жду.

    Миша, а зачем нормальную строку в UTF-8 переделывать в это ascii-представление, ты можешь объяснить?

    Flint, см. выше. Я тоже раньше думал, что работает как часы, да вот, столкнулся с проблемой.

  19. # t0os: 

    json хочет, чтобы русские строки хранились в UTF-8, поэтому надо iconv‘ом (если изначально не в UTF-8) преобразовывать перед тем, как делать json_encode.

  20. # Flint: 

    Тормоз, так у тебя не десериализация неправильно работает. У тебя категорически неправильно определена переменная $arr. Естественно она не будет десериализовываться. Данная переменная попросту не является сериализованной.
    Пытаться десериализовать то, что до этого небыло сериализовано категорически не верно.
    Уж правильней для начала задать массив класическим способом, например:
    $arr = Array(‘pass’ => ‘krakazabla’, ‘user’ => ‘tormoz’);
    Кстати так и код значительно читабельней.
    После этого уже массив можно сериализовать после чего десериализовать.

    Если же есть желание разобраться в своей ошибке досконально, рекомендую взять ваш код и добавить в него до сериализации нормальное определение массива и его сериализацию, например, в переменную $arr2. После этого дебаггером сравните две строки $arr и $arr2. Ошибка будет видна сразу же.

    Также, рекомендую, обращай внимание на сообщения об ошибка. При десериализации неправильной строки у тебя должно было быть брошено исключение типа E_NOTICE, в котором совершенно четко указана ошибка вплоть до символа, который вызвал проблему.

  21. # Тормоз

    t0os, про все кодировки кроме UTF-8 вообще давно забыть нужно, не понимаю, зачем это даже обсуждать. Причём здесь iconv?

    Flint, блин, ну неужели не очевидно, что $arr до создания файла была сериализована? Не надо меня уж за дебила-то держать. И прочти выше, пожалуйста, всё же внимательно. Работает нормально везде, кроме некоторых серверов.

    И повторяю второй раз: ИНТЕРПРЕТАТОР НЕ ПОКАЗЫВАЕТ НИКАКИХ ОШИБОК, в том числе E_NOTICE. При этом в скрипте стоит установка error_reporting(E_ALL). Пожалуйста, вникайте в суть.

  22. # aktuba

    Я тоже, как-то, попал на сериализации – хранил в файлах баннеры. В итоге, файл на одном из баннеров полностью ломался…

    Кстати, вариант Flint-а должен помочь ;)

  23. # aktuba

    Тьфу… Опоздал

  24. # Flint: 

    Тормоз, никак не очевидно было из твоего примера что массив был сериализован до этого.

    Если везде кроме некоторых серверов, то логичный встречный вопрос: не включен ли данных серверах SAFEMODE = ON ?

  25. # Тормоз

    Aktuba, вариант Флинта, это со stripslashes? ОК, а что будет, если в массиве перед сериализацией были слэши? И вообще, блин, никто не читает, что я пишу :(

    Flint, извини, глаз замылен уже. Хотя, всё равно считаю, что должно быть очевидно. Потому что иначе что ещё может хранится в $arr и зачем, если там не сериализованные данные, я бы их потом десереализировал? Так что, повторюсь, вы нифига не вникаете просто :)

    SafeMode выключен. А если был бы включен, какая связь?

  26. # Flint: 

    Тормоз, – Если в тексте до сериализации были слеши, они также являются спецсимволами и перед ними ставятся тоже слеши. Отсюда получаются двойные слеши. И с ними бывают подводные камни, но это отдельная история. – Связь с SAFEMODE такая, что очень у тебя странное поведение непосредственно функции unserialize. Она обязана в случае ошибке возвращать FALSE и скидывать E_NOTICE.
    Если таковое не делается, то варианты следующие:
    1. Некая «самопальная/извращенская» сборка ядра PHP на сервере (кстати версии PHP между серверами одинаковые?)
    2. Гдето объявлена персональная директива перехвата возвращаемого значения (unserialize_callback_func directive), которая попросту гасит проблему.
    3. Таки где-то между сериализацией и десириализацией вмешались кривые руки.
    4. Заговор. Это все Заговор! ;)

    p.s. Сори, но если у тебя никто ни во что не вникает кроме тебя, то нафига вообще обращаться к кому либо?

  27. # Роланд Чанишвили

    Тормоз, может быть файло заливали на сервер в аскимоде и строки побились?

    Попробуй на том сервер где глючит и нет вывода ошибок, прописать в .htaccess
    php_flag display_errors on
    php_flag display_startup_errors on
    может ошибки в php.ini выключены вот и не видно.

    JSON уж очень раздувает строки, когда немного это ладно, а когда ты мегабайты им гоняеш – накладно становится. Так что мой выбор сериализованная строка под gz :)

  28. # Arser

    Может для загрузки настроек лучше использовать что-то типа parse_ini_file()

    А вообще конечно надо просто отвлечься и посмотреть на код свежим взглядом, иногда и не такие «проделки» бывают :)

  29. # Миша

    тормоз: очевидно же, php (или конкретно модуль, выполняющий кодирование) не знает что используется utf8 ;)

  30. # Тормоз

    Arser, ну там не только настройки, там есть и текстовые данные.

    Миша, должен знать.

  31. # Миша

    Не понятно вообще зачем тогда было их сериализировать? Бритва же…

  32. # Тормоз

    Ну типа компактнее должно было быть. И я просто не знал про var_export.

  33. # Прохожий: 

    Самый подходящий вариант под проблему тормоза, это как раз ascii режим при копировании файлов через фтп. Проверить можно простым способом. На тех серверах где твой скрипт ломался (если ты их еще не забыл ;)) сравни размеры файлов с сериализованными данными с файлами на работающих серверах. если скажешь что одинаковый размер, значит 100% заговор)

  34. # Тормоз

    Забыл, потому что всё давно по-другому сделано. А почему ascii влиять должен?

  35. # enshtein: 

    не пробовали смотреть тут http://ru2.php.net/manual/en/function.unserialize.php

  36. # ?: 

    возникла та же проблема, решилось так:

    при сериализации:

    $p = htmlspecialchars(serialize($_POST));

    восстанавливаем:

    $_POST = unserialize(stripslashes(htmlspecialchars_decode($p)));
  37. # Евгений: 

    Голосуем на страничке бага, чтобы побыстрее исправили ситуацию: bugs.php.net/bug.php…

Комментирование этой статьи закрыто.

Интересное Покупки ТехникаРазное Отдых Статьи Строительство Услуги Общество Хобби Культура Советы Уют