Reply to eoin's post:
The regular expression you used to unserialize a PHP session file won't work if the session file contains string variable which contains character "|".
As I can see now, there is no any regular expression that can easily split data in a PHP session file. To read data from a session file, we may have to write a function to read content from the file, and then parse the content.
(This reply was also posted at http://www.php.net/manual/en/function.serialize.php to reply Erudd's post)
unserialize
(PHP 4, PHP 5)
unserialize — 保存用表現から PHP の値を生成する
説明
mixed unserialize ( string $str )unserialize() は、シリアル化された変数 (serialize() を参照) をとり、PHP 変数値に 戻す変換を行います。変換された値が返されます。その値は、 boolean, integer, float, string, array, object とすることが可能です。オブジェクトがシリアル化された場合、返り値 にそのメソッドは保存されていません。 E_NOTICE が発生した場合、FALSE を返します。
エラーやシリアライズされた FALSE 値をアンシリアライズする場合、 FALSE が返されます。この特殊なケースは str を serialize(false) で比較する、もしくは E_NOTICE をキャッチすることで区別することができます。
unserialize_callback_func ディレクティブ: コールバック関数を設定することが可能です。(不完全な object "__PHP_Incomplete_Class"を得ることを防ぐため) コールバック関数は、非シリアル化する際に未定義のクラスをインスタ ンス化する必要がある場合にコールされます。 'unserialize_callback_func'を定義するためには、 php.ini, ini_set(), .htaccess を使用し てください。未定義のクラスをインスタンス化する度に、コールバック関 数がコールされます。この機能を無効とするには、 単純にこの設定を空にしてください。また、ディレクティブ unserialize_callback_func は PHP 4.2.0 で利用可能になったことに注意してください。
もしアンシリアライズサル変数がオブジェクトの場合、 オブジェクトが無事再作成された後、PHP は自動的にメンバー関数 __wakeup() (存在していれば) をコールしようとします。
例 2524. unserialize_callback_funcの例
<?php
$serialized_object='O:1:"a":1:{s:5:"value";s:3:"100";}';
// unserialize_callback_func ディレクティブは PHP 4.2.0 以降で利用可能
ini_set('unserialize_callback_func', 'mycallback'); // 独自のコールバック関数を設定する
function mycallback($classname)
{
// just include a file containing your classdefinition
// you get $classname to figure out which classdefinition is required
}
?>
注意: PHP 3では、メソッドはシリアル化されたオブジェクトを非シリアル化 する際に保存されません。PHP 4ではこの制限は取り除かれ、プロパティ とメソッドの両方を保存します。より詳細な情報については、 オブジェクトとクラス の オブジェクトのシリア ル化の節を参照ください。
例 2525. unserialize()の例
<?php
// ここで、データベースから $session_data にセッションデータをロード
// するために unserialize() を使用します。
// この例は、<function>serialize</function> で記述された例を補足するものです。
$conn = odbc_connect("webdb", "php", "chicken");
$stmt = odbc_prepare($conn, "SELECT data FROM sessions WHERE id = ?");
$sqldata = array ($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();
}
}
?>
serialize()も参照ください。
unserialize
09-Aug-2007 01:46
27-Jun-2007 02:42
props to steve at bluearena dot com and getmequick at gmail dot com
Here's my implementation :p
function mb_unserialize($serial_str) {
$out = preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $serial_str );
return unserialize($out);
}
Works great for serialized UTF-8 content that doesn't like to be unserialized!
15-Jun-2007 09:18
To getmequick at gmail dot com
Your outstanding preg_replace
<? php
$unserialized = preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $unserialized );
?>
helped me out of a hole after converting a database from latin1 to UTF8 and forgetting about how strict the strlen of serialized strings needs to be!
I just added the s modifier for it to be able to support whitespace and it fixed everything.
Thanks a million.
05-May-2007 03:02
If instead of using JSON, you'd like to stick with PHP-style serialization, here's some JavaScript code I posted at http://magnetiq.com for serializing JavaScript objects in PHP fashion:
/* Returns the class name of the argument or undefined if
it's not a valid JavaScript object.
*/
function getObjectClass(obj)
{
if (obj && obj.constructor && obj.constructor.toString)
{
var arr = obj.constructor.toString().match(
/function\s*(\w+)/);
if (arr && arr.length == 2)
{
return arr[1];
}
}
return undefined;
}
/* Serializes the given argument, PHP-style.
The type mapping is as follows:
JavaScript Type PHP Type
--------------- --------
Number Integer or Decimal
String String
Boolean Boolean
Array Array
Object Object
undefined Null
The special JavaScript object null also becomes PHP Null.
This function may not handle associative arrays or array
objects with additional properties well.
*/
function phpSerialize(val)
{
switch (typeof(val))
{
case "number":
return (Math.floor(val) == val ? "i" : "d") + ":" +
val + ";";
case "string":
return "s:" + val.length + ":\"" + val + "\";";
case "boolean":
return "b:" + (val ? "1" : "0") + ";";
case "object":
if (val == null)
{
return "N;";
}
else if ("length" in val)
{
var idxobj = { idx: -1 };
return "a:" + val.length + ":{" + val.map(
function (item)
{
this.idx++;
var ser = phpSerialize(item);
return ser ?
phpSerialize(this.idx) + ser :
false;
}, idxobj).filter(
function (item)
{
return item;
}).join("") + "}";
}
else
{
var class_name = getObjectClass(val);
if (class_name == undefined)
{
return false;
}
var props = new Array();
for (var prop in val)
{
var ser = phpSerialize(val[prop]);
if (ser)
{
props.push(phpSerialize(prop) + ser);
}
}
return "O:" + class_name.length + ":\"" +
class_name + "\":" + props.length + ":{" +
props.join("") + "}";
}
case "undefined":
return "N;";
}
return false;
}
On the client side, you can pass in a complex (nested) JavaScript object to the phpSerialize function to get a PHP-style serialized representation. This string can be posted back and directly passed to the unserialize function to yield a representation of the complex object in PHP realm. Use of this technique requires caution on security matters.
11-Apr-2007 12:49
A note to the last person. To avoid the incomplete class notice, all you need to do is make sure you include the class definition before session_start(); is called. Simple as that.
07-Apr-2007 06:15
Thank you, thank you, thank you to Chris Hayes for
$_SESSION['my_object'] = unserialize(serialize($_SESSION['my_object']))
I wrestled through several very learned articles that dealt with creating factory classes for serializing objects and including the entire class definition in each and every file, yet one single line was all it took to stop my drupal module from whining about __PHP_Incomplete_Class when I tried to use load an instance from a session.
02-Mar-2007 11:20
When trying to serialize or unserialize recursive arrays or otherwise linked data you might find the undocumented R data type quite useful.
If you want a array like the one produced with
<?
$a = array();
$a[0] =& $a;
?>
serialized you can store it using a string simular to this one:
<?
$a = unserialize("a:1:{i:0;R:1;}");
?>
Both sources will make $a hold an array that self-references itself in index 0.
The argument for R is the index of the created sub-variable of the serialize-string beginning with 1.
27-Feb-2007 01:07
I couldn't get it with all these solutions, but it worked for me by mixing some of them this way:
<?php
function __serialize($object) {
$serialized = serialize($object);
return htmlentities($serialized,ENT_QUOTES);
}
//
function __unserialize($string) {
$unserialized = stripslashes($string);
$unserialized = preg_replace('!s:(\d+):"(.*?)";!e', "'s:'.strlen('$2').':\"$2\";'", $unserialized );
return unserialize($unserialized);
}
?>
23-Feb-2007 02:16
To Igor Defaye:
Thanks a lot. It worked to me.
unserialize() parameter ends when it first find a ' character, so if there is any it truncates the data and it does work properly.
(tested with PHP 4.4.1)
22-Feb-2007 01:59
To Igor Defaye:
unserialize() works fine with anything I've thrown at it, single quotes included. My guess is you're running into problems created by Magic Quotes.
17-Feb-2007 01:08
It took me a good hour to figure out why serialized data stored in mysql database would not unserialize.
This is what I was trying to unserialize :
a:4:{i:0;s:43:"Date_Format(dateEntree,'%Y%m%d')";}
Addslashes did not work for me, I had to use :
str_replace("'", "\'", $serializedData)
in order to replace only sigle quotes and not double quotes, so I obtained this :
a:4:{i:0;s:43:"Date_Format(dateEntree,\'%Y%m%d\')";}
unserialize does not like single quotes.
Hope it can help someome.
19-Dec-2006 10:09
This little function will check whether the serialized string is well formed.
PHP < 6 because i'd heard changes will be made in this php-intern function,
maybe it could be edited easy for it.
<?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 = 'not a serialized string';
return false;
}
$open_arrays = 0;
foreach( $matches[1] AS $key => $value )
{
if( !empty( $value ) && ( $value != $array xor $value != $str xor $value != $integer ) )
{
$errmsg = 'undefined datatype';
return false;
}
if( $value == $array )
{
$open_arrays++;
if( $matches[3][$key] != '{' )
{
$errmsg = 'open tag expected';
return false;
}
}
if( $value == '' )
{
if( $matches[4][$key] != '}' )
{
$errmsg = 'close tag expected';
return false;
}
$open_arrays--;
}
if( $value == $str )
{
$aVar = ltrim( $matches[3][$key], '"' );
$aVar = rtrim( $aVar, '";' );
if( strlen( $aVar ) != $matches[2][$key] )
{
$errmsg = 'stringlen for string not match';
return false;
}
}
if( $value == $integer )
{
if( !empty( $matches[3][$key] ) )
{
$errmsg = 'unexpected data';
return false;
}
if( !is_integer( (int)$matches[2][$key] ) )
{
$errmsg = 'integer expected';
return false;
}
}
}
if( $open_arrays != 0 )
{
$errmsg = 'wrong setted arrays';
return false;
}
return true;
}
?>
20-Nov-2006 08:22
for those who has error like "
error at offset .."
but doesn't know why.
This function might be usefull
<?php
$sObject3 = 'a:2:{i:0;s:1:"1";i:1;s:3654:"1a1dc91c907325c69271ddf0c944bc72";}';
print_r( __unserialize($sObject3 ));
function __unserialize($sObject) {
$__ret =preg_replace('!s:(\d+):"(.*?)";!e', "'s:'.strlen('$2').':\"$2\";'", $sObject );
return unserialize($__ret);
}
?>
03-Nov-2006 01:10
Apparently, unserialize is really picky about anyone messing with the serial string. Just spent an hour debugging why unserialize wasn't working on a serial string stored in a database where, per client requirement, all inserted data is strtoupper'd. Can't just strtolower on a serial string, though -- if there are null values in there (signified as N;), they must be upper case.
The following call may be a bit convoluted, but it works.
unserialize(preg_replace('/;n;/', ';N;', strtolower($serial)))
Of course, if you have string data within that array, that'll get clobbered by the strtolower. In my case, I was dealing with integer values (aside from the key names). But this might save someone else out there a little trouble.
09-Aug-2006 05:46
Be aware that if useing serialize/unserialize in a serverfarm with both 32bit and 64bit servers you can get unexpected results.
Ex: if you serialize an integer with value of 2147483648 on a 64bit system and then unserialize it on a 32bit system you will get the value -2147483648 instead. This is because an integer on 32bit cannot be above 2147483647 so it wraps.
06-May-2006 01:08
When unserializing in PHP5 (behavior observed with 5.1.2), __autoload() will be checked first, and unserialize_callback_func called only if __autoload failed to load the class definition.
02-Mar-2006 02:37
I have noticed that using the eval function as described in other posts might produce class redefinitions if separate class files are used and included.
My solution was to include_once at the top all .php files with classes that will be used on a particular page. This way the class definition exists when unserialize is called and you get the no duplicate check for free by using include_once.
Maybe someone can chime in on how this might affect performance.
10-Feb-2006 09:00
<?php
// Create your array()
$array = array("First","Second","Third");
// Create your package
$package = serialize($array);
// You can print your serialized package
// a:3:{i:0;s:5:"First";i:1;s:6:"Second";i:2;s:5:"Third";}
print $package."<br>";
// Unserialize your serialized package with print_r or var_dump
$data = unserialize($package);
print_r($data);
// var_dump($data); // It's same as print_r
// Array ( [0] => First [1] => Second [2] => Third )
?>
30-Jan-2006 01:49
"Moreover, you must be *very* careful when using eval() as it can be easily used to do something nasty."
Actually, you should probably be equally careful with unserialize, as using it might result in code from your (or standard PHP) classes being executed when you aren't expecting it. It's clearly harder to exploit, but the possibility is there. Therefore, only unserialize data that you know you generated yourself (either because it's stored where you put it, or because its signed, or has been validated in some other way).
20-Jan-2006 03:28
Accualy artistan's function does not work (there should be $serialized_data instead of $text in function body). Moreover, you must be *very* careful when using eval() as it can be easily used to do something nasty. Here comes what I've made basing on the two available versions:
<?php
function unserialize_data($str, $objs = true) {
if ($objs && preg_match_all('/O:\d+:"([a-zA-Z0-9_]+)":/', $str, $arr)) {
$callback = ini_get('unserialize_callback_func');
foreach ($arr[1] as $cls) {
if (!class_exists($cls)) {
call_user_func($callback, $cls);
}
if (!class_exists($cls)) {
eval('class ' . $cls . ' { }');
}
}
}
return unserialize($str);
}
?>
However, better (not tested though) sollution seems to be:
<?php
function declare_empty_class($classname) {
static $callback = null;
if ($callback===null) {
$callback = $classname;
return;
}
if ($callback) {
call_user_func($callback, $classname);
}
if (!class_exists($classname)) {
eval('class ' . $classname . ' { }');
}
}
declare_empty_class(ini_get('unserialize_callback_func'));
ini_set('unserialize_callback_func', 'declare_empty_class');
?>
12-Jan-2006 01:46
// thanks to user comments I created this
/**
UnSerialize and return data
@param string [$serialized_data] data to unserialize
@return mixed
*/
function unserialize_data($serialized_data,$objects=true){
if($objects){
// thanks to comment from veg@rd.no 04-Jan-2006 10:34 at http://us2.php.net/manual/en/function.unserialize.php
// make classes for those that do not exist
$matches = Array();
preg_match_all('/O:(\d)*:"([^":]*)":/', $text, $matches);
foreach ($matches[2] as $class)
{
if(!class_exists($class))
eval('class '.$class.' { }');
}
}
return unserialize($serialized_data);
}
// if using php5 you may want to modify to use __autoload function.
05-Jan-2006 03:34
Unserializing an object of a class that hasn't been declared where it is being unserialized will result in an unusable object. I do not know why this happens, but I know of two workarounds which I've written about here; http://vega.rd.no/entry/200601_serializing_objects_in_php
In short, the solution is to declare the class before unserializing the object; so this code will do when unserializing an object of type MyClass;
<?php
class MyClass {}
$object = unserialize($serializedData);
?>
However, if you have several different classes, which you don't necessarily know the names of beforehand, use this short function to handle it for you;
<?php
function unserializeUnknownObject($text)
{
$matches = Array();
preg_match_all('/O:(\d)*:"([^":]*)":/', $text, $matches);
foreach ($matches[2] as $class)
{
eval('class '.$class.' { }');
}
return unserialize($text);
}
$object = unserializeUnknownObject($serializedData);
?>
This could also have been accomplished by using unserialize_callback_func.
18-Dec-2005 10:30
I screwed around with this for hours and hours and it was doing my head in. Hopefully this can help.
When I was loading my page which was unserializing a string it would just hang (not finish sending information to the browser) at random places if I had not put the Class information first.
This is probably obvious (I know it says that it "will not work" if I do not declare the classes first) but the strange error of randomly stopping putting data to the page was very offputting from the actual error. It seems to go for a few KB or something and then just decide to stop.
11-Dec-2005 12:57
The only complaint I have about serializing, is that it will not save the variable name. To fix this, as well as add support for more then one variable/array, I chose to write this class:
class SaveVar
{
function store($list)
{
$sl = explode("|", $list);
for ($i = 0; $i < count($sl); $i++)
{
$varname = $sl[$i];
global $$varname;
$thevar = $$varname;
$serialized = serialize($thevar);
$saved[$i] = base64_encode($varname)."@".base64_encode($serialized);
}
$data = implode("|", $saved);
return($data);
}
function restore($stored)
{
$sv = explode("|", $stored);
for ($i = 0; $i < count($sv); $i++)
{
$svp = $sv[$i];
list($name64, $value64) = explode("@", $svp);
$name = base64_decode($name64);
$value = base64_decode($value64);
global $$name;
$$name = unserialize($value);
}
}
}
----
An example of using this class is as follows...
----
$arr1 = array("Hello", "World");
$arr2 = array("World", "Hello");
$sv = new SaveVar;
$saved = $sv->store("arr1|arr2");
$arr1 = NULL;
$arr2 = NULL;
echo $saved;
echo "<HR>";
$sv->restore($saved);
print_r($arr1);
echo "<BR>";
print_r($arr2);
----
I hope someone finds this useful...
-Jestin S Larson
15-Aug-2005 08:48
When you use sessions, its very important to keep the sessiondata small, due to low performance with unserialize. Every class shoud extend from this class. The result will be, that no null Values are written to the sessiondata. It will increase performance.
<?
class BaseObject
{
function __sleep()
{
$vars = (array)$this;
foreach ($vars as $key => $val)
{
if (is_null($val))
{
unset($vars[$key]);
}
}
return array_keys($vars);
}
};
?>
06-Feb-2005 03:20
I got the same case as yabba at the dot hut with his post
>> caveat: stripslashes!!!
In my server configutation the magic_quotes_gpc is on therefore it will automatically escape ' (single-quote), " (double quote), \ (backslash) and NUL's with a backslash.
And the stripslashes is the workaround for my case as well.
Erwin
24-Oct-2004 01:27
In reply to the earlier post about having to include object definitions *before* using unserialize. There is a workaround for this.
When an object is serialized, the first bit of the string is actually the name of the class. When an unknown object is unserialized, this is maintained as a property. So if you serialize it again, you get back the exact same string as if you'd serialized the original object. Basically, to cut to the point...
If you use
$_SESSION['my_object'] = unserialize(serialize($_SESSION['my_object']))
then you get back an object of the correct type, even if the session had originally loaded it as an object of type stdClass.
09-Sep-2004 08:14
If you are accepting a serialized string from an untrusted source (e.g. generated in Javascript), you need to be careful to check that it doesn't result in "unexpected" objects being created when you unserialize it.
The following function pulls out the class names of all objects in a _valid_ serialized string. It works by first removing an serialized string values (which might contain serialized object syntax) then pulling out the class names from the remaining string. The returned value is a unique list of class names which the serialized string contains.
Note it assumes the serialized string is valid (that it will be accepted by unserialize()). There may be invalid serialized strings that could trick this function but these should fail when unserialized.
<?php
function getSerializedClassNames($string) {
// Stip any string representations (which might contain object syntax)
$string = preg_replace('/s:[0-9]+:".*"/Us','',$string);
// Pull out the class named
preg_match_all('/O:[0-9]+:"(.*)"/U', $string, $matches, PREG_PATTERN_ORDER);
// Make sure names are unique (same object serialized twice)
return array_unique($matches[1]);
}
?>
Unit tests for a version of this function can be found at:
http://cvs.sourceforge.net/viewcvs.py/xmlrpccom
/scriptserver/tests/php/classparser.test.php?view=auto
See also the discussion here;
http://marc.theaimsgroup.com/?t=109439858700006&r=1&w=2
http://marc.theaimsgroup.com/?l=php-dev&m=109444959007776&w=2
03-Sep-2004 06:25
Having had a problem with an mysql-stored serialized array which I had edited I found out that unserialize seems to have got a problem with "\r" within the string I wanted to unserialize.
It simply quits it's job with "false".
To work arround this problem I just replaced \r with "":
<?php
$serializedArray = 'a:1:{i:0;a:2:{s:4:"name";s:70:"Here comes the newline\r\nthis is the new line";s:5:"value";d:2.20;}}';
var_dump(unserialize($serializedArray)); //This just outputs bool(false)
$serializedArray = str_replace("\r", "", $serializedArray);
var_dump(unserialize($serializedArray)); //This outputs the array structure
?>
18-Aug-2004 03:38
I recently found myself in need of unserializing PHP session data (stored in a database) that had expired but was not yet deleted i.e. I needed to get at the contents of the session but was unable to use the usual PHP interface. The following function takes a string of serialized session data and returns an array of PHP values. The structure of the returned array is the same as the $_SESSION array if you were using the normal interface. I haven't tested this extensively but it did the job for me.
<?php
function unserialize_session_data( $serialized_string ) {
$variables = array( );
$a = preg_split( "/(\w+)\|/", $serialized_string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );
for( $i = 0; $i < count( $a ); $i = $i+2 ) {
$variables[$a[$i]] = unserialize( $a[$i+1] );
}
return( $variables );
}
?>
31-May-2004 01:29
yabba at the dot hut:
The cookie mechanism for the webserver adds the slashes automatically. instead of just dumping strings into the cookie, make sure you base64_encode them first - to protect the cookie's content from escape characters.
Of course, this means that when retrieving the cookie, you'll need to base64_decode the string.
If a a string is unserializeable FALSE is returned as well as an E_NOTICE error. This is odd since you may want to know if a given string converts back to a PHP value and act accordingly. If you run your script with E_ALL reporting this will show up.
I noticed this debugging this line of code:
$b = unserialize(base64_decode($a));
Curiously, base64_decode() does not throw errors if the string can't be decoded. The only workaround is to prepend the @ operator to unserialize() or to change the error level.
29-Jan-2004 06:09
caveat: stripslashes!!!
if using
setcookie('hubba',serialize($data));
to set a cookie, you might want to check
$data(unserialize(stripslashes($_COOKIE['hubba']));
to retrieve them back!!!
this is, if unserialize fails. you can also print_r($_COOKIE) to look into what you've got back.
beats me how the slashes got there in the first place....
10-Dec-2003 10:27
It is possible to make a neat autoloader for class definitions using this, but there are some gotchas for the unwary programmer:
1) If you are setting the unserialize_callback_func directive in the ini or .htaccess file, use auto_prepend_file to load the definition of your callback function - otherwise objects that stay in the session may trigger errors on pages where you didn't expect the object to be used.
2) It helps if you define all your classes in files with lowercase file names from the beginning. The callback function is always call with the class name in lower case, and it is a lot quicker to use that directly than make a lookup table:
function callback_func($classname) {
@include_once("${classname}.class.php");
}
3) It does not appear to be possible to use a static member fuction of a class (for example, a your object persistence layer) as the unserialize callback function, so this will cause confusion:
php_value auto_prepend_file "Persist.php"
php_value unserialize_callback_func Persist::factory
The next best solution is to make it a function in the global scope and have your object factory call it as required:
Class Persist
{
function &factory($type) {
callback_func(strtolower($type));
$classname = "${type}";
if (!class_exists($classname)) {
return PEAR::raiseError('Class Not Found',PERSIST_ERROR_CLASS_NOT_FOUND,PEAR_ERROR_RETURN);
}
@$obj =& new $classname;
return $obj;
}
...
}
31-Oct-2003 03:02
A quick note:
If you store a serialized object in a session, you have to include the class _before_ you initialize (session_start()) the session.