ClassLoader für PHP
Aus TriagonyWiki
Nach langem, erfolglosen suchen, wie man Klassen in PHP dynamisch laden kann, habe ich mich entschieden mir selber eine Möglichkeit zu schaffen. Was ich schaffen wollte ist, dass ich eine Möglichkeit habe meine Klassen logisch in Paketen zu ordnen und diese zu laden, wenn ich sie brauche. Was ich hier vorstellen werde ist das bisherige Ergebnis meiner Arbeit. Wenn ihr dies nutzen wollt, dann könnt ihr dies unter der GNU General Public License tun. Gerne könnt ihr an der Diskussion teilnehmen, wenn ihr wollt. Gerade wenn ihr diesen ClassLoader verwendet wäre ich sehr darber erfreut. Nun jedoch zu der Klasse.
Die Idee dahinter
Meine Idee hinter der ganzen Sache war einfach das Klassen ordentlich geladen werden. Ich komme eigentlich aus der Richtung Java und C#, wo man doch schon ein riesiges Framework hat. In PHP schaut dies noch sehr viel anders aus. Nur über extra Module sind einige Klassen vorhanden, wie zum Beispiel mysqli für eine MySQL Schnittstelle oder ZipArchive für Zip Dateien. Jedoch sind diese bei Standard Installationen nicht immer vorhanden. Ich habe jedoch vor, mir ein kleines Framework zu schaffen, womit ordentliche OOP möglich ist. Obwohl es Namespaces in PHP gibt, habe ich mich trotzdem ran gesetzt um ein kleine Verwaltung für Klassen in PHP zu schreiben. Man kann sich nun mit package ein Paket registrieren und mit import einzelne Klassen. Von diesen wird der absolute Pfad ermittelt und in einem Array hinterlegt. Über den Klassennamen ist es dann möglich mittels der __autoload Funktion die benötigte Klasse zu laden, wenn diese benötigt wird. Diese Klasse darf keine Abhängigkeiten zu anderen Klassen haben. Da dadurch nicht mehr gewährleistet werden kann, das alles ordentlich geladen werden kann.
Pakete und Klassen
Die einzelnen Paketnamen werden getrennt durch einen Punkt. Wobei jeder Name immer ein Ordnername ist. Bsp.: php.core.lang liegt dann in der Ordnerstruktur ./php/core/lang/ . Somit kann man Klassen einzelne Pakete zuordnen und dann auch nutzen. Der Trenner der einzelnen Paketnamen wird in dieser Datei gespeichert. Die Klassen welche sich in dem Paket befinden werden durch import direkt registriert. Es wird dadurch nicht das Verzeichnis nach den einzelnen vorhandenen Klassen durchsucht.
Quellcode
<?php/** package_name ClassLoader.php** Copyright (C) 2009 Sebastian Koschmieder** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License as published by* the Free Software Foundation; either version 3 of the License, or* (at your option) any later version.** This program is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the* GNU General Public License for more details.** You should have received a copy of the GNU General Public License* along with this program; if not, see <http://www.gnu.org/licenses/>.**//*** Registriert ein Paket und damit alle in sich* befindlichen Klassen. Die einzelnen Pakete* werden durch '.' voneinander getrennt. Außerdem* ist die Ordnerstruktur die gleiche wie der* Paketname.** @param String name Name des Paketes*/function package($name) {
$cl = ClassLoader :: singleton();
$cl->registerPackage($name);
}/*** Es wird eine Klasse registriert. Auch hier* ist die Ordnerstruktur einzuhalten, welche* gleich dem Paketnamen ist. Wenn ein ganzes* Paket geladen werden soll, muss es auf '.*'* enden.** @param String class Name der Klasse, welcher registriert werden soll*/function import($class) {
$cl = ClassLoader :: singleton();
$cl->registerClass($class);
}/*** Durch diese Funktion können alle registrierten* Klassen automatisch geladen werden. Wenn ein* Objekt instanziert wird, wird der Funktion* der Objektname übergeben. Anhand des Objektnamens* und der registrierten Objekte kann der Pfad* ausgemacht werden und die Klasse wird automatisch* geladen.** @param String class Der Typ des Objektes, welcher instanziert wird*/function __autoload($class) {
$cl = ClassLoader :: singleton();
$cl->loadClass($class);
}package("php.core.lang");
/*** Diese Klasse kümmert sich um das automatische* laden von Klassen. Dazu müssen die Klassen* und Pakete jedoch erst registriert werden.* Den rest übernimmt diese Klasse.* Es sollte reichen nur diese Datei einzubinden.* Um den rest kümmert sich ClassLoader.*/class ClassLoader {
/***************** Typen *****************************************//***************** Konstanten ************************************//*** Nur in dem ClassLoader muss eine Konstante* package gesetzt sein. Über diese Konstante* erfasst eine Methode den Basis Pfad*/const PACKAGE = "php.core.lang";
/*** Durch diesen Teiler werden Klassen und* Pakete voneinander getrennt*/const CLASSSEPARATOR = '.';
/*** Durch den / einzelne Pfade. Jedoch sollte* dies auch anders gehen. Sobald ich es raus* bekomme, werde ich es ändern! ;)*/const FILESEPARATOR = '/';
/***************** Aufzählungen **********************************//***************** Member ****************************************//*** Der Basis Pfad. Von dort aus werden alle* Klassen geladen. Die Pakete geben die* Ordnerstruktur an. Die Klassennamen den* Dateinamen.*/private $basePath;
/***************** Static Member *********************************//*** Eine statische Instanz. Damit ich immer* wieder auf das gleiche Objekt arbeite.*/private static $instance = __CLASS__;
/*** Hier werden die Klassennamen und die* Dateien, welche eingebunden werden* aufgelistet*/private $classes = array ();
/*** Hier werden die schon registrierten* Pakete gespeichert. Somit braucht* nicht nochmal in dem Ordner geschaut* werden, welche Klassen schon vorhanden* sind*/private $regPackages = array ();
/***************** Getter und Setter *****************************//***************** Private Methoden ******************************//*** Generiert den Basis Pfad anhand der eigenen* Datei und der package - Konstante*/private function createBasePath() {
if (isset ($this->classPath)) {
return;
}// Ich zerteile das Paket, um mir den Basis Pfad geben zu lassen$packages = explode(self :: CLASSSEPARATOR, self :: PACKAGE);
// Ich brauche das aktuelle Verzeichnis des Scriptes$path = substr(__FILE__, 0, strrpos(__FILE__, self :: FILESEPARATOR));
for ($i = (count($packages) - 1); $i >= 0; $i--) {
// Es wird geprüft, ob der Pfad auch dem Paket entspricht$pos = strrpos($path, self :: FILESEPARATOR);
$tmpPackage = substr($path, $pos +strlen(self :: FILESEPARATOR));
if ($tmpPackage != $packages[$i]) {
throw new Exception("Invalid package atom. ( " . $tmpPackage . " != " . $packages[$i] . " )", 1);
}$path = substr($path, 0, $pos);
}// Und speichern$this->basePath = $path;
}/***************** Proteced Methoden *****************************//***************** Public Methoden *******************************//*** Registriert ein gesammtes Paket. Dazu wird* zu dem Pfad gegangen und jede einzelne Datei* wird untersucht. Ist es eine PHP Datei und* ist diese Klasse noch nicht registriert, wird* sie registriert.** @param String package Das angegebene Paket wird registriert.*/public function registerPackage($package) {
if (in_array($package, $this->regPackages, true) == true) {
// Dieses Paket wurde schon geladen. es muss nicht nochmal gesucht werdenreturn;
}// Ich lasse mir den Pfad des Packages geben$path = $this->basePath . self :: FILESEPARATOR . str_replace(self :: CLASSSEPARATOR, self :: FILESEPARATOR, $package) . self :: FILESEPARATOR;
if (!is_dir($path)) {
throw new Exception("Package \"" . $package . "\" does not exist.", 3);
}$directory = dir($path);
while (false !== ($file = $directory->read())) {
// Wenn es keine Datei ist, wird es übersprungenif (!is_file($directory->path . $file)) {
continue;
}// Wenn es keine PHP Datei istif (substr($file, strlen($file) - 4) != ".php") {
continue;
}$className = substr($file, 0, -4);
// Wenn die Klasse schon geladen wurde, dann kann bei der nächsten weiter gemacht werdenif (array_key_exists($className, $this->classes)) {
continue;
}if (!file_exists($directory->path . $file)) {
continue;
}$this->classes[$className] = $directory->path . $file;
}$directory->close();
$this->regPackages[] = $package;
}/*** Eine einzelne Klasse wird registriert.** @param String class Diese Klasse wird registriert*/public function registerClass($class) {
// Wenn es sich um eine einzelne Datei handelt, kann ich die einzeln registrierenif (substr($class, -1 * (strlen(self :: CLASSSEPARATOR) + 1)) !== self :: CLASSSEPARATOR . '*') {
$className = substr($class, strrpos($class, self :: CLASSSEPARATOR) + 1, strlen($class));
if (array_key_exists($className, $this->classes)) {
return;
}$file = $this->basePath . self :: FILESEPARATOR . str_replace(self :: CLASSSEPARATOR, self :: FILESEPARATOR, $class) . ".php";
if (!file_exists($file)) {
throw new Exception("Class \"" . $class . "\" does not exist.", 2);
}// Und rein damit$this->classes[$className] = $file;
return;
}$this->registerPackage(substr($class, 0, strlen($class) - strlen(self :: CLASSSEPARATOR) - 1));
}/*** Gibt alle Klassen inerhalb eines Paketes aus.** @param String package Des Paket aus welchem die Klassen ermittelt werden* @throws Exception wenn das Paket nicht existiert*/public function getClassesFromPackage($package) {
$packagePath = $this->basePath . self :: FILESEPARATOR . str_replace(self :: CLASSSEPARATOR, self :: FILESEPARATOR, $package) . self :: FILESEPARATOR;
if (!is_dir($packagePath)) {
throw new Exception("Package \"" . $package . "\" does not exist.", 3);
}// Erstelle mir das array und geh in den Pfad$classesInPackage = array ();
$dir = dir($packagePath);
while (false !== ($file = $dir->read())) {
if (!is_file($dir->path . $file)) {
continue;
}if (substr($file, -4) !== ".php") {
continue;
}if (!file_exists($dir->path . $file)) {
continue;
}$classesInPackage[] = substr($file, 0, -4);
}$dir->close();
return $classesInPackage;
}/*** Gibt alle registrierten Klassen aus.*/public function showClasses() {
printf("<pre>");
print_r($this->classes);
printf("</pre>");
}/*** Sucht in der Registrierung nach dem* Klassennamen und bindet diese dann ein.* Es wird weder Erfolg noch Misserfolg zurück* gegeben. Da require selbst einen Fehler wirft.** @param String class Diese Klasse wird geladen* @throws Exception Wenn die angegebene Klasse nicht existiert*/public function loadClass($class) {
// Wenn der Schlüssel vorhanden ist, dann lade ich die passende Datei dazu!if (array_key_exists($class, $this->classes)) {
require_once ($this->classes[$class]);
} else {
throw new Exception("Class \"" . $class . "\" does not exist.", 2);
}}/***************** Konstruktoren *********************************//*** Der Konstruktor. Hier wird der* Basis Pfad generiert.*/private function __construct() {
$this->createBasePath();
}/***************** Static Methoden *******************************//*** Gibt mir die statische Instanz oder instanziert diese.*/public static function singleton() {
return is_object(self :: $instance) ? self :: $instance : (self :: $instance = new self :: $instance);
}}?>
