Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is unserialize_callback_func needed when spl_autoload_register is already used?

ini_set('unserialize_callback_func', 'spl_autoload_call');

spl_autoload_register(array(self::getInstance(), 'autoload'));

Why set spl_autoload_call like above?

I made a test:

$serialized_object='O:1:"a":1:{s:5:"value";s:3:"100";}';

ini_set('unserialize_callback_func','mycallback');

function mycallback($classname) {
    echo 1;
}

function func2() 
{
    echo 2;
}

spl_autoload_register('func2');
unserialize($serialized_object);

The output is:

212

Can someone explain this?

like image 784
user198729 Avatar asked Feb 24 '10 12:02

user198729


1 Answers

I did some tests, and here are the notes I took (hope it'll be understandable ^^ ;; and that I didn't get too lost in my own thought ^^ )
Note : I've done my tests on PHP 5.3.2-dev, in case it matters.


First of all, let's define a temp-2.php file, that's going to contain only this :

<?php

class a {

}

i.e. the definition of the class that corresponds to the object we'll be trying to unserialize.

And all other portions of code I will post will be contained in a file called temp.php -- which would have to include temp-2.php so the class' definition is known.


First try : we try to unserialize the string, without having defined the class a :

$serialized_object='O:1:"a":1:{s:5:"value";s:3:"100";}';

function callback_spl($className)  {
    var_dump(__FUNCTION__ . ' : ' . $className);
}

spl_autoload_register('callback_spl');
$data = unserialize($serialized_object);
var_dump($data);

As output, we get this :

string 'callback_spl : a' (length=16)

object(__PHP_Incomplete_Class)[1]
  public '__PHP_Incomplete_Class_Name' => string 'a' (length=1)
    public 'value' => string '100' (length=3)

Which means that :

  • The autoloading function callback_spl has been called
    • even if registered by spl_autoload_register
    • But it has not autoloaded anything
  • And, as the class as not been autoloaded, we get an object that's an instance of __PHP_Incomplete_Class


Now, let's try using spl_autoload_register to register an autoloading function that actually autoloads the class' definition :

$serialized_object='O:1:"a":1:{s:5:"value";s:3:"100";}';

function callback_spl($className)  {
    var_dump(__FUNCTION__ . ' : ' . $className);
    require dirname(__FILE__) . '/temp-2.php';
}

spl_autoload_register('callback_spl');
$data = unserialize($serialized_object);
var_dump($data);

And we get this ouput :

string 'callback_spl : a' (length=16)

object(a)[1]
  public 'value' => string '100' (length=3)

Which means :

  • The autoloading function registered by spl_autoload_register has been called
    • And, this time, it did require the file containing the definition of the class
  • The un-serialization has been successful
    • i.e. we don't get an instance of __PHP_Incomplete_Class anymore,
    • we actually get an instance of a

So, here, I would say that unserialize_callback_func is not needed when spl_autoload_register is used.

I think, here, that I've kind of answered the question ? But I'll post a couple of other tests, just for fun ^^



Now, what if we try using unserialize_callback_func, and not using spl_autoload_register ?
The code will look like his, I suppose :

$serialized_object='O:1:"a":1:{s:5:"value";s:3:"100";}';

ini_set('unserialize_callback_func', 'callback_no_spl');

function callback_no_spl($className)  {
    var_dump(__FUNCTION__ . ' : ' . $className);
    require dirname(__FILE__) . '/temp-2.php';
}

$data = unserialize($serialized_object);
var_dump($data);

And, as output, we get :

string 'callback_no_spl : a' (length=19)

object(a)[1]
  public 'value' => string '100' (length=3)

So, everything works OK :

  • The callback_no_spl callback function registered via unserialize_callback_func is called
    • It loads the definition of the class
  • And the data is unserialized properly
    • i.e. we get an instance of a


Going a bit farther, let's try what we can get when both :

  • Setting an autoload function, called callback_no_spl, with unserialize_callback_func
  • And setting another autoload function, called callback_spl, with spl_autoload_register

The code will look like this :

$serialized_object='O:1:"a":1:{s:5:"value";s:3:"100";}';

ini_set('unserialize_callback_func', 'callback_no_spl');
function callback_no_spl($className)  {
    var_dump(__FUNCTION__ . ' : ' . $className);
    require dirname(__FILE__) . '/temp-2.php';
}

spl_autoload_register('callback_spl');
function callback_spl($className)  {
    var_dump(__FUNCTION__ . ' : ' . $className);
    require dirname(__FILE__) . '/temp-2.php';
}

$data = unserialize($serialized_object);
var_dump($data);

And the output we get :

string 'callback_spl : a' (length=16)

object(a)[1]
  public 'value' => string '100' (length=3)

Which means :

  • only the autoloading function registered with spl_autoload_register has been called
  • It did load the file that contains the class' definition
  • And the data has been unserialized properly.


Now, just for fun, what if we try changing the order in which we set the autoloaders ?
i.e. use this portion of code :

$serialized_object='O:1:"a":1:{s:5:"value";s:3:"100";}';

spl_autoload_register('callback_spl');
function callback_spl($className)  {
    var_dump(__FUNCTION__ . ' : ' . $className);
    require dirname(__FILE__) . '/temp-2.php';
}

ini_set('unserialize_callback_func', 'callback_no_spl');
function callback_no_spl($className)  {
    var_dump(__FUNCTION__ . ' : ' . $className);
    require dirname(__FILE__) . '/temp-2.php';
}

$data = unserialize($serialized_object);
var_dump($data);

We get exactly the same output as before :

string 'callback_spl : a' (length=16)

object(a)[1]
  public 'value' => string '100' (length=3)

Which seems to indicate that the autoloader defined with spl_autoload_register as a higher priority than the one defined with unserialize_callback_func.


What else can I test ?
Oh, let's test setting both autoloading functions, but have the one registered by spl_autoload_register (i.e. the one with the highest priority) not actually load the class' definition :

$serialized_object='O:1:"a":1:{s:5:"value";s:3:"100";}';

ini_set('unserialize_callback_func', 'callback_no_spl');
function callback_no_spl($className)  {
    var_dump(__FUNCTION__ . ' : ' . $className);
    require dirname(__FILE__) . '/temp-2.php';
}

spl_autoload_register('callback_spl');
function callback_spl($className)  {
    var_dump(__FUNCTION__ . ' : ' . $className);
    //require dirname(__FILE__) . '/temp-2.php';        // We don't load the class' definition
}

$data = unserialize($serialized_object);
var_dump($data);

This time, here's the ouput we get :

string 'callback_spl : a' (length=16)

string 'callback_no_spl : a' (length=19)

object(a)[1]
  public 'value' => string '100' (length=3)

Basically :

  • The autoloading function registered with spl_autoload_register has been called
    • It did not load the class' definition
  • So the autoloading function registered with unserialize_callback_func has been called
    • And it did load the class' definition
    • So, we've obtained the data properly un-serialized.


Now, let's come back to the code example you posted -- translated to my functions names, it would give us something like this, I suppose :

$serialized_object='O:1:"a":1:{s:5:"value";s:3:"100";}';

ini_set('unserialize_callback_func', 'callback_no_spl');
function callback_no_spl($className)  {
    var_dump(__FUNCTION__ . ' : ' . $className);
    //require dirname(__FILE__) . '/temp-2.php';        // We don't load the class' definition
}

spl_autoload_register('callback_spl');
function callback_spl($className)  {
    var_dump(__FUNCTION__ . ' : ' . $className);
    //require dirname(__FILE__) . '/temp-2.php';        // We don't load the class' definition
}

$data = unserialize($serialized_object);
var_dump($data);

And, this time, I get the same kind of thing as you did :

string 'callback_spl : a' (length=16)
string 'callback_no_spl : a' (length=19)
string 'callback_spl : a' (length=16)

( ! ) Warning: unserialize() [function.unserialize]: Function callback_no_spl() hasn't defined the class it was called for ...

object(__PHP_Incomplete_Class)[1]
  public '__PHP_Incomplete_Class_Name' => string 'a' (length=1)
  public 'value' => string '100' (length=3)

And, this time :

  • The function registered with spl_autoload_register is called
    • And doesn't load the class' definition
  • Then, the function registered with unserialize_callback_func is called
    • It doesn't load the class' defition either...
  • Like magic, the function registered with spl_autoload_register is called again !
    • It still doesn't load the class' definition
  • And boom, we get a warning saying that the function registered with unserialize_callback_func did not load the class' definition
    • Note this only happens after callback_spl has been called for the second time !
    • Which seems to indicate that there is some kind of autoloading happening even if the function defined with unserialize_callback_func didn't load what it should have...

I have to admit, this is both nice and tricky -- and I have quite no idea why this is happening, as it doesn't seem to make much sense...


I suppose this strange behavior has to do with the fact that :

  • unserialize_callback_func exists since PHP 4.2
  • while spl_autoload_register only exists since PHP 5.1 and __autoload has been introduced in PHP 5

The "stack / queue" behavior of spl_autoload_register, I suppose, can have some interferences with the old behavior of unserialize_callback_func...

like image 85
Pascal MARTIN Avatar answered Oct 16 '22 21:10

Pascal MARTIN