Best Practise: Shared Code in PHP

von Patrick Bédat, 14. November 2014

Die Verwaltung von Shared Code in PHP war mir schon immer ein Dorn im Auge… Zum Glück gibt es mittlerweile Composer. Ein vernünftiges Package Management System à la NPM, bower, NuGet und Konsorten. Doch wie ist das mit eigenen Bibliotheken, die in anderen Projekten wiederverwendet werden sollen? Als ich vor kurzem, nach Jahren der PHP Abstinenz, ein völlig desolates PHP Projekt „retten“ sollte, habe ich mir diese Frage erneut gestellt.

In der Steinzeit

Da wo ich her kam, aus der C# Welt, reicht es, eine Bibliothek einzubinden, den gewünschten Namespace der gewünschten Klasse zu importieren und voilà – man kann diese Klasse dann verwenden.

In einer Skript Sprache wie PHP ist es damit nicht getan. Nicht nur, dass es bis vor kurzem noch gar keine Namespaces gab, nein um eine Klasse zu nutzen, muss man die Datei, die sie definiert, importieren:

// Foo.php
require "my/other/lib/Bar.php";
class Foo {
  public function __construct(Bar $bar){ }
}
// Bar.php
class Bar {
}

Das Problem mit dem require ist: Die Pfade dürfen sich nicht ändern. D.h. die verwendeten Bibliotheken, müssen immer am gleichen Ort eingebundenen werden.

Dieses Problem wurde irgendwann durch die magische PHP Funktion __autoload angegangen. Wird diese Funktion implementiert, wird sie aufgerufen, sobald eine Klasse verwendet wird, die noch nicht geladen wurde.

Beispiel:

function __autoload ( string $class ) {
  require "my/other/lib/" . $class . ".php";
}
new Foo();

Da wir nun unsere Klassen nicht in einem Ordner, sondern so wie in der C# Welt in einer Ordnerstruktur ablegen, die quasi Namespaces entspricht, wollte ich mich nicht lange mit __autoload herumschlagen und bin einen pragmatischeren Weg gegangen. Ich habe schlicht und einfach in der Eintrittsdatei (index.php) alle PHP-Dateien aus den zur Verfügung stehenden Ordnerstruktur geladen. Fertig.

Die Arbeit mit Composer

Ungefähr das erste, was ich zu beginn des Projekts unternommen habe, war die Suche nach einem Package System und ich wurde fündig.

Brauche ich beispielsweise PHPUnit in einem Projekt:

php composer.phar require anahkiasen/underscore-php

Composer läd dann das Paket in den Ordner vendor und updated seine autload.php, die ich in meine PHP App einbinde:

//index.php
require "vendor/autoload.php";
use Underscore\Types\Arrays;
$arr = array(1, 2, 3);
print_r(Arrays::each($arr, function($val){
  return $val + 1;
}));
// prints: 2, 3, 4

Ganz recht: Man muss nur noch Typen über ihren vollqualifizierten Namespace einbinden (use) und die autoload.php von Composer erledigt den Rest!

An einem Punkt im Projekt, wo ich anfing Use Cases in eigene Projekte auszulagern, erschien mir mein bisheriges Vorgehen als rückständig und nicht mehr praktikabel und ich dachte mir: Irgendwie muss man doch diesen tollen Composer Autoloader für sich gefügig machen können! Und ja – man kann =)

Wenn man composer verwendet, wird eine Konfigurationsdatei angelegt. In dieser composer.json, kann man auf eigene Namespaces verweisen:

{
  "require": {
    "monolog/monolog": "1.2.*"
  },
  "autoload": {
    "psr-0": {
      "your-namespace": "deps/your-namespace"
    }
  }
}

PSR-0 gibt an dieser Stelle nur an, welcher autoloading Standard verwendet wird. PSR-0 erschien mir völlig ausreichend. Nachdem die composer.json richtig konfiguriert ist, muss man den autloader von composer neu erzeugen:

php composer.phar install

Wird nun ein Build meines Projekts angestoßen, werden zunächst die abhängigen Projekte gebaut. Die abhängigen Builds, werden dann wiederum im Build-Ordner des zu bauenden Projekts im Ordner deps/ abgelegt.

Jetzt können auch eigene Klassen, die mit Namespaces ausgestattet werden, über den Autoloader von Composer dynamisch geladen werden!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *