PluginInterface, Events und Custom Installer im Detail
Composer ist selbst über sein eigenes Plugin System erweiterbar, dieselbe Architektur, mit der Tools wie Symplify MonorepoBuilder oder Magento Komponenten Installer funktionieren. Wer ein eigenes Composer Plugin entwickelt, kann auf Lifecycle Events reagieren, eigene Console Commands registrieren und sogar eigene Paketinstallationslogik für benutzerdefinierte Paket Typen implementieren.
Inhaltsverzeichnis
- 1. Was ein Composer Plugin wirklich ist
- 2. Grundgerüst: PluginInterface und composer.json Typ plugin
- 3. Auf Composer Events reagieren
- 4. Capabilities: eigene Console Commands registrieren
- 5. Custom Installer für eigene Paket Typen
- 6. Plugin Konfiguration über extra Sektion auslesen
- 7. Composer Plugins testen ohne echte Installation
- 8. Veröffentlichung und Kompatibilität über Composer Versionen
- 9. Composer Plugin vs. Composer Script im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Was ein Composer Plugin wirklich ist
Ein Composer Plugin ist ein reguläres Composer Paket vom Typ plugin, das eine Klasse bereitstellt, die Composer\Plugin\PluginInterface implementiert. Sobald ein solches Paket als Abhängigkeit installiert wird, lädt Composer die angegebene Klasse automatisch beim Start und ruft deren activate Methode auf. Ab diesem Zeitpunkt kann das Plugin auf interne Composer Events lauschen, eigene Console Commands beisteuern oder sogar die Installation bestimmter Paket Typen komplett selbst übernehmen.
Der entscheidende Unterschied zu einem einfachen Composer Script, das lediglich ein Shell Kommando oder eine statische PHP Methode zu einem festen Lifecycle Punkt ausführt, liegt in der Tiefe der Integration. Ein Composer Plugin läuft im selben PHP Prozess wie Composer selbst, hat Zugriff auf das komplette Composer Objektmodell, den IO Handler für Konsolenausgaben und den Event Dispatcher, und kann dadurch weit komplexere Automatisierung umsetzen als ein simples Script.
Bekannte Beispiele für Composer Plugins in der Praxis sind composer/installers, das Pakete für Dutzende CMS und Framework Verzeichnisstrukturen an den richtigen Ort kopiert, oder hirak/prestissimo, das früher parallele Downloads ermöglichte, bevor diese Funktion in Composer 2 fest eingebaut wurde. Auch symplify/monorepo-builder nutzt intern Plugin Mechanismen, um zusätzliche Console Commands bereitzustellen.
2. Grundgerüst: PluginInterface und composer.json Typ plugin
Ein minimales Composer Plugin besteht aus zwei Teilen: einer composer.json mit type plugin und einem Eintrag extra.class, der den vollqualifizierten Klassennamen der Plugin Klasse angibt, sowie der eigentlichen PHP Klasse, die PluginInterface implementiert. Diese Schnittstelle verlangt drei Methoden: activate, deactivate und uninstall, jeweils mit Zugriff auf das Composer Objekt und den IOInterface Handler für Ausgaben.
Die activate Methode wird bei jedem Composer Aufruf ausgeführt, sobald das Plugin installiert ist, nicht nur einmalig bei der Installation selbst. Das ist ein häufiger Anfängerfehler: wer Einmal Logik in activate platziert, etwa das Anlegen einer Konfigurationsdatei, führt diese Logik bei jedem composer install und composer update erneut aus. Für echte Einmal Aktionen bei der Erstinstallation sind stattdessen die Events PackageInstalled oder PostInstall relevant.
{
"name": "mironsoft/composer-audit-plugin",
"type": "composer-plugin",
"require": {
"php": "^8.4",
"composer-plugin-api": "^2.0"
},
"require-dev": {
"composer/composer": "^2.7"
},
"autoload": {
"psr-4": {
"Mironsoft\\ComposerAuditPlugin\\": "src/"
}
},
"extra": {
"class": "Mironsoft\\ComposerAuditPlugin\\AuditPlugin"
}
}
<?php
declare(strict_types=1);
namespace Mironsoft\ComposerAuditPlugin;
use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
final class AuditPlugin implements PluginInterface
{
public function activate(Composer $composer, IOInterface $io): void
{
$io->write('<info>[audit-plugin] activated</info>');
}
public function deactivate(Composer $composer, IOInterface $io): void
{
// Called when the plugin is disabled or removed
}
public function uninstall(Composer $composer, IOInterface $io): void
{
// Called on full removal — clean up any generated files here
}
}
3. Auf Composer Events reagieren
Für die eigentliche Automatisierungslogik implementiert ein Composer Plugin zusätzlich die Schnittstelle Composer\EventDispatcher\EventSubscriberInterface, dieselbe PSR-14 ähnliche Idee wie in vielen anderen PHP FIG Standards, allerdings mit Composer eigener Event Klasse statt PSR-14 selbst. Die Methode getSubscribedEvents gibt ein assoziatives Array zurück, das Event Namen wie ScriptEvents::POST_INSTALL_CMD oder PackageEvents::POST_PACKAGE_INSTALL auf Methodennamen der eigenen Klasse abbildet.
Relevante Events für ein Composer Plugin decken den gesamten Lifecycle ab: PRE_DEPENDENCIES_SOLVING vor der Versionsauflösung, PACKAGE_INSTALL und PACKAGE_UPDATE für einzelne Pakete, POST_INSTALL_CMD und POST_UPDATE_CMD nach Abschluss des kompletten Befehls. Ein Plugin, das etwa nach jeder Installation automatisch eine Sicherheitsprüfung gegen bekannte CVEs durchführt, hängt sich an POST_INSTALL_CMD und POST_UPDATE_CMD und iteriert über den installierten Paketbestand aus dem Composer Repository Objekt.
<?php
declare(strict_types=1);
namespace Mironsoft\ComposerAuditPlugin;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\Script\Event;
use Composer\Script\ScriptEvents;
final class AuditEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
ScriptEvents::POST_INSTALL_CMD => 'onPostInstall',
ScriptEvents::POST_UPDATE_CMD => 'onPostInstall',
];
}
public function onPostInstall(Event $event): void
{
$io = $event->getIO();
$repository = $event->getComposer()->getRepositoryManager()->getLocalRepository();
foreach ($repository->getPackages() as $package) {
// ... check the installed package version against a CVE database
$io->write(sprintf(' auditing %s (%s)', $package->getName(), $package->getVersion()));
}
}
}
4. Capabilities: eigene Console Commands registrieren
Ab Composer 2 stellt das Composer Plugin System sogenannte Capabilities zur Verfügung, ein Erweiterungsmechanismus, mit dem ein Plugin zusätzliche Funktionalität deklariert, ohne die Kernklasse selbst mit zu vielen Verantwortlichkeiten zu überladen. Die relevanteste Capability für eigene Werkzeuge ist Composer\Plugin\Capability\CommandProvider, mit der ein Plugin eigene Console Commands registriert, die anschließend über composer eigener-befehl aufgerufen werden können, genau wie composer install oder composer require.
Die Implementierung erfolgt über getCapabilities in der Plugin Hauptklasse, die eine Zuordnung von Capability Interface zu einer konkreten Implementierungsklasse zurückgibt. Diese Trennung erlaubt es, mehrere unabhängige Commands in separaten Klassen zu pflegen, während die Plugin Hauptklasse selbst schlank bleibt und lediglich die Verkabelung zwischen Composer und den eigenen Erweiterungen übernimmt.
<?php
declare(strict_types=1);
namespace Mironsoft\ComposerAuditPlugin;
use Composer\Plugin\Capability\CommandProvider as CommandProviderCapability;
final class AuditPlugin implements PluginInterface, Capable
{
public function getCapabilities(): array
{
return [
CommandProviderCapability::class => CommandProvider::class,
];
}
// ... activate/deactivate/uninstall from the earlier example
}
final class CommandProvider implements CommandProviderCapability
{
public function getCommands(): array
{
// Registers "composer audit-full" as a new CLI command
return [new AuditFullCommand()];
}
}
5. Custom Installer für eigene Paket Typen
Für Anwendungsfälle, in denen Pakete nicht im Standard vendor Verzeichnis landen sollen, sondern an einen anwendungsspezifischen Ort, etwa Module in einem CMS oder Plugins in einer Magento Installation, implementiert ein Composer Plugin die Schnittstelle Composer\Installer\InstallerInterface. Diese Schnittstelle verlangt Methoden wie supports, das prüft, ob der Installer für einen gegebenen Paket Typ zuständig ist, sowie install und getInstallPath, die den tatsächlichen Installationsort bestimmen.
Das bereits erwähnte composer/installers Paket ist das Referenzbeispiel für diesen Ansatz: es registriert einen Custom Installer, der anhand des Paket Typs, etwa wordpress-plugin oder magento2-module, das passende Zielverzeichnis berechnet und Composer anweist, die Dateien dorthin zu kopieren statt in den generischen vendor Ordner. Ein eigenes Composer Plugin mit Custom Installer folgt demselben Muster, meist mit einer Konfigurationsoption im extra Block der composer.json, um das Zielverzeichnis flexibel anzupassen.
<?php
declare(strict_types=1);
namespace Mironsoft\ComposerAuditPlugin;
use Composer\Installer\LibraryInstaller;
use Composer\Package\PackageInterface;
final class RuleSetInstaller extends LibraryInstaller
{
public function supports(string $packageType): bool
{
// Only handle packages explicitly declared as "mironsoft-ruleset"
return $packageType === 'mironsoft-ruleset';
}
public function getInstallPath(PackageInterface $package): string
{
// Install into a dedicated directory instead of vendor/
return 'rulesets/' . $package->getPrettyName();
}
}
6. Plugin Konfiguration über extra Sektion auslesen
Ein flexibles Composer Plugin liest projektspezifische Einstellungen typischerweise aus dem extra Block der root composer.json aus, zugänglich über $composer->getPackage()->getExtra(). Ein üblicher Ansatz ist ein eigener Namespace innerhalb von extra, etwa extra.mironsoft-audit-plugin, um Namenskollisionen mit anderen Plugins zu vermeiden, die möglicherweise ebenfalls Einstellungen im selben extra Block hinterlegen.
Wichtig für robuste Composer Plugins: fehlende Konfiguration darf niemals zu einem Fatal Error führen, sondern sollte auf sinnvolle Standardwerte zurückfallen. Ein Plugin, das ohne jegliche Konfiguration im extra Block einfach abstürzt, sorgt zuverlässig für Frustration bei Nutzern, die das Plugin lediglich als transitive Abhängigkeit einer anderen Bibliothek installiert haben, ohne es überhaupt bewusst konfigurieren zu wollen.
7. Composer Plugins testen ohne echte Installation
Composer Plugins lassen sich mit PHPUnit testen, ohne bei jedem Testlauf eine echte composer.json Installation durchzuführen. Der Trick besteht darin, ein Composer Objekt und ein IOInterface Mock direkt im Test zu instanziieren und die zu testenden Methoden isoliert aufzurufen, statt den kompletten Composer Prozess zu starten. Für die Event Subscriber Logik genügt es meist, ein Event Objekt mit den relevanten Daten manuell zu konstruieren und an die entsprechende Methode zu übergeben.
Für Integrationstests, die den echten Composer Lifecycle abdecken, etwa um zu prüfen, ob ein Custom Installer tatsächlich Dateien an den richtigen Ort kopiert, bietet sich composer/composer als require dev Abhängigkeit mit einem temporären Testverzeichnis an. Ein solcher Test legt in einem tmpfs Verzeichnis eine minimale composer.json an, führt composer install programmatisch über die Composer Application Klasse aus und prüft anschließend das Ergebnis auf der Festplatte.
8. Veröffentlichung und Kompatibilität über Composer Versionen
Ein Composer Plugin deklariert seine Kompatibilität über die spezielle virtuelle Abhängigkeit composer-plugin-api, nicht über eine reguläre composer/composer Abhängigkeit. Diese virtuelle Abhängigkeit repräsentiert die tatsächlich installierte Composer Version zur Laufzeit und verhindert, dass ein für Composer 1 geschriebenes Plugin versehentlich unter Composer 2 geladen wird, wo sich Teile der internen API grundlegend geändert haben.
Für die Veröffentlichung gelten dieselben Regeln wie für jedes andere Composer Paket: Semantic Versioning, ein aussagekräftiges Changelog und eine Veröffentlichung über Packagist oder Private Packagist. Ein wichtiger Zusatzpunkt speziell für Composer Plugins: da sie mit interner Composer API arbeiten, die sich zwischen Minor Versionen gelegentlich ändert, lohnt sich eine explizite Testmatrix gegen mehrere Composer Versionen in der CI Pipeline, um Breaking Changes frühzeitig zu erkennen.
9. Composer Plugin vs. Composer Script im Vergleich
Die Wahl zwischen einem vollwertigen Composer Plugin und einem einfachen Composer Script hängt von der benötigten Integrationstiefe ab. Die folgende Tabelle zeigt die wichtigsten Unterschiede.
| Kriterium | Composer Script | Composer Plugin | Praxisrelevanz |
|---|---|---|---|
| Einrichtungsaufwand | Ein Eintrag in composer.json | Eigenes Paket, eigene Klasse nötig | Wichtig für einfache Automatisierung |
| Zugriff auf Composer API | Nur über CLI Aufrufe | Voller Zugriff auf Composer Objekt | Relevant für tiefe Integration |
| Eigene Console Commands | Nicht möglich | Über CommandProvider Capability | Wichtig für eigene CLI Werkzeuge |
| Wiederverwendbarkeit | Pro Projekt kopiert | Als Composer Paket über Projekte hinweg | Hoch bei mehreren Projekten |
| Wartungsaufwand | Minimal | Eigene Tests, eigene Kompatibilitätsprüfung | Relevant bei Composer Major Updates |
Für einmalige, projektspezifische Automatisierung, etwa das Löschen eines Cache Verzeichnisses nach composer install, genügt ein einfaches Composer Script vollkommen. Sobald die Logik über mehrere Projekte hinweg wiederverwendet werden soll, eigene Console Commands benötigt oder tief in den Composer Lifecycle eingreifen muss, überwiegt der zusätzliche Aufwand eines vollwertigen Composer Plugins deutlich die Vorteile eines einfachen Scripts.
Mironsoft
PHP Architektur, Paketstrategie und Composer Tooling
Ein eigenes Composer Plugin für euren Entwicklungsalltag?
Wir entwickeln maßgeschneiderte Composer Plugins mit Event Handling, eigenen Console Commands und Custom Installer Logik, getestet und versioniert für den produktiven Einsatz.
Konzeption
Analyse, ob ein Composer Script oder ein vollwertiges Plugin die passende Lösung ist
Entwicklung
PluginInterface, Event Subscriber, Capabilities und Custom Installer implementiert
Tests und Release
Testmatrix gegen mehrere Composer Versionen, Veröffentlichung auf Packagist
10. Zusammenfassung
Ein eigenes Composer Plugin zu entwickeln lohnt sich, sobald Automatisierung über die Möglichkeiten eines einfachen Composer Scripts hinausgeht: PluginInterface bildet das Grundgerüst mit activate, deactivate und uninstall, EventSubscriberInterface erlaubt gezieltes Reagieren auf Lifecycle Events wie POST_INSTALL_CMD, Capabilities registrieren eigene Console Commands, und ein Custom Installer übernimmt vollständige Kontrolle über die Installation eigener Paket Typen.
Für den produktiven Einsatz gilt: Konfiguration über den extra Block mit sinnvollen Standardwerten, Tests ohne echte Installation über gemockte Composer und IO Objekte, und eine explizite Kompatibilitätsprüfung gegen mehrere Composer Versionen in der CI Pipeline. Wer diese Bausteine kombiniert, baut ein Composer Plugin, das robust über Jahre und mehrere Composer Major Versionen hinweg funktioniert.
Composer Plugins eigene entwickeln — Das Wichtigste auf einen Blick
Grundgerüst
composer.json vom Typ plugin plus eine Klasse, die PluginInterface mit activate, deactivate und uninstall implementiert.
Events und Capabilities
EventSubscriberInterface für Lifecycle Events, CommandProvider Capability für eigene Console Commands.
Custom Installer
InstallerInterface für vollständige Kontrolle über Installationsort und Logik eigener Paket Typen.
Kompatibilität
composer-plugin-api Abhängigkeit statt fester Composer Version, Testmatrix gegen mehrere Composer Releases.