__PHP_Incomplete_Class Object Demystified
1. First take note of the output. A simple example:
__PHP_Incomplete_Class Object (
[__PHP_Incomplete_Class_Name] => SomeObject1
[obj1property1] => somevalue1 [obj1property2] => __PHP_Incomplete_Class Object ( [__PHP_Incomplete_Class_Name] => SomeObject2 [obj2property1] => somevalue1 [obj2property2] => Array (
['key1'] => somevalue3, ['key2'] => somevalue4 ) ) )
2. We analyze this and break it down.
__PHP_Incomplete_Class Object tells you there is an object that needs to be declared somehow.
__PHP_Incomplete_Class_Name simply tells you the expected class name. It is just one of the properties for now.
So we have:
a) an unknown object that has a class name SomeObject1 (first class)
b) it has 2 properties, namely obj1property1 and obj2property2
c) obj2property2 is itself an object whose class name is SomeObject2 (the second class)
d) SomeObject2 has two properties, obj2property1 and obj2property2
e) obj2property2 is an array that contains two elements
3. Now that we have an idea of the structure, we shall create class definitions based from it. We will just create properties for now, methods are not required as a minimum.
<?php
class SomeObject1 {
public $obj1property1;
public $obj1property2;
}
class SomeObject2 {
public $obj2property1;
public $obj2property2;
}
?>
4. Have that accessible to your script and it will solve the __PHP_Incomplete_Class Object problem as far as the output is concerned. Now you will have:
SomeObject1 ( [obj1property1] => somevalue1 [obj1property2] => SomeObject2 ( [obj2property1] => somevalue1 [obj2property2] => Array ( ['key1'] => somevalue3, ['key2'] => somevalue4 ) ) )
As you will notice, __PHP_Incomplete_Class Object is gone and replaced by the class name. The property __PHP_Incomplete_Class_Name is also removed.
5. As for the array property obj2property2, we can directly access that and just assume that it is an array and loop through it:
<?php
// this will be SomeObject1
$data = unserialize($serialized_data);
// this will be SomeObject2
$data2 = $data->obj1property2();
foreach($data2->obj2property2 as $key => $value):
print $key.' : '. $value .'<br>';
endforeach;
?>
Outputs:
key1 : somevalue3
key2 : somevalue4
That's it. You can add more methods on the class declarations for the given properties, provided you keep your original output as basis for the data types.
unserialize
(PHP 4, PHP 5)
unserialize — Bir PHP değerini saklanmış gösteriminden oluşturur
Açıklama
unserialize() işlevi tek bir dizgeleştirilmiş değişken alır ve bunu tekrar bir PHP değeri haline getirir.
Değiştirgeler
-
dizge -
Dizgeleştirilmiş değer.
Dizgeleştirilmiş bir değişkeni bir nesne haline getirmeye çalışırsanız PHP nesneyi oluşturduktan sonra özdevinimli olarak __wakeup() üye işlevini (varsa) çağıracaktır.
Bilginize: unserialize_callback_func yönergesi
Nesneleştirme sırasında tanımsız bir sınıf örneklenecekse (eksikli bir "__PHP_Incomplete_Class" nesnesinin elde edilmesini engellemek için) çağrılmak üzere bir geriçağırım işlevi tanımlamak mümkündür. Böyle bir işlevi php.ini veya .htaccess dosyasında unserialize_callback_func yönergesinde veya ya da ini_set() işlevinde belirtebilirsiniz. Bu işlevin her çağrılışında tanımsız bir sınıfın örneklenmesi gerekir. Bu özelliği iptal etmek için bu ayarı boş bırakmanız yeterlidir.
Dönen Değerler
Dönüştürülerek döndürülen değer boolean, integer, float, string, array veya object türünde olabilir.
Nesneleştirilemeyen bir dizge belirtilmesi durumunda işlev FALSE döndürür
ve bir E_NOTICE çıktılar.
Örnekler
Örnek 1 - unserialize() örneği
<?php
// Burada, oturum verisini $session_data dizisine yüklemek
// için bir veritabanından seçilmiş bir dizgeyi nesneleştireceğiz
// Bu örnek serialize() işlevi örneğinin tamamlayıcısıdır.
$conn = odbc_connect("webdb", "php", "chicken");
$stmt = odbc_prepare($conn, "SELECT data FROM sessions WHERE id = ?");
$sqldata = array($_SERVER['PHP_AUTH_USER']);
if (!odbc_execute($stmt, $sqldata) || !odbc_fetch_into($stmt, $tmp)) {
// çalıştırma veya alım başarısız olursa boş bir dizi ilklendireceğiz
$session_data = array();
} else {
// artık dizgeleştirilmiş veri $tmp[0] içinde.
$session_data = unserialize($tmp[0]);
if (!is_array($session_data)) {
// birşeyler yanlış gitmiş, boş bir dizi ilklendirelim
$session_data = array();
}
}
?>
Örnek 2 - unserialize_callback_func örneği
<?php
$serialized_object='O:1:"a":1:{s:5:"value";s:3:"100";}';
// unserialize_callback_func yönergesi PHP 4.2.0'dan beri kullanılabilmektedir.
// kendi geriçağırım işlevimizi belirtelim
ini_set('unserialize_callback_func', 'mycallback');
function mycallback($classname)
{
// Sınıf tanımınızı içeren bir dosyayı dahil etmeniz yeterlidir
// Hangi sınıf tanımının gerçekleneceğini $classname değişkenine
// bakarak saptayacaksınız
}
?>
Notlar
Hata durumunda veya dizgeleştirilmiş bir FALSE değerini nesneleştirmeye
çalışıyorsanız işlev FALSE döndürür. Bu özel durumu
str değiştirgesini
serialize(false) ile karşılaştırarak veya
E_NOTICE çıktısını inceleyerek yakalayabilirsiniz.
Ayrıca Bakınız
- serialize() - Bir değerin saklanabilir bir gösterimini üretir
- Özdevinimli Nesne Yükleme
- unserialize_callback_func
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.
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.
As mentioned in the notes, unserialize returns false in the event of an error and for boolean false. Here is the first solution mentioned, without using error handling:
<?php
function isSerialized($str) {
return ($str == serialize(false) || @unserialize($str) !== false);
}
var_dump(isSerialized('s:6:"foobar";')); // bool(true)
var_dump(isSerialized('foobar')); // bool(false)
var_dump(isSerialized('b:0;')); // bool(true)
?>
Here's a simple function to get the class of a serialized string (that is, the type of object that will be returned if it's unserialized):
<?php
function get_serial_class($serial) {
$types = array('s' => 'string', 'a' => 'array', 'b' => 'bool', 'i' => 'int', 'd' => 'float', 'N;' => 'NULL');
$parts = explode(':', $serial, 4);
return isset($types[$parts[0]]) ? $types[$parts[0]] : trim($parts[2], '"');
}
?>
I use this when saving a serialized object to a cookie, to make sure it is the right type when I go to unserialize it.
The type names are the same format/case as you would see if you did a var_dump().
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;
}
?>
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);
}
};
?>
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.
When dealing with a string which contain "\r", it seems that the length is not evaluated correctly. The following solves the problem for me :
<?php
// remove the \r caracters from the $unserialized string
$unserialized = str_replace("\r","",$unserialized);
// and then unserialize()
unserialize($unserialized);
?>
When dealing with sessions, try session_decode($data) instead of unserialize($data).
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.
To check if a string is serialized:
$blSerialized=(@unserialize($sText)||$sText=='b:0;');
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.
I was getting unserialize() Error at offset error.
If you face similar problem then use the following procedure
$auctionDetails = preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $dataArr[$i]['auction_details'] );
$auctionDetails = unserialize($auctionDetails);
To all who have problem with quoting and slashes when storing serialized data in MySQL: you are probably doing it wrong.
Use e.g. PDO with placeholders and the blob column type, and it will Just Work.
a replacement for unserialize that returns whether it worked and populates the unserialized variable by reference:
<?php
function funserialize($serialized, &$into) {
static $sfalse;
if ($sfalse === null)
$sfalse = serialize(false);
$into = @unserialize($serialized);
return $into !== false || rtrim($serialized) === $sfalse;//whitespace at end of serialized var is ignored by PHP
}
$s_foo = 'b:0;';
var_dump(funserialize($s_foo, $foo), $foo);
$s_bar = 'bar';
var_dump(funserialize($s_bar, $bar), $bar);
$s_foo = 'a:0:{};';
var_dump(funserialize($s_foo, $foo), $foo);
?>
gives:
bool(true)
bool(false)
bool(false)
bool(false)
bool(true)
array(0) {
}
I've been having horrendous problems getting my saved to mysql serialized data to unserialize. All I got back was false.
However, one handy tip saved the day.
<?php
//to safely serialize
$safe_string_to_store = base64_encode(serialize($multidimensional_array));
//to unserialize...
$array_restored_from_db = unserialize(base64_decode($encoded_serialized_string));
?>
Anyone having trouble serializing data with SimpleXMLElement objects stored within it, check this out:
This will traverse $data looking for any children which are instances of SimpleXMLElement, and will run ->asXML() on them, turning them into a string and making them serializable. Other data will be left alone.
<?php
function exportNestedSimpleXML($data) {
if (is_scalar($data) === false) {
foreach ($data as $k => $v) {
if ($v instanceof SimpleXMLElement) {
$v = str_replace(" ","\r",$v->asXML());
} else {
$v = exportNestedSimpleXML($v);
}
if (is_array($data)) {
$data[$k] = $v;
} else if (is_object($data)) {
$data->$k = $v;
}
}
}
return $data;
}
$data = array (
"baz" => array (
"foo" => new stdClass(),
"int" => 123,
"str" => "asdf",
"bar" => new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><foo>bar</foo>'),
)
);
var_dump($data);
/*array(1) {
["baz"]=>
array(4) {
["foo"]=>
object(stdClass)#3 (0) {
}
["int"]=>
int(123)
["str"]=>
string(4) "asdf"
["bar"]=>
object(SimpleXMLElement)#4 (1) {
[0]=>
string(3) "bar"
}
}
}*/
var_dump(exportNestedSimpleXML($data));
/*array(1) {
["baz"]=>
array(4) {
["foo"]=>
object(stdClass)#3 (0) {
}
["int"]=>
int(123)
["str"]=>
string(4) "asdf"
["bar"]=>
string(54) "<?xml version="1.0" encoding="UTF-8"?>
<foo>bar</foo>
"
}
}
*/
?>
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.
Ignore all the advice to implement __autoload-ers or magical handlers, to solve problems when retrieving class instances from sessions. It leads to terrible debugging nightmares.
After fussing with all sorts of such solutions and getting nowhere, I\'ve found a very simple solution that works correctly and explicitly:
$_SESSION[\'myInstance\'] = serialize($myInstance);
....
$myInstance_s = @$_SESSION[\'myInstance\']
if (!is_null($myInstance_s)) {
$myInstance = unserialize($myInstance_s);
}
In other words, serialize the thing as a string, and put it in the session in this state - you regain control of the unserialization process and can code to handle exception cases rather than praying php\'s black magic does the right thing (and being sorely disappointed when it doesn\'t).
The methods here for correcting string lengths in UTF-8 encoded serialized data weren't reliable for me; various of the data I had stored broke them. This is the method that handled all of my cases:
<?php
$serialized = preg_replace_callback(
'!(?<=^|;)s:(\d+)(?=:"(.*?)";(?:}|a:|s:|b:|d:|i:|o:|N;))!s',
'serialize_fix_callback',
$serialized
);
function serialize_fix_callback($match) {
return 's:' . strlen($match[2]);
}
?>
[EDITOR thiago NOTE: This note has fixes from user "w33ble"]
