Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

forcing access to __PHP_Incomplete_Class object properties

Tags:

php

This issue appends when you un serialize an object of a class that hasn't been included yet. For exemple, if you call session_start before include the class.

A PHPIncompleteClass object can't be accessed directly, but it's ok with foreach, serialize and gettype. Calling is_object with an PHPIncompleteClass object will result false.

So, if you find a '__PHP_Incomplete_Class' object in your session and you've included your class after the session_load, you can use this function :

function fixObject (&$object)
{
  if (!is_object ($object) && gettype ($object) == 'object')
    return ($object = unserialize (serialize ($object)));
  return $object;
}

This will results a usable object :

fixObject($_SESSION['member']);

I found this hack which will let you cast an object:

function casttoclass($class, $object)
{
  return unserialize(preg_replace('/^O:\d+:"[^"]++"/', 'O:' . strlen($class) . ':"' . $class . '"', serialize($object)));
}

From http://blog.adaniels.nl/articles/a-dark-corner-of-php-class-casting/

So you can do:

$obj = casttoclass('stdClass', $incompleteObject);

and then access properties as normal.


You could also define an unserialize_callback_func in a .htaccess/Apache configuration file. That way you wouldn't need to hack any PHP but you could include the file on demand.


As an addition here is my version of the fix_object() function: The main change is step 3 in the code: Make all properties public.

When PHP serializes an object, all private and protected properties are prefixed with two null-bytes! These null-bytes are the actual reason, why the property cannot be accessed via $obj->key because actually it is something like $obj->{NULL*NULL}key.

/**
 * Takes an __PHP_Incomplete_Class and casts it to a stdClass object.
 * All properties will be made public in this step.
 *
 * @since  1.1.0
 * @param  object $object __PHP_Incomplete_Class
 * @return object
 */
function fix_object( $object ) {
    // preg_replace_callback handler. Needed to calculate new key-length.
    $fix_key = create_function(
        '$matches',
        'return ":" . strlen( $matches[1] ) . ":\"" . $matches[1] . "\"";'
    );

    // 1. Serialize the object to a string.
    $dump = serialize( $object );

    // 2. Change class-type to 'stdClass'.
    $dump = preg_replace( '/^O:\d+:"[^"]++"/', 'O:8:"stdClass"', $dump );

    // 3. Make private and protected properties public.
    $dump = preg_replace_callback( '/:\d+:"\0.*?\0([^"]+)"/', $fix_key, $dump );

    // 4. Unserialize the modified object again.
    return unserialize( $dump );
}

var_dump will not display these NULL byte prefixes to you, but you can see them with this code:

class Test {
    private $AAA = 1;
    protected $BBB = 2;
    public $CCC = 3;
}

$test = new Test();
echo json_encode( serialize( $test ) );

// Output:
// "O:4:\"Test\":3:{s:9:\"\u0000Test\u0000AAA\";i:1;s:6:\"\u0000*\u0000BBB\";i:2;s:3:\"CCC\";i:3;}"

$test2 = fix_object( $test );
echo json_encode( serialize( $test2 ) );

// Output:
// "O:8:\"stdClass\":3:{s:3:\"AAA\";i:1;s:3:\"BBB\";i:2;s:3:\"CCC\";i:3;}"

There you see:

  • The private property is prefixed with NULL + classname + NULL
  • The protected property is prefixed with NULL + "*" + NULL

None of the above answers actually worked for me, except this solution:

$object = unserialize(serialize($object));

$object->function();

Hope it helps someone


If you just need to access raw data (like class variables) from a PHP_Incomplete_Class object, you can use the foreach hack, or you can also do:

$result_array = (array)$_SESSION['incomplete_object_index'];
echo $result_array['desired_item'];

I tried the answer of Tom Haigh here, but discovered 2 problems.

  1. when you have other "Incomplete_Class" objects as properties of the top-level-class they stay untouched as __PHP_Incomplete_Class Object
  2. if you have private properties, they will still be private in your stdClass object

So I rewrote the function handle this:

/**
 * @see: https://stackoverflow.com/a/965704/2377961
 *
 * @param  object  $object  The object that should be casted
 * @param  String  $class   The name of the class
 * @return mixed   The new created object
 */
function casttoclass($object, $class = 'stdClass')
{
  $ser_data = serialize($object);
  # preg_match_all('/O:\d+:"([^"]++)"/', $ser_data, $matches); // find all classes

  /*
   * make private and protected properties public
   *   privates  is stored as "s:14:\0class_name\0property_name")
   *   protected is stored as "s:14:\0*\0property_name")
   */
  $ser_data = preg_replace_callback('/s:\d+:"\0([^\0]+)\0([^"]+)"/',
    function($prop_match) {
      list($old, $classname, $propname) = $prop_match;
      return 's:'.strlen($propname) . ':"' . $propname . '"';
  }, $ser_data);

  // replace object-names
  $ser_data = preg_replace('/O:\d+:"[^"]++"/', 'O:' . strlen($class) . ':"' . $class . '"', $ser_data);
  return unserialize($ser_data);
}

I switch tha function arguments too, so that you can use

$obj = casttoclass($incompleteObject);

And you get an stdClass object with only public properties. Even if you have objects in childclasses they are converted to stdClass with public properties too.