für eigene Automatisierung im Projekt
Composer kann weit mehr als nur Abhängigkeiten installieren: Über Composer-Scripts und Lifecycle-Hooks lässt sich jeder Schritt des Installationsprozesses, vom ersten composer install bis zum letzten Autoload-Dump, um eigene Automatisierung erweitern. Ob Shell-Befehl oder PHP-Callback über Composer\Script\Event, wer die vollständige Event-Liste kennt, ersetzt manuelle Setup-Schritte, vergessene Cache-Leerungen und improvisierte Onboarding-Anleitungen durch reproduzierbare, versionierte Automatisierung direkt in der composer.json.
Inhaltsverzeichnis
- 1. Warum Composer-Scripts: Automatisierung jenseits von install und update
- 2. Die vollständige Event-Liste im Überblick
- 3. Zwei Arten von Script-Handlern: Shell-Befehle und PHP-Callbacks
- 4. Composer\Script\Event im Detail
- 5. Eigene Composer-Kommandos definieren
- 6. Praxisbeispiel Schritt für Schritt: Setup nach jedem Install
- 7. Composer-Plugins vs. Composer-Scripts
- 8. Fehlerbehandlung in Scripts
- 9. Alle Hook-Typen im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Composer-Scripts: Automatisierung jenseits von install und update
Composer wird in den meisten Projekten nur als Werkzeug zum Auflösen und Installieren von Abhängigkeiten wahrgenommen. Dabei bringt Composer ein vollständiges Lifecycle-System mit, das bei jedem composer install, composer update und praktisch jeder anderen Operation eigene Ereignisse feuert. Composer-Scripts hängen sich an genau diese Ereignisse und führen dort Shell-Befehle oder PHP-Code aus, ganz ohne zusätzliches Tooling. Wer Composer-Scripts nie eingesetzt hat, verschenkt damit einen Automatisierungsmechanismus, der bereits in jeder PHP-Installation vorhanden ist.
Die Motivation für eigene Lifecycle-Hooks ist meistens dieselbe: manuelle Schritte nach dem Auschecken eines Projekts werden vergessen, README-Anleitungen veralten schneller als der Code, und jedes Onboarding eines neuen Entwicklers wiederholt dieselben Copy-Paste-Kommandos. Mit Composer-Hooks wandert diese Logik direkt in die composer.json, versioniert zusammen mit dem Projekt, statt in einem separaten Wiki-Artikel oder Bash-Skript zu verstauben. Typische Anwendungsfälle sind das Kopieren einer .env.example nach .env, das Anlegen von Verzeichnissen für Logs und Cache, das Setzen von Dateiberechtigungen oder das automatische Leeren eines Cache-Verzeichnisses nach jedem Update.
Der entscheidende Vorteil gegenüber einem externen Makefile oder Shell-Skript: Composer-Scripts laufen automatisch an der richtigen Stelle im Installationsprozess, ohne dass ein Entwickler sich an einen zusätzlichen Befehl erinnern muss. Und weil der Mechanismus Teil von Composer selbst ist, funktioniert er unabhängig vom eingesetzten Framework, ob reines PHP 8.4 ohne Framework, Symfony, oder ein internes Firmenpaket ohne jede Framework-Bindung.
2. Die vollständige Event-Liste im Überblick
Composer unterscheidet zwischen mehreren Kategorien von Ereignissen, an die sich Composer-Scripts hängen lassen. Die Kommando-Events umklammern die zentralen Operationen: pre-install-cmd und post-install-cmd feuern vor und nach composer install, pre-update-cmd und post-update-cmd analog vor und nach composer update. Diese vier Events sind der Standardort für projektweite Automatisierung, weil sie bei jedem vollständigen Installationslauf ausgelöst werden, unabhängig davon, wie viele einzelne Pakete betroffen sind.
Eine zweite Kategorie feuert pro Paket statt pro Kommando: pre-package-install, post-package-install, pre-package-update, post-package-update, pre-package-uninstall und post-package-uninstall. Diese Lifecycle-Hooks sind vor allem für Composer-Plugins relevant, die auf einzelne Abhängigkeiten reagieren müssen, in normalen Projekt-Scripts kommen sie selten zum Einsatz. Daneben gibt es post-autoload-dump, das nach jeder Neugenerierung des Autoloaders feuert, egal ob durch install, update oder ein explizites composer dump-autoload ausgelöst, sowie post-create-project-cmd, das ausschließlich beim Anlegen eines neuen Projekts mit composer create-project läuft und sich ideal für Erstkonfiguration eignet.
Weitere spezialisierte Events runden die Liste ab: pre-archive-cmd und post-archive-cmd rahmen den composer archive-Befehl, pre-autoload-dump feuert vor der Autoloader-Generierung und erlaubt das Manipulieren der Autoload-Konfiguration zur Laufzeit, und post-root-package-install läuft einmalig direkt nach der Installation des Root-Pakets bei create-project. Wer Composer-Scripts gezielt einsetzen will, sollte diese Events nicht auswendig lernen, sondern anhand der eigenen Automatisierungsaufgabe die passende Kategorie auswählen: projektweit oder paketweit, einmalig oder wiederkehrend.
{
"name": "acme/example-project",
"type": "project",
"require": {
"php": ">=8.4"
},
"scripts": {
"pre-install-cmd": "Acme\\Setup\\ComposerHooks::preInstall",
"post-install-cmd": [
"Acme\\Setup\\ComposerHooks::postInstall",
"@php bin/clear-cache.php"
],
"pre-update-cmd": "Acme\\Setup\\ComposerHooks::preInstall",
"post-update-cmd": [
"Acme\\Setup\\ComposerHooks::postInstall",
"@php bin/clear-cache.php"
],
"post-autoload-dump": "Acme\\Setup\\ComposerHooks::postAutoloadDump",
"post-create-project-cmd": "Acme\\Setup\\ComposerHooks::firstTimeSetup"
}
}
3. Zwei Arten von Script-Handlern: Shell-Befehle und PHP-Callbacks
Für jedes Event akzeptiert der scripts-Block in der composer.json zwei grundsätzlich verschiedene Arten von Handlern. Die erste Variante ist ein Shell-Befehl als String, der von Composer über einen neuen Prozess ausgeführt wird, zum Beispiel "post-install-cmd": "chmod -R 775 var/cache". Das ist schnell hingeschrieben, hat aber einen Nachteil: Shell-Befehle sind plattformabhängig, ein Befehl, der unter Linux und macOS funktioniert, schlägt auf Windows ohne WSL oft fehl. Composer-Scripts, die reine Portabilität brauchen, sollten diese Variante daher nur für Aufrufe eigener, plattformunabhängiger PHP-Skripte nutzen, etwa mit dem Präfix @php.
Die zweite und für echte Composer-Scripts deutlich mächtigere Variante ist der PHP-Callback als statische Klassenmethode, notiert als "Vendor\\Klasse::methode". Composer instanziiert dafür keine Klasse, sondern ruft die Methode statisch auf und übergibt ein Composer\Script\Event-Objekt als einzigen Parameter. Der entscheidende Vorteil: Der Callback läuft im selben PHP-Prozess wie Composer selbst, ohne den Overhead eines neuen Kindprozesses, und hat vollen Zugriff auf die Composer-Laufzeitumgebung, inklusive IO-Interface und Konfiguration.
Eine Besonderheit, die häufig übersehen wird: Damit ein PHP-Callback als Composer-Hook funktioniert, muss die referenzierte Klasse zum Zeitpunkt des Events bereits über den Autoloader auffindbar sein. Bei einem allerersten composer install in einem frischen Checkout ohne vendor-Verzeichnis kann das zu einem Henne-Ei-Problem führen, wenn die Handler-Klasse selbst erst durch die Installation autoladbar wird. In der Praxis platziert man Handler-Klassen deshalb meist in einem eigenen, bereits vorhandenen Namespace innerhalb des Root-Pakets, dessen PSR-4-Mapping unabhängig vom Installationsstand aktiv ist.
<?php
declare(strict_types=1);
namespace Acme\Setup;
use Composer\Script\Event;
/**
* Static Composer script handlers for pre- and post-install hooks.
*/
final class ComposerHooks
{
/**
* Runs before install and update: writes a marker file so post hooks
* can detect whether this was a fresh install or an update.
*/
public static function preInstall(Event $event): void
{
$event->getIO()->write('<info>Preparing installation...</info>');
file_put_contents(__DIR__ . '/../../.composer-pre-install', (string) time());
}
/**
* Runs after install and update: the main entry point for project setup.
*/
public static function postInstall(Event $event): void
{
$io = $event->getIO();
$io->write('<info>Running post-install setup...</info>');
// Delegate to a dedicated task runner instead of inlining logic here
$runner = new ProjectSetup($event->getIO(), $event->isDevMode());
$runner->ensureEnvFile();
$runner->ensureDirectories();
}
}
4. Composer\Script\Event im Detail
Jeder PHP-Callback erhält als einzigen Parameter eine Instanz von Composer\Script\Event, und dieses Objekt ist der einzige Zugriffspunkt auf den Kontext, in dem das Script läuft. Die Methode getIO() liefert ein IOInterface, über das sich Ausgaben mit Formatierung schreiben lassen (write(), writeError()), interaktive Rückfragen an den Nutzer stellen lassen (ask(), askConfirmation()) und sich die aktuelle Verbosity-Stufe abfragen lässt. Für Composer-Scripts, die den Nutzer informieren oder um Bestätigung bitten sollen, ist getIO() der einzig korrekte Weg, statt direkt mit echo zu arbeiten, weil nur so das Verhalten von Composer-Flags wie --quiet respektiert wird.
Die Methode getComposer() liefert die vollständige Composer-Instanz mit Zugriff auf das aktuell installierte Root-Paket, die Konfiguration aus composer.json und dem globalen config.json, sowie den Repository- und Installation-Manager. Darüber lässt sich etwa programmatisch ermitteln, welche Pakete installiert sind oder welche Version ein bestimmtes Paket hat, ganz ohne das Dateisystem selbst zu durchsuchen. isDevMode() gibt zurück, ob der Aufruf ohne das Flag --no-dev erfolgte, was für Lifecycle-Hooks wichtig ist, die sich zwischen Entwicklungs- und Produktionsverhalten unterscheiden sollen, etwa um Debug-Tools nur lokal zu aktivieren.
Zusätzlich liefert getName() den Namen des aktuell feuernden Events, was in einem einzigen Handler nützlich ist, der für mehrere Events registriert wird und je nach Event unterschiedlich reagieren soll. Und getArguments() gibt zusätzliche Argumente zurück, die beim Aufruf über composer run-script name -- arg1 arg2 nach dem doppelten Bindestrich übergeben wurden, sodass ein Composer-Script auch parametrisiert aufgerufen werden kann.
<?php
declare(strict_types=1);
namespace Acme\Setup;
use Composer\Script\Event;
/**
* Demonstrates the information available on the Composer\Script\Event object.
*/
final class EventInspector
{
/**
* Logs event context and reacts differently for dev vs. production installs.
*/
public static function inspect(Event $event): void
{
$io = $event->getIO();
$composer = $event->getComposer();
$rootPackage = $composer->getPackage();
$io->write(sprintf('<comment>Event:</comment> %s', $event->getName()));
$io->write(sprintf('<comment>Package:</comment> %s (%s)', $rootPackage->getName(), $rootPackage->getPrettyVersion()));
$io->write(sprintf('<comment>Dev mode:</comment> %s', $event->isDevMode() ? 'yes' : 'no'));
$arguments = $event->getArguments();
if ($arguments !== []) {
$io->write(sprintf('<comment>Arguments:</comment> %s', implode(' ', $arguments)));
}
if (!$event->isDevMode() && $io->isInteractive()) {
$io->askConfirmation('Running in production mode, continue? [Y/n] ', true);
}
}
}
5. Eigene Composer-Kommandos definieren
Neben den vordefinierten Lifecycle-Events lässt sich der scripts-Block auch als reiner Alias-Mechanismus für beliebige eigene Kommandos verwenden. Ein Eintrag wie "test": "phpunit --colors=always" definiert kein Lifecycle-Event, sondern ein frei benanntes Composer-Script, das über composer run-script test oder in der Kurzform composer test aufgerufen wird. Diese Kurzform funktioniert immer dann, wenn der gewählte Name nicht mit einem eingebauten Composer-Kommando kollidiert, andernfalls muss explizit run-script verwendet werden.
Mehrere Befehle lassen sich als Array notieren, dann führt Composer sie sequentiell aus und bricht ab, sobald einer der Befehle einen Fehler zurückgibt. Mit dem Präfix @ lässt sich innerhalb eines Scripts ein anderes, bereits definiertes Script referenzieren, etwa "post-install-cmd": ["@setup-env", "@clear-cache"], was komplexe Ketten aus mehreren benannten Composer-Scripts zusammensetzt, ohne Logik zu duplizieren. Der Eintrag scripts-descriptions ergänzt eigene Kommandos mit einer kurzen Beschreibung, die dann in der Ausgabe von composer list neben dem Kommando erscheint, sodass eigene Composer-Hooks für das gesamte Team dokumentiert und auffindbar sind.
{
"scripts": {
"test": "phpunit --colors=always",
"cs-check": "phpcs --standard=PSR12 src tests",
"cs-fix": "phpcbf --standard=PSR12 src tests",
"analyse": "phpstan analyse src --level=8",
"setup-env": "Acme\\Setup\\ComposerHooks::ensureEnvFile",
"clear-cache": "Acme\\Setup\\ComposerHooks::clearCache",
"check": [
"@cs-check",
"@analyse",
"@test"
],
"post-install-cmd": [
"@setup-env",
"@clear-cache"
]
},
"scripts-descriptions": {
"test": "Run the full PHPUnit test suite",
"check": "Run coding standard, static analysis and tests in one go",
"cs-fix": "Automatically fix coding standard violations"
}
}
6. Praxisbeispiel Schritt für Schritt: Setup nach jedem Install
Ein realistisches Beispiel bringt die vorigen Abschnitte zusammen: Nach jedem composer install und composer update soll automatisch geprüft werden, ob eine .env-Datei existiert, andernfalls wird sie aus .env.example kopiert. Zusätzlich sollen die Verzeichnisse var/log und var/cache angelegt werden, falls sie fehlen, und der Inhalt des Cache-Verzeichnisses soll bei jedem Lauf geleert werden, damit veralteter Bytecode oder generierte Konfiguration nicht versehentlich weiterverwendet wird. Genau für solche wiederkehrenden Aufgaben sind Composer-Scripts gemacht: die Logik liegt einmal im Projekt, läuft aber bei jedem Entwickler und in jeder CI-Umgebung automatisch identisch ab.
Die Implementierung nutzt eine kleine Klasse mit Constructor Property Promotion, die das IOInterface und den Dev-Mode-Status entgegennimmt und mehrere einzelne Setup-Schritte kapselt. Wichtig ist, jeden Schritt defensiv zu schreiben: Ein bereits existierendes Verzeichnis darf keinen Fehler auslösen, ein bereits existierendes .env darf nicht überschrieben werden, weil sonst lokale Konfiguration bei jedem composer update verloren ginge. Diese Zurückhaltung unterscheidet einen robusten Composer-Hook von einem Skript, das beim zweiten Aufruf bereits vorhandene Arbeit zerstört.
<?php
declare(strict_types=1);
namespace Acme\Setup;
use Composer\IO\IOInterface;
/**
* Encapsulates the individual setup steps triggered by Composer lifecycle hooks.
*/
final class ProjectSetup
{
public function __construct(
private readonly IOInterface $io,
private readonly bool $isDevMode,
) {
}
/**
* Copies .env.example to .env if no .env file exists yet.
* Never overwrites an existing .env to avoid losing local configuration.
*/
public function ensureEnvFile(): void
{
$envFile = getcwd() . '/.env';
$exampleFile = getcwd() . '/.env.example';
if (is_file($envFile) || !is_file($exampleFile)) {
return;
}
copy($exampleFile, $envFile);
$this->io->write('<info>.env created from .env.example</info>');
}
/**
* Creates required runtime directories if they are missing.
*/
public function ensureDirectories(): void
{
foreach (['var/log', 'var/cache'] as $relativePath) {
$path = getcwd() . '/' . $relativePath;
if (!is_dir($path)) {
mkdir($path, 0775, recursive: true);
$this->io->write(sprintf('<info>Created directory %s</info>', $relativePath));
}
}
}
/**
* Removes stale cache contents after every install or update.
*/
public function clearCache(): void
{
$cacheDir = getcwd() . '/var/cache';
if (!is_dir($cacheDir)) {
return;
}
foreach (glob($cacheDir . '/*') as $entry) {
is_dir($entry) ? $this->removeDirectory($entry) : unlink($entry);
}
$this->io->write('<info>Cache directory cleared</info>');
}
/**
* Recursively removes a directory and its contents.
*/
private function removeDirectory(string $path): void
{
foreach (glob($path . '/*') as $entry) {
is_dir($entry) ? $this->removeDirectory($entry) : unlink($entry);
}
rmdir($path);
}
}
7. Composer-Plugins vs. Composer-Scripts
Für einfache, projektspezifische Automatisierung reichen Composer-Scripts in aller Regel aus: Sie sind schnell definiert, brauchen kein eigenes Paket und liegen direkt in der composer.json des Projekts. Sobald jedoch Logik über mehrere Projekte hinweg wiederverwendet werden soll, ohne die Handler-Klasse in jedes einzelne Projekt zu kopieren, stößt der Script-Mechanismus an seine Grenzen. Ein Composer-Script gehört zum Root-Paket und muss in jeder composer.json erneut eingetragen werden, ein Composer-Plugin dagegen wird selbst als Abhängigkeit über composer require eingebunden und aktiviert sich automatisch.
Ein echtes Composer-Plugin implementiert Composer\Plugin\PluginInterface mit den Methoden activate(), deactivate() und uninstall(), und optional Composer\EventDispatcher\EventSubscriberInterface, um sich für beliebige Lifecycle-Hooks programmatisch zu registrieren, statt über statische Einträge im scripts-Block. Das lohnt sich, sobald die Automatisierung Konfigurationsoptionen benötigt, eigene Composer-Kommandos über Capable und CommandProvider bereitstellen soll, oder als geteiltes Tooling über ein privates Packagist-Repository an mehrere Teams verteilt wird. Für ein einzelnes Projekt mit überschaubarer Setup-Logik bleibt das einfache Composer-Script jedoch die pragmatischere und wartungsärmere Wahl.
8. Fehlerbehandlung in Scripts
Composer behandelt Fehler in Shell-Befehlen und PHP-Callbacks unterschiedlich, aber mit demselben Ergebnis: Ein fehlgeschlagenes Script bricht die gesamte Composer-Operation ab. Bei einem Shell-Befehl gilt jeder Exit-Code ungleich null als Fehler, genau wie bei jedem anderen Shell-Skript. Bei einem PHP-Callback zählt eine geworfene Exception als Fehler, während ein regulärer Rückgabewert der Methode ignoriert wird, weil die Signatur als void aufgerufen wird. Wer in einem Composer-Hook also gezielt abbrechen will, muss eine Exception werfen, statt lediglich false zurückzugeben.
Zwei Flags beeinflussen das Verhalten von Composer-Scripts global. --no-scripts unterdrückt sämtliche Lifecycle-Hooks bei einem einzelnen Aufruf, was hilfreich ist, um bei der Fehlersuche zu isolieren, ob ein Problem von Composer selbst oder von einem eigenen Script verursacht wird, oder um in sicherheitskritischen Umgebungen das Ausführen von potenziell unvertrauenswürdigem Code aus Abhängigkeiten zu verhindern. --no-dev beeinflusst zwar nicht direkt, ob Scripts laufen, wohl aber, ob Pakete aus require-dev installiert und autoladbar sind. Liegt die Handler-Klasse eines produktionsrelevanten Composer-Scripts versehentlich nur im Dev-Autoload, schlägt der Hook bei einem --no-dev-Deployment mit einem Class-Not-Found-Fehler fehl, ein klassischer und leicht vermeidbarer Konfigurationsfehler.
9. Alle Hook-Typen im direkten Vergleich
Die vorigen Abschnitte haben vier grundsätzlich verschiedene Mechanismen vorgestellt, mit denen sich Composer-Automatisierung umsetzen lässt. Welcher davon der richtige ist, hängt vom Umfang der Aufgabe ab und davon, ob die Logik projektspezifisch bleibt oder über mehrere Projekte hinweg geteilt werden soll. Die folgende Tabelle stellt Shell-Command-Scripts, PHP-Callback-Scripts, Composer-Plugins und Event-Dispatcher-Subscriber direkt gegenüber.
| Hook-Typ | Einsatzzweck | Komplexität | Wann verwenden | Vorteil |
|---|---|---|---|---|
| Shell-Command-Script | Einzelne CLI-Befehle direkt ausführen | Sehr gering | Kurzer, plattformunabhängiger Befehl | Keine PHP-Klasse nötig |
| PHP-Callback-Script | Projektspezifische Setup-Logik | Gering bis mittel | Zugriff auf Composer\Script\Event nötig | Kein Kindprozess, voller Kontext |
| Composer-Plugin | Wiederverwendbare Automatisierung | Mittel bis hoch | Logik über mehrere Projekte teilen | Automatische Aktivierung per require |
| Event-Dispatcher-Subscriber | Programmatische Registrierung mehrerer Events | Hoch | Plugin reagiert auf viele Events dynamisch | Kein starrer scripts-Block nötig |
In der Praxis beginnt fast jedes Projekt mit einfachen Composer-Scripts für pre-install-cmd und post-install-cmd und wechselt erst dann zu einem echten Plugin, wenn die Automatisierung tatsächlich über die Grenzen des einzelnen Repositories hinauswächst. Diese schrittweise Eskalation, vom Shell-Befehl über den PHP-Callback bis zum eigenen Plugin, ist kein Zeichen mangelnder Planung, sondern der pragmatische Normalfall bei Composer-Hooks.
10. Zusammenfassung
Die wichtigsten Composer-Scripts und Lifecycle-Hooks lösen immer dasselbe Grundproblem: manuelle Setup-Schritte, die nach jedem Checkout wiederholt werden müssen, verschwinden aus README-Dateien und Entwicklerköpfen und landen versioniert in der composer.json. Die vier zentralen Kommando-Events pre-install-cmd, post-install-cmd, pre-update-cmd und post-update-cmd decken die meisten projektweiten Anwendungsfälle ab, post-autoload-dump und post-create-project-cmd ergänzen sie um spezialisierte Zeitpunkte.
Shell-Befehle eignen sich für kurze, plattformunabhängige Aufrufe, PHP-Callbacks über statische Methoden und Composer\Script\Event für alles, was Zugriff auf IO, Konfiguration und Dev-Mode-Status braucht. Sobald Automatisierung über ein einzelnes Repository hinauswächst, ist ein echtes Composer-Plugin die passendere Wahl als ein weiterhin kopiertes Composer-Script. Und wer --no-scripts sowie die Fallstricke bei --no-dev kennt, vermeidet die häufigsten Fehlerquellen im Zusammenspiel von Composer-Hooks und Deployment-Pipelines.
Composer-Scripts und Lifecycle-Hooks - Das Wichtigste auf einen Blick
Event-Kategorien
Kommando-Events wie post-install-cmd für projektweite Automatisierung, Paket-Events für Composer-Plugins.
Handler-Typen
Shell-Befehl als String für kurze Aufrufe, statischer PHP-Callback mit Composer\Script\Event für alles Weitere.
Eigene Kommandos
Der scripts-Block funktioniert auch als Alias-Mechanismus, aufrufbar über composer run-script oder Kurzform.
Plugins statt Scripts
Sobald Logik projektübergreifend geteilt werden soll, übernimmt ein Composer-Plugin mit eigener Aktivierung die Aufgabe.
11. FAQ: Composer-Scripts und Lifecycle-Hooks
1Was sind Composer-Scripts?
2Welche Composer-Events gibt es?
3Shell-Befehl oder PHP-Callback: was ist besser?
4Was liefert Composer\Script\Event?
5Wie definiere ich ein eigenes Composer-Kommando?
6run-script mit zusätzlichen Argumenten?
7Wann ein Composer-Plugin statt eines Scripts?
8Was passiert bei einem Fehler im Script?
9Was macht --no-scripts?
10Warum schlägt ein Hook bei --no-dev fehl?
Mironsoft
PHP-Architektur, Automatisierung und Magento-Entwicklung
Composer-Scripts und Lifecycle-Hooks sauber im eigenen Projekt einsetzen?
Wir analysieren bestehende Setup-Anleitungen und manuelle Onboarding-Schritte und ersetzen sie durch versionierte Composer-Scripts und Lifecycle-Hooks, mit sauberer Fehlerbehandlung und PHPStan-Absicherung auf Level 5 und höher.
Code-Review
Analyse bestehender Composer-Scripts auf Fehleranfälligkeit und Portabilität
Automatisierung
Setup-Schritte, Cache-Handling und Onboarding als PHP-Callback-Hooks umsetzen
Plugin-Entwicklung
Eigene Composer-Plugins für geteiltes Tooling über mehrere Projekte hinweg