PHP Autoloader solves the session problem

PHP Autoloader serves not only for comfort purposes. You might have a problem with incomplete objects after calling unserialize(). Especially missing class definitions during session deserialization are a common error. If a class definitions is not defined, PHP creates incomplete objects of the class __PHP_Incomplete_Class. This degenerated object misses every method. To avoid this error, every class definition must be included before starting the session.

Following examples will show the problem and its solutions in PHP-Code. Therefore we will read a MyClass object from the session. The class MyClass is defined in MyClass.php:

<?php

class MyClass
{

    private
    
/**
     * @var int
     */
    
$_counter 0;

    
/**
     * @return int
     */
    
public function increment()
    {
        return 
$this->_counter++;
    }
    
}

The next examples do all the same things:

  1. If the object $_SESSION['myObject'] doesn't exist, a new object will be stored in the session.
  2. The class name of the object in the session is printed out.
  3. Finally the method MyClass::increment() is called on that object and the result is printed out.

During the first execution every example is correct and has following output:

MyClass 0

In this iteration they did only create a new object and store it in the session.

A second execution should load the stored object from the session and print the following output:

MyClass 1

__PHP_Incomplete_Class - a common mistake

This example starts a new session (this could implicitly happen by session.auto_start=true). If the object doesn't exist its class definition is loaded and a new object is stored into the session.

<?php

session_start
();

if (! isset(
$_SESSION['myObject'])) {
    require_once 
dirname(__FILE__) . '/MyClass.php';
    
$_SESSION['myObject'] = new MyClass();

}
echo 
get_class($_SESSION['myObject']),   "\n";
echo 
$_SESSION['myObject']->increment(), "\n";

The second execution fails with a fatal error:

__PHP_Incomplete_Class Fatal error: main(): The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition "MyClass" of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide a __autoload() function to load the class definition in fail.php on line 11

As the class MyClass is only defined if the session is empty, PHP doesn't know the class defintion during deserialization of the session. Instead of MyClass PHP creates an instance of __PHP_Incomplete_Class. This incomplete object doesn't know the semantic of increment().

PHP says in its error message how to repair this error. You have to take care that the class definition exists before the session start. The two next examples show solutions to this problem.

require 'MyClass.php' before session_start()

Incomplete objects are avoided by including the class definition before session_start(). In our example we just move the require_once dirname(__FILE__) . '/MyClass.php' out of the if-block before the session_start().

<?php

require_once dirname(__FILE__) . '/MyClass.php';

session_start();

if (! isset(
$_SESSION['myObject'])) {
    
$_SESSION['myObject'] = new MyClass();

}
echo 
get_class($_SESSION['myObject']),   "\n";
echo 
$_SESSION['myObject']->increment(), "\n";

A second execution prints now the expected output:

MyClass 1

That's an easy constructed example. You could also imagine a usecase where you cannot predict the content of the session. A shopping cart might be such an example. The shopping cart might contain instances of different product classes. You could solve this problem by including all product classes before the session starts. But then you require memory for unused classes, which also consumes time for loading. The next example will solve this efficiency problem.

PHP Autoloader - class definitions on demand

The error message of the first example told already that an autoloader would also solve the problem. The next example doesn't load any class definition. This is done by the PHP Autoloader.

<?php

require dirname(__FILE__) . '/autoloader/Autoloader.php';

session_start();

if (! isset(
$_SESSION['myObject'])) {
    
$_SESSION['myObject'] = new MyClass();

}
echo 
get_class($_SESSION['myObject']),   "\n";
echo 
$_SESSION['myObject']->increment(), "\n";

A second execution produces the correct output:

MyClass 1

The difference to the previous example is that only required class definitions are loaded. In this example there is no real difference, as the only loaded class is MyClass. But this solution makes a big difference in the shopping cart usecase. The autoloader solution loads only the required classes for the products in the shopping cart.