mermshaus: PHP - Fehlerbehandlung im Autoloader

Beitrag lesen

Composer habe ich mir bisher nicht angeschaut, es klingt aber nach einer Wissenschaft für sich. Mal gucken. Im Moment bin ich mit meinem handgemachten Simpel-Autoloader, der fix einen Namespaceraum auf eine Verzeichnisstruktur mappt , noch zufrieden. Bis auf die genannten Details.

Composer kann auf den ersten Blick etwas komplex wirken, da bin ich durchaus bei dir. Es ist allerdings mittlerweile auch das Standardtool für Autoloading/Package-Management in PHP. Wie es bei derlei Tools häufig der Fall ist, nutzt man auch bei Composer dann im Normalfall die meiste Zeit nur drei oder vier Funktionen.

Das Setup unter Windows ist ein simpler Installer.[1] Wenn du das ausführst, solltest du danach in einem Terminal[2] composer --version tippen können. Wenn das was Sinnvolles ausgibt, ist das Tool funktionsfähig.

Ich glaube, es ist nicht sinnvoll, wenn ich jetzt die Doku nacherzähle, aber ein kurzes „Hello World“-Beispiel, das die maßgebliche Funktionalität demonstriert, möchte ich dennoch liefern. Ich erwarte natürlich nicht, dass du das sofort ausprobierst oder dein Projekt umbaust. Ich schreibe den Text auch, um für zukünftige Nutzung so was zu haben. Letztlich kann ich jedem PHP-Entwickler die Nutzung von Composer nur sehr ans Herz legen. Nebenbei möchte ich auch immer empfehlen, eine etwaige Scheu vor der Nutzung der Kommandozeile (Terminal-/Konsolen-Anwendungen) abzulegen. Eine Nutzung vereinfacht Workflows oft erheblich und macht viele Dinge überhaupt erst möglich.

Vorsichtshalber der Hinweis: Diese Kurzeinführung stellt eigentlich keine Operationen vor, mit denen versehentlich die falschen Dateien gelöscht oder überschrieben werden könnten, aber vor allem bei wichtigen Sachen schadet ein vorheriges Backup natürlich nie.


Schnelleinstieg

Wenn du irgendwo ein Verzeichnis (myproject oder so) für ein Demoprojekt anlegst, kannst du dort eine composer.json-Datei mit dem folgenden Inhalt hinzufügen:

{
    "require": {
        "league/commonmark": "^0.15"
    }
}

Das würde aussagen, dass per Composer das CommonMark-Package (ein Markdown-Parser) eingebunden und verwaltet werden soll.[3]

Wenn du nun im Terminal in das myproject-Verzeichnis navigierst und dort composer install tippst, lädt Composer automatisch das Package herunter[4] und installiert es ins vendor-Unterverzeichnis.

$ composer install
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing league/commonmark (0.15.0)
    Loading from cache

league/commonmark suggests installing league/commonmark-extras (Library of useful extensions including smart punctuation)
Writing lock file
Generating autoload files

Im vendor-Verzeichnis wird zudem ein Autoloader generiert, den du nun nur noch in deinem eigentlichen Code einzubinden brauchst, um die Inhalte (Klassen, Interfaces, …) aller mit Composer verwalteten Packages zu nutzen. Dieser Autoloader wird per require __DIR__ . '/vendor/autoload.php'; eingebunden.

Wenn du eine Datei test.php im Projektverzeichnis (myproject) erstellst und dort diesen Code einfügst…

<?php

require __DIR__ . '/vendor/autoload.php';

$converter = new League\CommonMark\CommonMarkConverter();
echo $converter->convertToHtml('# Hello World!');

…, hast du zum Beispiel ein fertiges Script, das Markdown-Quellcode in HTML umwandeln kann. Diese require-Zeile und die composer.json-Datei sind nahezu immer die einzige Anbindung, die Composer benötigt.

Wichtig ist an der Stelle: Composer wird in aller Regel nur lokal ausgeführt. Wenn du die test.php nun beispielweise auf einem Webspace ausführen möchtest (sozusagen ein Deployment des Projekts), musst du lediglich die Datei und das vendor-Verzeichnis hochladen. Composer selbst muss (und sollte) auf dem Zielsystem nicht installiert sein. Die fertige Software wäre also von den Abhängigkeiten und vom Aufbau her genauso, wie wenn du den Quellcode das CommonMark-PHP-Packages von Hand in ein Verzeichnis vendor kopiert und dort zudem einen Autoloader dafür erstellt hättest.


Eigenen Code ebenfalls über den Composer-Autoloader einbinden

Du schreibst davon, dass du deinen eigenen simplen Autoloader hast. Das ist natürlich völlig in Ordnung. Autoloading ist grundsätzlich ein gelöstes Problem und nichts, das komplizierter gemacht werden muss, als es ist.

Mit einem Code wie…

spl_autoload_register(function ($className) {
    $path = __DIR__ . '/src/' . str_replace('\\', '/', $className) . '.php';
    if (file_exists($path)) {
        require $path;
    }
});

…ist die Sache oft erledigt. Dieser Loader wäre auch perfekt kompatibel mit Composer, könnte also problemlos hinter der require-Zeile von Composer stehen.

Allerdings kann man, wenn man schon Composer nutzt, auch diesen Loader mit in Composer integrieren. Dazu wäre ein entsprechender autoload-Zusatz in der composer.json nötig, der in diesem Fall alle PHP-Klassen (und dergleichen) aus dem src-Unterverzeichnis per Autoloading verfügbar machen würde:

{
    "require": {
        "league/commonmark": "^0.15"
    },
    "autoload": {
        "classmap": "src/"
    }
}

Ich habe hier der Einfachheit halber die Classmap-Variante gewählt, da die prinzipiell immer funktioniert und dort Namespaces keine Rolle spielen. Eine Organisation im Dateisystem nach PSR-0 und PSR-4 wird aber ebenfalls unterstützt. In deinem Fall wäre alternativ wohl PSR-4 angebracht. Dazu müsstest du bei Interesse kurz in die Doku schauen, weil bei dem Format der oberste Namespace mit angegeben werden muss, den ich natürlich nicht kenne.

Nach Änderungen in composer.json muss im Projektverzeichnis einmal composer update ausgeführt werden, um beispielsweise den Autoloader in vendor entsprechend zu aktualisieren. Ist dies geschehen, kann in diesem Fall der eigene Autoloader aus dem Code entfernt werden. Der Code in src kann nun auch mit vendor/autoload.php geladen werden.


Warum Composer?

Der wesentliche Einsatzzweck in eigenen Projekten liegt natürlich im Einbinden und Verwalten von Third-Party-Packages. Schreibt man selbst an Code, der von anderen Entwicklern eingebunden werden können soll, gehört es heutzutage zudem fast schon zum „guten Ton“, diese Library auch als per Composer nutzbares Package anzulegen.

Die eben vorgestellte Autoloading-Funktionalität für eigenen Code ist aber immer ein netter Bonus. Siehe dazu auch meine vorherige Antwort.

Eine composer.json-Datei eines privaten Projekts (Fußball-Tippspiel) von mir sieht beispielsweise so aus:

{
    "config": {
        "optimize-autoloader": true
    },
    "require": {
        "roave/security-advisories": "dev-master",
        "beberlei/assert": "~2.4",
        "kaloa/renderer": "0.1.0",
        "kaloa/view": "0.1.0",
        "silex/silex": "~1.3"
    },
    "autoload": {
        "psr-4": {
            "Fuppes\\": "library/Fuppes"
        }
    }
}

Mein eigener Code liegt in library/Fuppes und nutzt PSR-4 (na ja, mehr oder weniger). Dazu nutze ich vor allem als Framework Silex und einige andere Packages, die nicht so wichtig sind.[5] Das vendor-Verzeichnis, in das Composer die Packages installiert, enthält derzeit etwas über 1000 Dateien. Das braucht mich als Entwickler aber nicht groß zu interessieren, da Composer das alles für mich verwaltet. Ich schreibe in meiner Anwendung nur am Anfang einmal require __DIR__ . '/vendor/autoload.php'; und kann dann alle Klassen nutzen.



  1. Unter Linux habe ich es nicht so gemacht, wie es in der Doku vorgeschlagen wird. Ich habe einfach die composer.phar runtergeladen und sie als composer nach ~/bin verschoben und dann vermutlich noch chmod +x ~/bin/composer gemacht. Ich glaube, der Grund dafür war, dass ich die Doku zu dem Zeitpunkt einfach nicht gesehen hatte und dass ich es mit PHPUnit aus diversen Gründen (IDE-Integration) auch immer so ähnlich mache. Es gibt jedenfalls verschiedene Möglichkeiten, die alle irgendwie zum Ziel führen. ↩︎

  2. Ein einfacher Weg, unter Windows eins zu öffnen, geht glaube ich so: Startmenü öffnen (Windows-Taste), cmd tippen, Enter drücken. ↩︎

  3. Zur Versionsangabe mit dem ^-Zeichen siehe hier. ↩︎

  4. Über ein zentrales Repository namens Packagist. ↩︎

  5. Das Package beberlei/assert ist eine Empfehlung für einigermaßen schmerzfreie typsichere Programmierung vor PHP 7. ↩︎