{"id":271,"date":"2015-07-22T08:43:16","date_gmt":"2015-07-22T06:43:16","guid":{"rendered":"http:\/\/staratnight.de\/blog\/?p=271"},"modified":"2018-12-02T16:44:20","modified_gmt":"2018-12-02T14:44:20","slug":"autoload-in-php","status":"publish","type":"post","link":"https:\/\/staratnight.de\/blog\/autoload-in-php\/","title":{"rendered":"Autoload in PHP"},"content":{"rendered":"<p>Wie man meinen Artikeln in den letzten Wochen entnehmen kann, befasse ich mich derzeit mit der Entwicklung von PHP. PHP habe ich schon fr\u00fcher programmiert, sei es Erweiterungen f\u00fcr das von Wikipedia bekannte Mediawiki oder diverse Webseiten, etwa die des Unternehmens, in dem ich arbeite oder meine eigene.<\/p>\n<p>Zuletzt verwendete ich PHP haupts\u00e4chlich dazu, Daten in einer MySQL-Datenbank, auf meinem Web-Space nach au\u00dfen hin zug\u00e4nglich zu machen. Z.B. habe ich ein C#-Programm, mit dem ich Koch-Rezepte verwalten kann. Die Daten liegen in einer MySQL-Datenbank und ich rufe sie \u00fcber einen Rest-Service ab.<\/p>\n<p>&nbsp;<\/p>\n<h1>Sch\u00f6ner Code mit PHP?<\/h1>\n<p>Diese Schnittstelle galt es nun noch einmal neu zu machen, da ich den Code so wie er war nicht mehr zum laufen bekommen hatte (das lag an einer Dritthersteller-Komponente, dessen Funktionsweise ich wohl vergessen hatte).<\/p>\n<p>Diesmal wollte ich aber nicht wieder einfach PHP-Seiten erstellen und tonnenweise Funktionen da rein packen. Funktionsorientierte Programmierung gef\u00e4llt mir mittlerweile gar nicht mehr. Das muss doch besser gehen!<\/p>\n<p>Auf den richtigen Weg hat mich die Seite <a href=\"http:\/\/www.phptherightway.com\/\">PHP: the right way<\/a> gebracht. Hier wird erkl\u00e4rt, wie man eine PHP-Anwendung sauber und objektorientiert strukturiert. Hier hat mir insbesondere der Abschnitt <a href=\"http:\/\/www.phptherightway.com\/#namespaces\">Namespaces<\/a> gefallen. Die l\u00e4stig langen Listen von <em>include <\/em>oder<em> require <\/em>am Anfang von Quellcode-Dateien geh\u00f6ren damit der Vergangenheit an, wir arbeiten jetzt nur noch mit <em>use<\/em> auf Basis von Namespaces, was im Grunde einem <em>import<\/em> in Java oder <em>using<\/em> in .Net entspricht.<!--more--><\/p>\n<p>&nbsp;<\/p>\n<h1>Was ist Autoload?<\/h1>\n<p>Autoload ist im Grunde ein Konzept, mit dessen Hilfe es m\u00f6glich ist, PHP-Dateien aus einem Namespace automatisch zu laden, wenn diese zur Laufzeit noch unbekannt sind. Das entspricht in etwa dem Konzept der Dependency Injection. Wir m\u00fcssen uns nicht mehr darum k\u00fcmmern, woher eine Klasse kommt, sie ist einfach da. Der Unterschied ist nur, dass wir sie hier noch selbst instanzieren m\u00fcssen.<\/p>\n<p>&nbsp;<\/p>\n<h1>Wie funktioniert Autoload?<\/h1>\n<p>Seit PHP5 gibt es die sogenannte <a href=\"http:\/\/php.net\/manual\/de\/language.oop5.autoload.php\">Autoload-Funktion<\/a>. Hier\u00fcber lassen sich Klassen laden, wenn PHP diese zum Zeitpunkt der Verwendung noch nicht kennt.<\/p>\n<p>In PHP 5.1.2 ist nun eine Regsitrierung f\u00fcr Autoloader dazugekommen (<a href=\"http:\/\/php.net\/manual\/de\/function.spl-autoload-register.php\">spl_autoload_register<\/a>). Dadurch ist man nicht mehr darauf beschr\u00e4nkt, einen Autoloader zu nutzen, sondern kann verschiedene Autoloader registrieren, die dann alle verwendet werden, wenn eine Klasse geladen werden soll.<\/p>\n<p>Durch diese Erweiterung ist Autoload ein m\u00e4chtiges Werkzeug, um Ordnung in das Chaos der PHP-Dateien zu bekommen. Dadurch lassen sich nun z.B. f\u00fcr alle verwendeten Bibliotheken eigene Autoloader registrieren (welche diese in aller Regel mitbringen) und man muss sich darum schon nicht mehr selbst k\u00fcmmen.<\/p>\n<p>Wir brauchen also nur noch einen Autoloader f\u00fcr unsere eigenen Klassen. Aber auch das hat mich einige Zeit gekostet, dahinter zu kommen, wie dies sauber funktioniert. Eine sehr gro\u00dfe Hilfe war der <a href=\"http:\/\/jes.st\/2011\/phpunit-bootstrap-and-autoloading-classes\/\">Artikel von Jess Telford<\/a> in seinem Blog. Auf diesem Artikel basiert auch das folgende Beispiel.<\/p>\n<p>&nbsp;<\/p>\n<h1>Beispiel<\/h1>\n<p>Nehmen wir als Beispiel das oben genannte Projekt, in dem Kochrezepte gespeichert werden. Wir haben eine Klasse <em>EntityRepository<\/em> (das sind Messeinheiten, etwa Liter oder Gramm, f\u00fcr die Angabe zu einer Zutat) in einer eigenen PHP-Datei. Diese ben\u00f6tigt das Model <em>Entity <\/em>und die Klasse <em>PDO<\/em> f\u00fcr den Zugriff auf die Datenbank (darauf kommt ich sp\u00e4ter in einem anderen Artikel nochmal drauf zur\u00fcck).<\/p>\n<h2><\/h2>\n<h2>Fr\u00fcher war alles Besser?<\/h2>\n<p>Nach dem alten Verfahren w\u00fcrde die Datei in etwa so aussehen:<\/p>\n<pre class=\"lang:php decode:true\" title=\"EntityRepository.php\">&lt;?php\r\n\r\ninclude \"model\\Entity.php\"\r\n\r\nclass EntityRepository\r\n{\r\n\r\n    \/**\r\n     * Retrieves the entity from the database by id.\r\n     *\r\n     * @param PDO $con connection to the database\r\n     * @param int $id The id of the Entity\r\n     *\r\n     * @return Entity The Entity found.\r\n     *\/\r\n    public function findEntityById(PDO $con, $id)\r\n    {\r\n        [...]\r\n    }\r\n[...]<\/pre>\n<p>W\u00fcrden wir nun ein paar Klassen in einer anderen ben\u00f6tigen, m\u00fcssten wir all diese in include-Anweisungen angeben. Die Liste kann mitunter sehr lang werden.<\/p>\n<p>&nbsp;<\/p>\n<h2>Der AutoLoader<\/h2>\n<p>Schreiben und registrieren wir nun also einen Autoloader.<\/p>\n<pre class=\"lang:php decode:true\" title=\"AutoLoad.php\">&lt;?php\r\n\r\nclass AutoLoader\r\n{\r\n\r\n    static private $classNames = array();\r\n\r\n    \/**\r\n     * Store the filename (without extension) &amp; full path of all \".php\" files found\r\n     *\/\r\n    public static function registerDirectory($dirName)\r\n    {\r\n\r\n        $di = new DirectoryIterator($dirName);\r\n        foreach ($di as $file) {\r\n\r\n            if ($file-&gt;isDir() &amp;&amp; !$file-&gt;isLink() &amp;&amp; !$file-&gt;isDot()) {\r\n                \/\/ recurse into directories other than a few special ones\r\n                self::registerDirectory($file-&gt;getPathname());\r\n            } elseif (substr($file-&gt;getFilename(), -4) === '.php') {\r\n                \/\/ save the class name \/ path of a .php file found\r\n                $className = substr($file-&gt;getPathname(), 0, -4);\r\n                AutoLoader::registerClass($className, $file-&gt;getPathname());\r\n            }\r\n        }\r\n    }\r\n\r\n    private static function registerClass($className, $fileName)\r\n    {\r\n        AutoLoader::$classNames[$className] = $fileName;\r\n    }\r\n\r\n    public static function loadClass($className)\r\n    {\r\n        if (isset(AutoLoader::$classNames[$className])) {\r\n            require_once(AutoLoader::$classNames[$className]);\r\n        }\r\n    }\r\n}\r\n\r\n\/\/ Register auto loader\r\nspl_autoload_register(array('AutoLoader', 'loadClass'));<\/pre>\n<p>&nbsp;<\/p>\n<p>\u00dcber die Methode <em>registerDirectory(&#8230;)<\/em> k\u00f6nnen wir dem Autoloader Verzeichnisse \u00fcbergeben, in denen er zur Laufzeit nach neuen Klassen suchen soll. Zur Laufzeit selbst wird dann, wenn PHP eine Klasse noch nicht kennt, die Methode <em>loadClass(&#8230;)<\/em> aufgerufen, um die Klasse zu laden.<\/p>\n<p>Die Klasse AutoLoader speichert alle registrierten Klassen in einem <a href=\"http:\/\/php.net\/manual\/de\/language.types.array.php\">assoziativen Array<\/a> ab. Dabei wird der vollqualifizierte Name der Klasse registriert.<\/p>\n<p>&nbsp;<\/p>\n<h2>Den Autoloader konfigurieren<\/h2>\n<p>Als n\u00e4chstes m\u00fcssen wir den AutoLoader noch mit den Verzeichnissen f\u00fcttern, in denen unsere Klassen liegen. Das erfolgt ganz klassisch \u00fcber einen PHP-include:<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"lang:php decode:true \" title=\"autoloadConfiguration.php\">&lt;?php\r\nrequire 'AutoLoader.php';\r\n\r\nAutoLoader::registerDirectory('dispatcher');\r\nAutoLoader::registerDirectory('json');\r\nAutoLoader::registerDirectory('model');\r\nAutoLoader::registerDirectory('repository');\r\n<\/pre>\n<p>Ich habe in meinem Beispiel also vier Verzeichnisse, in denen der Autoloader nach Klassen suchen soll. Der Autoloader merkt sich dann alle PHP-Dateien aus allen angegebenen Verzeichnisse.<\/p>\n<p>Kommt ein neues Verzeichnis hinzu, muss dies ebenfalls einmalig registriert werden.<\/p>\n<p>Diese Konfigurationsdatei muss nat\u00fcrlich auch einmal per <em>include<\/em> eingebunden werden, etwa in der <em>index.php<\/em>.<\/p>\n<p>&nbsp;<\/p>\n<h2>Der AutoLoader in Aktion<\/h2>\n<p>Habe ich den AutoLoader einmalig konfiguriert, stehen mir alle Klassen unter dem vollqualifizierten Namen zur Verf\u00fcgung. Um diese festzulegen, nutze ich Namespaces.<\/p>\n<p>Als Beispiel die Entity-Klasse von oben:<\/p>\n<pre class=\"lang:php decode:true \" title=\"Entity.php\">&lt;?php namespace model;\r\n\r\nclass Entity\r\n{\r\n    [...]\r\n}<\/pre>\n<p>Die Klasse <em>Entity<\/em> liegt im Verzeichnis <em>model<\/em>. Durch die Angabe des Namespace <em>model<\/em> ist die Klasse nun unter dem vollqualifizierten Namen <em>model\\Entity<\/em> zu finden. Verwenden wir in PHP Namespaces, verwendet PHP bei der Suche nach der Klasse immer die vollqualifizierten Namen.<\/p>\n<p>&nbsp;<\/p>\n<p>Unsere Klasse <em>EntityRepository<\/em> sieht also nun so aus:<\/p>\n<pre class=\"lang:php decode:true\" title=\"EntityRepository.php\">&lt;?php namespace repository;\r\n\r\nuse model\\Entity;\r\n\r\nclass EntityRepository\r\n{\r\n\r\n    \/**\r\n     * Retrieves the entity from the database by id.\r\n     *\r\n     * @param PDO $con connection to the database\r\n     * @param int $id The id of the Entity\r\n     *\r\n     * @return Entity The Entity found.\r\n     *\/\r\n    public function findEntityById(PDO $con, $id)\r\n    {\r\n        [...]\r\n    }\r\n[...]<\/pre>\n<p>Ich kann das include weglassen, ich muss nicht wissen, wo sich die Klasse befindet. Verschiebe ich die Klasse, findet der AutoLoader sie trotzdem und ich muss nicht alle include-Anweisungen korrigieren.<\/p>\n<p><em>Hinweis:<\/em> Ich verwende hier noch use f\u00fcr die <em>Entity<\/em>-Klasse. Der Grund ist dabei nicht, dass PHP die Klasse nicht auch so finden w\u00fcrde. Der Grund ist, dass mir IntelliJ dann sehr viel Unterst\u00fctzung bietet, was Code-Vervollst\u00e4ndigung und Pr\u00fcfung angeht.<\/p>\n<p>&nbsp;<\/p>\n<h1>Verzeichnisstruktur<\/h1>\n<p>Ein Problem das ich bei der Besch\u00e4ftigung mit dem Thema noch hatte ist der der Verzeichnisstruktur. Wir baue ich die Verzeichnisse sauber auf, so, dass nachher auch noch alles funktioniert?<\/p>\n<p>Hier mal ein Beispiel:<\/p>\n<ul>\n<li>\\dispatcher<br \/>\nFunktionen zum Routing innerhalb der Anwendung<\/li>\n<li>\\json<br \/>\nKlassen, die den Umgang mit JSON erm\u00f6glichen oder vereinfachen<\/li>\n<li>\\model<br \/>\nDie Models der Anwendung, also <em>Recipe<\/em>, <em>Ingredient<\/em>, &#8230;<\/li>\n<li>\\repository<br \/>\nDie Repositories f\u00fcr den Zugriff auf die Daten<\/li>\n<li>\\test<br \/>\nUnit- und E2E-Tests<\/li>\n<li>\\vendor<br \/>\nExterne Bibliotheken, die mithilfe von Composer hinzugef\u00fcgt werden. \\vendor ist das Standardverzeichnis f\u00fcr Composer-Abh\u00e4ngigkeiten.<\/li>\n<li>autoloadConfiguration.php<br \/>\nDie oben beschriebene Konfiguration es AutoLoaders.<\/li>\n<li>AutoLoader.php<br \/>\nDie AutoLoader-Klasse selbst<\/li>\n<li>composer.json<br \/>\nDie Konfiguration von Composer<\/li>\n<li>index.php<br \/>\nUnsere Einstiegsseite. Meine sieht f\u00fcr obiges Projekt recht \u00fcberschaubar aus:<\/p>\n<pre class=\"lang:default decode:true\" title=\"index.php\">&lt;?php\r\n\r\nrequire 'vendor\/autoload.php';\r\nrequire 'AutoLoader.php';\r\nrequire 'autoloadConfiguration.php';\r\n\r\ninclude_once 'dispatcher\/slimDispatcher.php';<\/pre>\n<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p>Diese Struktur funktioniert f\u00fcr mich sehr gut. Vielleicht hilft es ja dem ein oder anderen da drau\u00dfen.<\/p>\n<p>&nbsp;<\/p>\n<h1>Fazit<\/h1>\n<p>Mit der Autoload-Funktionalit\u00e4t bekommen wir PHP-Entwickler eine gute, flexible Methodik an die Hand, um unsere Klassen ordentlich zu strukturieren. Wir m\u00fcssen uns an ein paar Regeln halten (die im <a href=\"https:\/\/github.com\/php-fig\/fig-standards\/blob\/master\/accepted\/PSR-4-autoloader.md\">PSR-4 <\/a>beschrieben sind), diese sind uns aber bereits dann bekannt, wenn wir schonmal versucht haben, <a href=\"http:\/\/invidit.de\/blog\/category\/codequalitaet\/\">sauberen Code<\/a> in einer objektorientierten Programmiersprache zu schreiben.<\/p>\n<p>Alles in allen sollte das PHP-Leben mit dem obigen Beispiel und einer sauberen Struktur aufger\u00e4umter werden.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Wie man meinen Artikeln in den letzten Wochen entnehmen kann, befasse ich mich derzeit mit der Entwicklung von PHP. PHP habe ich schon&hellip;<\/p>\n","protected":false},"author":2,"featured_media":439,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[14,12],"tags":[],"class_list":["post-271","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-php","category-snippet"],"_links":{"self":[{"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/posts\/271","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/comments?post=271"}],"version-history":[{"count":4,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/posts\/271\/revisions"}],"predecessor-version":[{"id":287,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/posts\/271\/revisions\/287"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/media\/439"}],"wp:attachment":[{"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/media?parent=271"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/categories?post=271"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/staratnight.de\/blog\/wp-json\/wp\/v2\/tags?post=271"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}