Why is unserialize_callback_func needed when spl_autoload_register is already used?

后端 未结 2 1541
南旧
南旧 2020-12-09 00:55
ini_set(\'unserialize_callback_func\', \'spl_autoload_call\');

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

Why set spl_autolo

2条回答
  •  攒了一身酷
    2020-12-09 01:28

    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 :

    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...

提交回复
热议问题