ClassLoader für PHP

Aus TriagonyWiki

Wechseln zu: Navigation, Suche

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

  1. <?php
  2.  
  3.  
  4. /*
  5.  * package_name ClassLoader.php
  6.  *
  7.  * Copyright (C) 2009 Sebastian Koschmieder
  8.  *
  9.  * This program is free software; you can redistribute it and/or modify
  10.  * it under the terms of the GNU General Public License as published by
  11.  * the Free Software Foundation; either version 3 of the License, or
  12.  * (at your option) any later version.
  13.  *
  14.  * This program is distributed in the hope that it will be useful,
  15.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17.  * GNU General Public License for more details.
  18.  *
  19.  * You should have received a copy of the GNU General Public License
  20.  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  21.  *
  22.  */
  23.  
  24. /**
  25.  * Registriert ein Paket und damit alle in sich
  26.  * befindlichen Klassen. Die einzelnen Pakete
  27.  * werden durch '.' voneinander getrennt. Außerdem
  28.  * ist die Ordnerstruktur die gleiche wie der
  29.  * Paketname.
  30.  *
  31.  * @param String name Name des Paketes
  32.  */
  33. function package($name) {
  34.  
  35. 	$cl = ClassLoader :: singleton();
  36. 	$cl->registerPackage($name);
  37.  
  38. }
  39.  
  40. /**
  41.  * Es wird eine Klasse registriert. Auch hier
  42.  * ist die Ordnerstruktur einzuhalten, welche
  43.  * gleich dem Paketnamen ist. Wenn ein ganzes
  44.  * Paket geladen werden soll, muss es auf '.*'
  45.  * enden.
  46.  *
  47.  * @param String class Name der Klasse, welcher registriert werden soll
  48.  */
  49. function import($class) {
  50.  
  51. 	$cl = ClassLoader :: singleton();
  52. 	$cl->registerClass($class);
  53.  
  54. }
  55.  
  56. /**
  57.  * Durch diese Funktion können alle registrierten
  58.  * Klassen automatisch geladen werden. Wenn ein
  59.  * Objekt instanziert wird, wird der Funktion
  60.  * der Objektname übergeben. Anhand des Objektnamens
  61.  * und der registrierten Objekte kann der Pfad
  62.  * ausgemacht werden und die Klasse wird automatisch
  63.  * geladen.
  64.  *
  65.  * @param String class Der Typ des Objektes, welcher instanziert wird
  66.  */
  67. function __autoload($class) {
  68.  
  69. 	$cl = ClassLoader :: singleton();
  70. 	$cl->loadClass($class);
  71.  
  72. }
  73.  
  74. package("php.core.lang");
  75.  
  76. /**
  77.  * Diese Klasse kümmert sich um das automatische
  78.  * laden von Klassen. Dazu müssen die Klassen
  79.  * und Pakete jedoch erst registriert werden.
  80.  * Den rest übernimmt diese Klasse.
  81.  * Es sollte reichen nur diese Datei einzubinden.
  82.  * Um den rest kümmert sich ClassLoader.
  83.  */
  84. class ClassLoader {
  85.  
  86. 	/***************** Typen *****************************************/
  87.  
  88. 	/***************** Konstanten ************************************/
  89.  
  90. 	/**
  91. 	 * Nur in dem ClassLoader muss eine Konstante
  92. 	 * package gesetzt sein. Über diese Konstante
  93. 	 * erfasst eine Methode den Basis Pfad
  94. 	 */
  95. 	const PACKAGE = "php.core.lang";
  96.  
  97. 	/**
  98. 	 * Durch diesen Teiler werden Klassen und
  99. 	 * Pakete voneinander getrennt
  100. 	 */
  101. 	const CLASSSEPARATOR = '.';
  102.  
  103. 	/**
  104. 	 * Durch den / einzelne Pfade. Jedoch sollte
  105. 	 * dies auch anders gehen. Sobald ich es raus
  106. 	 * bekomme, werde ich es ändern! ;)
  107. 	 */
  108. 	const FILESEPARATOR = '/';
  109.  
  110. 	/***************** Aufzählungen **********************************/
  111.  
  112. 	/***************** Member ****************************************/
  113.  
  114. 	/**
  115. 	 * Der Basis Pfad. Von dort aus werden alle
  116. 	 * Klassen geladen. Die Pakete geben die
  117. 	 * Ordnerstruktur an. Die Klassennamen den
  118. 	 * Dateinamen.
  119. 	 */
  120. 	private $basePath;
  121.  
  122. 	/***************** Static Member *********************************/
  123.  
  124. 	/**
  125. 	 * Eine statische Instanz. Damit ich immer
  126. 	 * wieder auf das gleiche Objekt arbeite.
  127. 	 */
  128. 	private static $instance = __CLASS__;
  129.  
  130. 	/**
  131. 	 * Hier werden die Klassennamen und die
  132. 	 * Dateien, welche eingebunden werden
  133. 	 * aufgelistet
  134. 	 */
  135. 	private $classes = array ();
  136.  
  137. 	/**
  138. 	 * Hier werden die schon registrierten
  139. 	 * Pakete gespeichert. Somit braucht
  140. 	 * nicht nochmal in dem Ordner geschaut
  141. 	 * werden, welche Klassen schon vorhanden
  142. 	 * sind
  143. 	 */
  144. 	private $regPackages = array ();
  145.  
  146. 	/***************** Getter und Setter *****************************/
  147.  
  148. 	/***************** Private Methoden ******************************/
  149.  
  150. 	/**
  151. 	 * Generiert den Basis Pfad anhand der eigenen
  152. 	 * Datei und der package - Konstante
  153. 	 */
  154. 	private function createBasePath() {
  155.  
  156. 		if (isset ($this->classPath)) {
  157. 			return;
  158. 		}
  159.  
  160. 		// Ich zerteile das Paket, um mir den Basis Pfad geben zu lassen
  161. 		$packages = explode(self :: CLASSSEPARATOR, self :: PACKAGE);
  162.  
  163. 		// Ich brauche das aktuelle Verzeichnis des Scriptes
  164. 		$path = substr(__FILE__, 0, strrpos(__FILE__, self :: FILESEPARATOR));
  165.  
  166. 		for ($i = (count($packages) - 1); $i >= 0; $i--) {
  167.  
  168. 			// Es wird geprüft, ob der Pfad auch dem Paket entspricht
  169. 			$pos = strrpos($path, self :: FILESEPARATOR);
  170. 			$tmpPackage = substr($path, $pos +strlen(self :: FILESEPARATOR));
  171.  
  172. 			if ($tmpPackage != $packages[$i]) {
  173. 				throw new Exception("Invalid package atom. ( " . $tmpPackage . " != " . $packages[$i] . " )", 1);
  174. 			}
  175.  
  176. 			$path = substr($path, 0, $pos);
  177.  
  178. 		}
  179.  
  180. 		// Und speichern
  181. 		$this->basePath = $path;
  182.  
  183. 	}
  184.  
  185. 	/***************** Proteced Methoden *****************************/
  186.  
  187. 	/***************** Public Methoden *******************************/
  188.  
  189. 	/**
  190. 	 * Registriert ein gesammtes Paket. Dazu wird
  191. 	 * zu dem Pfad gegangen und jede einzelne Datei
  192. 	 * wird untersucht. Ist es eine PHP Datei und
  193. 	 * ist diese Klasse noch nicht registriert, wird
  194. 	 * sie registriert.
  195. 	 *
  196. 	 * @param String package Das angegebene Paket wird registriert.
  197. 	 */
  198. 	public function registerPackage($package) {
  199.  
  200. 		if (in_array($package, $this->regPackages, true) == true) {
  201. 			// Dieses Paket wurde schon geladen. es muss nicht nochmal gesucht werden
  202. 			return;
  203. 		}
  204.  
  205. 		// Ich lasse mir den Pfad des Packages geben
  206. 		$path = $this->basePath . self :: FILESEPARATOR . str_replace(self :: CLASSSEPARATOR, self :: FILESEPARATOR, $package) . self :: FILESEPARATOR;
  207.  
  208. 		if (!is_dir($path)) {
  209. 			throw new Exception("Package \"" . $package . "\" does not exist.", 3);
  210. 		}
  211.  
  212. 		$directory = dir($path);
  213.  
  214. 		while (false !== ($file = $directory->read())) {
  215.  
  216. 			// Wenn es keine Datei ist, wird es übersprungen
  217. 			if (!is_file($directory->path . $file)) {
  218. 				continue;
  219. 			}
  220.  
  221. 			// Wenn es keine PHP Datei ist
  222. 			if (substr($file, strlen($file) - 4) != ".php") {
  223. 				continue;
  224. 			}
  225.  
  226. 			$className = substr($file, 0, -4);
  227. 			// Wenn die Klasse schon geladen wurde, dann kann bei der nächsten weiter gemacht werden
  228. 			if (array_key_exists($className, $this->classes)) {
  229. 				continue;
  230. 			}
  231.  
  232. 			if (!file_exists($directory->path . $file)) {
  233. 				continue;
  234. 			}
  235.  
  236. 			$this->classes[$className] = $directory->path . $file;
  237.  
  238. 		}
  239.  
  240. 		$directory->close();
  241.  
  242. 		$this->regPackages[] = $package;
  243.  
  244. 	}
  245.  
  246. 	/**
  247. 	 * Eine einzelne Klasse wird registriert.
  248. 	 *
  249. 	 * @param String class Diese Klasse wird registriert
  250. 	 */
  251. 	public function registerClass($class) {
  252.  
  253. 		// Wenn es sich um eine einzelne Datei handelt, kann ich die einzeln registrieren
  254. 		if (substr($class, -1 * (strlen(self :: CLASSSEPARATOR) + 1)) !== self :: CLASSSEPARATOR . '*') {
  255.  
  256. 			$className = substr($class, strrpos($class, self :: CLASSSEPARATOR) + 1, strlen($class));
  257. 			if (array_key_exists($className, $this->classes)) {
  258. 				return;
  259. 			}
  260.  
  261. 			$file = $this->basePath . self :: FILESEPARATOR . str_replace(self :: CLASSSEPARATOR, self :: FILESEPARATOR, $class) . ".php";
  262. 			if (!file_exists($file)) {
  263. 				throw new Exception("Class \"" . $class . "\" does not exist.", 2);
  264. 			}
  265.  
  266. 			// Und rein damit
  267. 			$this->classes[$className] = $file;
  268. 			return;
  269.  
  270. 		}
  271.  
  272. 		$this->registerPackage(substr($class, 0, strlen($class) - strlen(self :: CLASSSEPARATOR) - 1));
  273.  
  274. 	}
  275.  
  276. 	/**
  277. 	 * Gibt alle Klassen inerhalb eines Paketes aus.
  278. 	 *
  279. 	 * @param String package Des Paket aus welchem die Klassen ermittelt werden
  280. 	 * @throws Exception wenn das Paket nicht existiert
  281. 	 */
  282. 	public function getClassesFromPackage($package) {
  283.  
  284. 		$packagePath = $this->basePath . self :: FILESEPARATOR . str_replace(self :: CLASSSEPARATOR, self :: FILESEPARATOR, $package) . self :: FILESEPARATOR;
  285.  
  286. 		if (!is_dir($packagePath)) {
  287. 			throw new Exception("Package \"" . $package . "\" does not exist.", 3);
  288. 		}
  289.  
  290. 		// Erstelle mir das array und geh in den Pfad
  291. 		$classesInPackage = array ();
  292. 		$dir = dir($packagePath);
  293.  
  294. 		while (false !== ($file = $dir->read())) {
  295. 			if (!is_file($dir->path . $file)) {
  296. 				continue;
  297. 			}
  298.  
  299. 			if (substr($file, -4) !== ".php") {
  300. 				continue;
  301. 			}
  302.  
  303. 			if (!file_exists($dir->path . $file)) {
  304. 				continue;
  305. 			}
  306.  
  307. 			$classesInPackage[] = substr($file, 0, -4);
  308. 		}
  309.  
  310. 		$dir->close();
  311.  
  312. 		return $classesInPackage;
  313.  
  314. 	}
  315.  
  316. 	/**
  317. 	 * Gibt alle registrierten Klassen aus.
  318. 	 */
  319. 	public function showClasses() {
  320.  
  321. 		printf("<pre>");
  322. 		print_r($this->classes);
  323. 		printf("</pre>");
  324.  
  325. 	}
  326.  
  327. 	/**
  328. 	 * Sucht in der Registrierung nach dem
  329. 	 * Klassennamen und bindet diese dann ein.
  330. 	 * Es wird weder Erfolg noch Misserfolg zurück
  331. 	 * gegeben. Da require selbst einen Fehler wirft.
  332. 	 *
  333. 	 * @param String class Diese Klasse wird geladen
  334. 	 * @throws Exception Wenn die angegebene Klasse nicht existiert
  335. 	 */
  336. 	public function loadClass($class) {
  337.  
  338. 		// Wenn der Schlüssel vorhanden ist, dann lade ich die passende Datei dazu!
  339. 		if (array_key_exists($class, $this->classes)) {
  340. 			require_once ($this->classes[$class]);
  341. 		} else {
  342. 			throw new Exception("Class \"" . $class . "\" does not exist.", 2);
  343. 		}
  344.  
  345. 	}
  346.  
  347. 	/***************** Konstruktoren *********************************/
  348.  
  349. 	/**
  350. 	 * Der Konstruktor. Hier wird der
  351. 	 * Basis Pfad generiert.
  352. 	 */
  353. 	private function __construct() {
  354.  
  355. 		$this->createBasePath();
  356.  
  357. 	}
  358.  
  359. 	/***************** Static Methoden *******************************/
  360.  
  361. 	/**
  362. 	 * Gibt mir die statische Instanz oder instanziert diese.
  363. 	 */
  364. 	public static function singleton() {
  365.  
  366. 		return is_object(self :: $instance) ? self :: $instance : (self :: $instance = new self :: $instance);
  367.  
  368. 	}
  369.  
  370. }
  371. ?>
Persönliche Werkzeuge