PHP Autoloader für das Sessionproblem
Der PHP Autoloader dient nicht allein der Bequemlichkeit. Es gibt beim Deserialisieren das Problem mit unvollständigen Klassen. Dies trifft insbesondere dann auf, wenn eine Klassendefinition während dem Deserialisieren der Session fehlt. Wenn die Klassendefinition fehlt erstellt PHP ein Objekt der Klasse __PHP_Incomplete_Class. Damit fehlen diesem entarteten Objekt sämtliche Methoden. Um diesen Programmierfehler zu vermeiden muss vor Sesssionbeginn die Klassendefinition bekannt sein.
Die folgenden Beispiele wollen das Problem konkret mit Code belegen und
Möglichkeiten zeigen diesen Fehler zu vermeiden. Dazu werden wir ein Objekt
der Klasse MyClass, welche in der Datei MyClass.php
definiert ist, aus der Session auslesen.
<?php
class MyClass
{
private
/**
* @var int
*/
$_counter = 0;
/**
* @return int
*/
public function increment()
{
return $this->_counter++;
}
}
Die folgenden Codebeispiele werden das Gleiche machen:
- Es wird falls das Objekt $_SESSION['myObject'] noch nicht existiert eine neue Instanz in die Session geschrieben.
- Dann wird der Klassenname des in der Session gespeicherten Objekts ausgegeben.
-
Abschließend wird die Methode
MyClass::increment()von dem Objekt aufgerufen und das Ergebnis ausgegeben.
Bei der ersten Ausführung laufen alle Beispiele korrekt ab und liefern folgende Ausgabe:
MyClass 0In dieser Ausführung haben sie lediglich ein neues Objekt in die Session geschrieben und mit diesem vollständigen Objekt weitergearbeitet.
Eine erneute Ausführung des Scripts sollte erwartungsgemäß das vorher gespeicherte Objekt laden und dann folgende Ausgabe liefern:
MyClass 1__PHP_Incomplete_Class - So geht's nicht.
Folgender Code started eine Session (dies kann auch implizit durch session.auto_start=true geschehen). Wenn das Objekt myObject noch nicht exisiert wird seine Klassendefinition geladen und eine neue Instanz in die Session hinterlegt.
<?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";
Dieser Code führt bei seiner zweiten Ausführung mit einem Fatal error zum Abbruch:
__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
Da die Klasse MyClass nur definiert wird, wenn noch kein
Objekt in der Session existiert, kennt PHP beim Deserialisieren die
Klassendefinition noch nicht. PHP wird statt MyClass eine Instanz der
Klasse __PHP_Incomplete_Class erstellen. Dieses
unvollständige Objekt kennt nicht die Semantik der Methode
increment().
PHP sagt in seiner Fehlermeldung bereits wie man diesen Fehler reparieren kann. Man muss dafür sorgen, dass die Klassendefinition vor dem Sessionbeginn bekannt ist. Die beiden nächsten Beispiele zeigen Möglichkeiten auf dies zu gewähren.
require 'MyClass.php' vor session_start()
Um keine unvollständige Objekte zu erhalten muss man vor
session_start() dafür sorgen dass die Klassendefinition
bekannt ist. In unserem Beispiel machen wir das in dem wir das
require_once dirname(__FILE__) . '/MyClass.php' aus dem
If-Block rausziehen und vor dem session_start() stellen.
<?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";
Ein zweiter Aufruf dieses Beispiels liefert die erwartete Ausgabe:
MyClass 1Dieses Beispiel ist sehr einfach konstruiert. Man kann sich aber auch einen Anwendungsfall vorstellen, bei dem man selber gar nicht weiß welche Klassen gerade in der Session enthalten sind. Ein Warenkorb mit etlichen Produktklassen wäre ein solches Szenario. Der Inhalt der Session kann eine beliebige Menge aus den möglichen Produktklassen sein. Dieses Problem kann man noch lösen in dem man einfach profilaktisch die Klassendefinitionen aller Produktklassen lädt. Dabei fällt auf, dass der Speicher mit unnötigen Klassendefinitionen belegt wird und das Laden dieser unnötigen Dateien auch Zeit in Anspruch nimmt. Dieses Effizienzproblem wird das letzte Beispiel lösen.
PHP Autoloader - Klassendefinition auf Anfrage
Die Fehlermeldung aus dem ersten Beispiel hat uns bereits gesagt, dass ein Autoloader auch eine Lösung für das Problem ist. Folgendes Beispiel verzichtet auf das Laden der Klassendefinition und überlässt dies stattdessen dem 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";
Auch hier haben wir wieder die erwartete Ausgabe beim zweiten Ausführen:
MyClass 1
Der Unterschied zur vorherigen Lösung ist, dass ausschließlich die
Klassendefinitionen geladen werden, welche tatsächlich für die Session
benötigt werden. Das macht in dieser Beispielanwendung effektiv keinen
Unterschied. In beiden Lösungen wird nur die Klasse MyClass
geladen. In dem angedachten Anwendungsfall mit dem Warenkorb macht
diese Lösung einen erheblichen Unterschied. Die Autoloaderlösung
lädt tatsächlich nur die Produktklassen welche sich in dem Warenkorb
befinden.