In vielen Software-Projekten kommen Entwickler irgendwann an einen Punkt, an dem Funktionalität implementiert wird, die in anderen Projekten wiederverwendet werden können soll. Nachdem ich in der jüngeren Vergangenheit von mehreren Kollegen gefragt wurde, wie man dies in Symfony2-Projekten bewerkstelligen kann, wollte ich schriftlich eine Zusammenfassung der wichtigsten Punkte bereitstellen. Das Resultat ist dieser Blog-Artikel, der eine Schritt-für-Schritt-Anleitung enthält, wie man wiederverwendbaren Code kapselt und in einem eigenen Symfony2-Bundle bereitstellt.
Das Wissen, welches ich hier verpacke, habe ich teils aus der Symfony2-Dokumentation, teils aus der Erfahrung, welche ich in den letzten Jahren in Symfony2-Projekten gesammelt habe. Nachdem ich des Öfteren Library-Code aus Projekten ausgelagert hatte, konnte ich einige Patterns identifizieren und vor allem feststellen, dass diese Vorgehensweisen in Symfony2-Projekten (abgesehen von syntaktischen Eigenheiten) denen in beliebigen anderen Umgebungen sehr stark ähneln.
Die folgende Liste ist als Template zu verstehen, welches in einfachen Projekten genau so verwendet werden kann, in komplexeren jedoch wahrscheinlich erweitert werden muss.
- Werde Dir darüber klar, welche Teile wirklich wiederverwendbar werden sollen.
Zu oft findet man „Libraries“, die so „generisch“ geschrieben sind, dass sie genau in einem Projekt Anwendung finden. Gute Libraries konzentrieren sich darauf, entweder genau eine Funktionalität oder eine API für eine externe Abhängigkeit bereitzustellen. - Definiere alle in Punkt 1 identifizierten Klassen als Services.
An allen Stellen, an denen solche Klassen verwendet werden, sollten sie per Dependency Injection bereitgestellt werden. Eine Ausnahme stellen Value-Objects dar. Diese können beispielsweise durch eine Factory (welche wiederum als Service definiert ist) erzeugt werden.
Der Sinn dieses Schritts ist es, die Schnittstellen zu der Library-Funktionalität explizit zu machen. Dies ist als Vorbereitung für das Verschieben des Codes notwendig. - Erstelle das neue Bundle
Erstelle jetzt ein neues Bundle im Ordnersrc
, in dem der Library-Code später unterkommt:./app/console generate:bundle
Im Folgenden bezeichne ich das neue Bundle LibraryBundle und das ursprüngliche, in dem das restliche Symfony2-Projekt lebt ProjectBundle.
- Verschiebe den Quellcode in das LibraryBundle
Jetzt musst Du den in Punkt 1 identifizierten Code in das LibraryBundle verschieben. Danach müssen die Service-Definitionen der umgezogenen Klassen aus dem ProjektBundle in das LibraryBundle umgezogen werden. Wenn sich dadurch die Namen der Services ändern, müssen im ProjectBundle die Referenzen angepasst werden. - Zwischenziel:
Das Projekt sollte zu diesem Zeitpunkt ohne Einschränkung funktionieren! (Wo sind Deine Tests?) - Erstelle ein neues Git-Repository für das LibraryBundle
Als Name eignet sich-
-bundle“ oder/
-bundle“. - Verschiebe den Code des LibraryBundles in das neue Repository
Verschiebe alle Dateien und Ordner aus dem LibraryBundlein das neue Repository.mv src/Mein/LibraryBundle/* /pfad/zum/neues/repository/
- Composer-Support für das LibraryBundle
Zusätzlich muss das neue Repository noch eine eigenecomposer.json
(und am besten eineREADME.md
) bekommen:{ "name": "paul/library-bundle", "description": "Das ist mein neues LibraryBundle.", "autoload": { "psr-0": { "Paul\\LibraryBundle": "" } }, "target-dir": "Paul/LibraryBundle", "require": { "php": ">=5.3.3", "symfony/symfony": "2.1.*" } }
Die Einträge unter
autoload
undtarget-dir
sorgen dafür, dass die Klassen im neuen Bundle zuerst an die richtige Stelle im Verzeichnisvendor
installiert werden und dann richtig am Autoloader registriert werden.Damit ist das neue Repository erst einmal fertig. Im Projekt-Repository kann man jetzt alle Überreste des LibraryBundles entfernen.
rm -fr src/Mein/LibraryBundle
- Veröffentlichen des LibraryBundles
Das neue Repository kann jetzt an einem öffentlichen Ort (warum nicht GitHub?) veröffentlicht werden.cd /pfad/zum/neuen/repository git add . git commit -m "Initial commit" git add remote origin git@github.com:.git git push origin master
Wenn das Paket öffentlich sichtbar (nicht nur in der eigenen Organisation) sein darf, kann an dieser Stelle ein Projekt auf Packagist erstellt werden.
- Einbinden des LibraryBundles im Projekt
Damit das LibraryBundle per Composer installiert werden kann, muss es noch in dercomposer.json
des Projekt-Repositorys registrieren.Wenn das LibraryBundle nicht auf Packagist veröffentlicht wurde, muss ein custom Repository definiert werden:
"require": { ... "paul/library-bundle": "*", ... }, ... "repositories": [ { "type": "git", "url": "git@github.com:Paul/library-bundle.git" }, ... ], ...
Wenn man jetzt im Projekt-Repository noch
composer update
ausführt, wird das LibraryBundle im Verzeichnisvendor
installiert.
Herzlichen Glückwunsch!
Deine Library-Funktionalität ist nun vollständig in ein eigenes Bundle und Repository ausgelagert! Dein Projekt sollte genauso funktionsfähig sein wie vor dem Umbau! (Ansonsten solltest du jetzt überlegen, einen Kommentar zu diesem Artikel zu hinterlassen!)
Schlussbemerkungen
Lagere Library-Funktionalität am besten erst aus, wenn sie für den Moment fertig und getestet ist. Wenn der Code noch im Entwicklungs-Stadium ist, solltest du noch abwarten. Das Problem ist, dass Änderungen im Library-Repository committet/gepusht werden müssen und danach das Project-Repository per composer aktualisiert werden muss, nur um eine kleine Änderung im LibraryBundle im Kontext testen zu können.
Wenn du Code im LibraryBundle ändern musst, kannst du die Änderungen direkt auch im Ordner vendor/Mein/LibraryBundle
vornehmen. In diesem Ordner liegt ein Clone des Library-Repositorys, in dem du auch committen und pushen kannst! Somit kann man das Problem des vorherigen Abschnitts umgehen. (Über die Sauberkeit dieser Vorgehensweise möchte ich an dieser Stelle jedoch kein Urteil fällen.)
Ein paar Wegweiser zur weiteren Lektüre:
- Service-Konfiguration: Symfony Book Link
- Konfiguration von Bundles: Symfony Cookbook Link
- Custom Repositories mit Composer: Composer Dokumentation Link
Schreibe einen Kommentar