{"id":2147,"date":"2015-06-25T03:00:26","date_gmt":"2015-06-25T02:00:26","guid":{"rendered":"https:\/\/u-labs.de\/blog\/?p=2147"},"modified":"2015-06-26T03:21:47","modified_gmt":"2015-06-26T02:21:47","slug":"autoload-php-klassen-automatisch-laden","status":"publish","type":"post","link":"https:\/\/u-labs.de\/portal\/autoload-php-klassen-automatisch-laden\/","title":{"rendered":"Autoload: PHP-Klassen ohne includes automatisch laden"},"content":{"rendered":"<p>Viele PHP-Entwickler d\u00fcrften mit dem Befehl <a href=\"http:\/\/php.net\/manual\/de\/function.require-once.php\" target=\"_blank\" rel=\"nofollow\">require_once<\/a> ausreichend und regelm\u00e4\u00dfig Bekanntschaft machen: M\u00f6chte man eine Klasse einbinden die sich in einer eigenen Datei befindet (etwa\u00a0<strong>class_user.php<\/strong>), f\u00fchrte fr\u00fcher kein Weg daran vorbei, diese vorher selbst einzubinden. Ansonsten fliegt einem die Webanwendung um die Ohren, weil man auf eine Klasse zugreift, die PHP \u00fcberhaupt nicht kennt. Zumindest bei der ersten Verwendung einer Klasse war daher ein Konstrukt wie das folgende unumg\u00e4nglich:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\nrequire_once DIR . '\/includes\/class_user.php';\r\n$user = new User();\r\n<\/pre>\n<p>Im Gegensatz zur Funktion <a href=\"http:\/\/php.net\/manual\/de\/function.require.php\" target=\"_blank\" rel=\"nofollow\">require<\/a> muss der Entwickler zwar nicht mehr h\u00f6llisch aufpassen, blo\u00df nicht aus versehen eine Datei zweimal einzubinden. Denn auch das nimmt einem PHP \u00fcbel. Dennoch verursacht das st\u00e4ndige einbinden Arbeit und bl\u00e4ht den Code unn\u00f6tig auf, ohne einen Mehrwert zu bieten.<\/p>\n<p>PHP hat das Problem erkannt und bereits in Version 5.1.2 ein tolles Feature nachger\u00fcstet: Die Funktion <a href=\"http:\/\/php.net\/manual\/de\/function.spl-autoload-register.php\" target=\"_blank\" rel=\"nofollow\">spl_autoload_register<\/a>.\u00a0Mit ihr lassen sich Klassen automatisch einbinden &#8211; und zwar erst dann, wenn sie gebraucht werden. Die Funktionsweise ist einfach: Man registriert einen Handler, der aufgerufen wird, wenn eine Klasse ben\u00f6tigt wird (etwa durch eine Instanziierung). Dieser Handler kann die ben\u00f6tigte Datei dann laden. Im einfachsten Fall l\u00e4sst sich dies mit lediglich 3 Zeilen Code realisieren:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\nspl_autoload_register(function ( $className ) {\r\n    include 'includes\/class_' . $className . '.php';\r\n});\r\n<\/pre>\n<p>Warum also nicht eine universelle Klasse daf\u00fcr entwickeln, die sich in verschiedenen Projekten mit unterschiedlichen Namenskonvensationen nutzen l\u00e4sst?<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\n\r\n\/**\r\n * L\u00e4d Klassen automatisch anhand bestimmter Regeln\r\n *\/\r\nclass AutoLoader {\r\n    private $loadRules = array();\r\n    private $loadedClasses = array();\r\n\r\n    \/**\r\n     * Gibt einen Array zur\u00fcck der die Pfade aller geladenen Dateien enth\u00e4lt (F\u00fcr Debugzwecke)\r\n     * @return array\r\n     *\/\r\n    public function getLoadedClasses() {\r\n        return $this-&gt;loadedClasses;\r\n    }\r\n\r\n    \/**\r\n     * Definiert eine Regel, anhand dieser Dateien geladen werden\r\n     * @param      $beforeClassName     Pr\u00e4fix f\u00fcr den Dateinamen (z.B. &quot;class_&quot;)\r\n     * @param      $afterClassName      Ende des Dateinamens inklusive Erweiterung (z.B. &quot;.php&quot;)\r\n     * @param      $folder              Voller Pfad des Ordners, in dem die Datei gesucht werden soll\r\n     * @param bool $recursive           Gibt an ob nur in $folder gesucht werden soll oder auch rekursiv in Unterordnern von $folder\r\n     *\/\r\n    public function addLoadRule( $beforeClassName, $afterClassName, $folder, $recursive = true) {\r\n        $this-&gt;loadRules&#x5B;] = array(\r\n            'beforeClassName' =&gt; $beforeClassName,\r\n            'afterClassName' =&gt; $afterClassName,\r\n            'folder' =&gt; $folder,\r\n            'recursive' =&gt; $recursive\r\n        );\r\n    }\r\n\r\n    \/**\r\n     * Startet den AutoLoader, sodass ab diesem Zeitpunkt alle verwendeten Klassen die nicht geladen wurden \u00fcber die Regeln des AutoLoaders geladen werden\r\n     *\/\r\n    public function startWork() {\r\n        spl_autoload_register( array( $this, 'loadClass') );\r\n    }\r\n\r\n    \/**\r\n     * L\u00e4d eine von spl_autoload_register angeforderte Klasse\r\n     * @param $className    Name der zu ladenden Klasse (Gro\u00df\/Kleinschreibung wird ignoriert)\r\n     *\/\r\n    private function loadClass( $className ) {\r\n        foreach( $this-&gt;loadRules as $loadRule ) {\r\n            \/\/ Dateiname ermitteln und Datei laden\r\n            $fileName = $loadRule&#x5B;'beforeClassName'] . strtolower( $className ) . $loadRule&#x5B;'afterClassName'];\r\n            $fullFilePath = self::searchFile( $loadRule&#x5B;'folder'], $fileName, $loadRule&#x5B;'recursive'] );\r\n            \/\/ Datei einbinden, sofern sie gefunden wurde\r\n            if( false !== $fullFilePath) {\r\n                $this-&gt;loadedClasses&#x5B;] = $fullFilePath;\r\n                require_once $fullFilePath;\r\n            }\r\n        }\r\n    }\r\n\r\n    \/**\r\n     * Durchsucht einen Ordner nach einer Datei\r\n     * @param      $dir         Vollst\u00e4ndiger Pfad des Ordners, der durchsucht werden soll\r\n     * @param      $fileName    Name der Datei, die gesucht wird\r\n     * @param bool $recursive   Gibt an ob nur in $dir gesucht wird oder auch rekursiv in dessen Unterordnern\r\n     * @return bool|string      Vollst\u00e4ndiger Pfad der gefundenen Datei oder FALSE, wenn die Suche kein Ergebnis lieferte\r\n     *\/\r\n    public static function searchFile( $dir, $fileName, $recursive = true) {\r\n        \/\/ Symlinks f\u00fcr \u00fcbergeordnete Verzeichnisse entfernen\r\n        $files = array_diff( scandir( $dir ), array( '.', '..' ) );\r\n        foreach( $files as $file ) {\r\n            $fullPath = $dir . '\/' . $file;\r\n            if( is_dir($fullPath) &amp;&amp; $recursive ) {\r\n                \/\/ Es handelt sich um einen Unterordner: Rekursiv scannen, sofern gew\u00fcnscht\r\n                $subdirScanResult = self::searchFile( $fullPath, $fileName );\r\n                if( false !== $subdirScanResult ) {\r\n                    return $subdirScanResult;\r\n                }\r\n            }else {\r\n                \/\/ Es handelt sich um eine Datei: Entspricht diese der gesuchten?\r\n                if( strtolower( $file ) == strtolower( $fileName) ){\r\n                    return $fullPath;\r\n                }\r\n            }\r\n        }\r\n        \/\/ Keine Datei gefunden\r\n        return false;\r\n    }\r\n}\r\n<\/pre>\n<p>Die Klasse <strong>AutoLoader<\/strong>\u00a0ist nahezu \u00fcberall anwendbar, da verschiedene Ordnerstrukturen und Namenskonventionen anhand von Regeln hinterlegt werden k\u00f6nnen. Werden PHP-Dateien beispielsweise nach dem Schema\u00a0<strong>class_{$KLASSENNAME}.php<\/strong> im Ordner\u00a0<strong>include<\/strong> gespeichert, gen\u00fcgt folgender Aufruf zu Beginn der Anwendung:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\ndefine( 'BASE_DIR', __DIR__ );\r\nrequire_once INCLUDE_DIR . '\/AutoLoader.php';\r\n$autoLoader = new Autoloader();\r\n$autoLoader-&gt;addLoadRule( 'class_', '.php', INCLUDE_DIR );\r\n<\/pre>\n<p>Der AutoLoader selbst muss nat\u00fcrlich noch manuell eingebunden werden. Aber danach kann ben\u00f6tigt man dank der Regeln nahezu keine Include oder Require-Once Anweisungen mehr. Die Funktion\u00a0<strong>addLoadRule()<\/strong> akzeptiert als 1. Parameter ein Pr\u00e4fix wie etwa\u00a0<strong>class_<\/strong> oder\u00a0<strong>Class<\/strong>. Im 2. Parameter folgt das Ende des Dateinamens. So lassen sich auch Namenskonventionen wie\u00a0<strong>user.class.php<\/strong> abdecken, in dem man hier\u00a0<strong>.class.php<\/strong> \u00fcbergibt. Schlussendlich wird im letzten Parameter der Ordner \u00fcbergeben, in dem sich die Dateien befinden. Dieser wird automatisch rekursiv durchsucht, also inklusive Unterordner. Die Datei <strong>includes\/database\/database.php<\/strong> wird also auch dann gefunden, wenn lediglich <strong>includes<\/strong> als Suchordner \u00fcbergeben wird. Gro\u00df-Kleinschreibung wird nicht ber\u00fccksichtigt, damit die Klasse auch z.B. f\u00fcr das dynamische Laden von Controllern (MVC) genutzt werden kann.<\/p>\n<p>Nat\u00fcrlich kann man durch mehrfaches Aufrufen von\u00a0<strong>addLoadRule()<\/strong> verschiedene Regeln definieren. Beispielsweise eine f\u00fcr Views und eine f\u00fcr Controller, wenn man nach dem MVC-Muster entwickelt. Richtig eingesetzt kann man das Thema <strong>Einbinden von Dateien<\/strong> damit abhaken. Wichtig ist nat\u00fcrlich, dass eine Namenskonvention f\u00fcr Dateinamen besteht und die Verzeichnisstruktur einigerma\u00dfen vern\u00fcnftig eingerichtet wurde.<\/p>\n<p>Die fertige Klasse kann alternativ auch als PHP-Datei hier heruntergeladen werden: <a href=\"https:\/\/u-labs.de\/wp-content\/uploads\/2015\/06\/AutoLoader.zip\">AutoLoader.zip herunterladen<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Viele PHP-Entwickler d\u00fcrften mit dem Befehl require_once ausreichend und regelm\u00e4\u00dfig Bekanntschaft machen: M\u00f6chte man eine Klasse einbinden die sich in einer eigenen Datei befindet (etwa\u00a0class_user.php), f\u00fchrte fr\u00fcher kein Weg daran vorbei, diese vorher selbst einzubinden. Ansonsten fliegt einem die Webanwendung um die Ohren, weil man auf eine Klasse zugreift, die PHP \u00fcberhaupt nicht kennt. Zumindest &#8230;<\/p>\n","protected":false},"author":5,"featured_media":2184,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[61],"tags":[155,156,55,158,157],"class_list":["post-2147","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-softwareentwicklung","tag-autoload","tag-include","tag-php","tag-php5","tag-require_once"],"_links":{"self":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/2147","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/comments?post=2147"}],"version-history":[{"count":14,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/2147\/revisions"}],"predecessor-version":[{"id":2525,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/2147\/revisions\/2525"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/media\/2184"}],"wp:attachment":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/media?parent=2147"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/categories?post=2147"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/tags?post=2147"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}