vom composer require bis zum eigenen privaten Recipe
Symfony Flex Recipes legen bei einem composer require automatisch Konfigurationsdateien, Verzeichnisse und Umgebungsvariablen an. Wer versteht, wie das manifest.json aufgebaut ist und wie das contrib-Repository funktioniert, kann eigene Recipes fuer interne Bundles bauen und in privaten Teams reproduzierbar verteilen.
Inhaltsverzeichnis
- 1. Was Symfony Flex Recipes wirklich sind
- 2. Der Recipe-Lifecycle: von composer require bis fertiger Konfiguration
- 3. Aufbau eines Recipes: manifest.json im Detail
- 4. Offizielles Repository vs. contrib-Repository
- 5. Eigene private Recipes erstellen und hosten
- 6. Recipes unpacken und gezielt anpassen
- 7. Post-Install-Skripte und Copy-from-Package
- 8. Recipes in CI/CD und reproduzierbaren Builds
- 9. Recipes im Vergleich zu manueller Konfiguration
- 10. Zusammenfassung
- 11. FAQ
1. Was Symfony Flex Recipes wirklich sind
Ein Symfony Flex Recipe ist ein kleines, versioniertes Paket an Anweisungen, das genau beschreibt, welche Dateien, Verzeichnisse und Konfigurationseintraege ein Composer-Paket beim Installieren in ein Symfony-Projekt einbringen soll. Ohne Flex Recipes muesste jedes Team nach composer require symfony/mailer manuell die passende config/packages/mailer.yaml anlegen, die .env-Variablen ergaenzen und pruefen, ob ein Bundle in der bundles.php registriert werden muss. Flex Recipes automatisieren genau diesen Schritt.
Technisch ist ein Recipe kein Teil des eigentlichen Composer-Pakets, sondern ein separater Eintrag in einem Recipe-Repository, der ueber den Paketnamen und die Versionsbedingung mit dem Paket verknuepft ist. Diese Trennung ist wichtig: ein Paket-Maintainer kann ein Recipe unabhaengig vom Paket-Release aktualisieren, und ein Team kann entscheiden, ob es einem Recipe vertraut, ohne den Code des Pakets selbst zu veraendern. Der symfony/flex-Composer-Plugin ist die Komponente, die diese Verknuepfung zur Installationszeit herstellt.
Dieser Artikel geht ueber die Oberflaeche von Flex Recipes hinaus: Wie ist ein Recipe intern aufgebaut, wie unterscheidet sich das offizielle vom contrib-Repository, und vor allem, wie baut man ein eigenes privates Recipe fuer ein internes Bundle, das im Team genauso automatisiert installiert werden soll wie ein offizielles Symfony-Paket.
2. Der Recipe-Lifecycle: von composer require bis fertiger Konfiguration
Wenn composer require ein Paket installiert, prueft das Flex-Plugin nach dem Composer-eigenen Installationsschritt, ob fuer dieses Paket und diese Version ein Recipe existiert. Dazu fragt es den konfigurierten Recipe-Endpoint ab, standardmaessig https://raw.githubusercontent.com/symfony/recipes/flex/main/index.json fuer offizielle und ein separates Repository fuer contrib-Recipes. Wird ein passendes Recipe gefunden, laedt Flex dessen Inhalt herunter und wendet die darin definierten Operationen an.
Diese Operationen sind deklarativ: Dateien aus dem Recipe werden an definierte Zielpfade im Projekt kopiert, Eintraege werden an die .env angehaengt, und Bundle-Klassen werden in config/bundles.php registriert. Nach erfolgreicher Anwendung schreibt Flex einen Eintrag in symfony.lock, der Paketname, Recipe-Version und angewendete Dateien festhaelt. Diese Lock-Datei ist der Schluessel zur Reproduzierbarkeit: ein zweites composer install auf einer anderen Maschine wendet exakt dieselben Recipe-Versionen an, weil symfony.lock mit eingecheckt wird.
{
"symfony/mailer": {
"version": "7.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.1",
"ref": "b09e162839da9b0027bee6b06024b9089dab9d75"
},
"files": [
"config/packages/mailer.yaml"
]
}
}
Dieser Ausschnitt aus einer symfony.lock zeigt genau, welches Recipe fuer welche Paketversion angewendet wurde, inklusive Git-Referenz. Wird das Paket spaeter aktualisiert und existiert eine neue Recipe-Version, zeigt composer recipes an, dass ein Update verfuegbar ist, wendet es aber nicht automatisch an, um bestehende manuelle Anpassungen nicht zu ueberschreiben.
3. Aufbau eines Recipes: manifest.json im Detail
Im Zentrum jedes Recipes steht die manifest.json, die alle Operationen beschreibt, die Flex beim Installieren ausfuehren soll. Die wichtigsten Schluessel sind bundles fuer die automatische Registrierung in config/bundles.php, copy-from-recipe fuer Dateien, die unveraendert kopiert werden, und env fuer Eintraege, die an die .env-Datei angehaengt werden. Ergaenzend gibt es gitignore fuer Eintraege in der .gitignore und post-install-output fuer eine Hinweismeldung, die nach der Installation im Terminal angezeigt wird.
{
"bundles": {
"App\\CustomLoggingBundle\\CustomLoggingBundle": ["all"]
},
"copy-from-recipe": {
"config/": "%CONFIG_DIR%/"
},
"env": {
"CUSTOM_LOGGING_DSN": "custom-log://localhost:9000"
},
"gitignore": [
"/var/custom-logs/"
],
"post-install-output": [
"The bundle has been installed and enabled in all environments.",
"Set CUSTOM_LOGGING_DSN in your .env.local file for local development."
]
}
Der Platzhalter %CONFIG_DIR% in copy-from-recipe wird zur Installationszeit durch den tatsaechlichen Konfigurationspfad des Projekts ersetzt, standardmaessig config. Diese Indirektion ermoeglicht, dass ein Recipe unabhaengig von individuellen Projektstrukturen funktioniert, solange die Standardkonvention eingehalten wird. Der Schluessel bundles mit dem Wert ["all"] registriert das Bundle fuer alle Umgebungen; alternativ koennen einzelne Environments wie ["dev", "test"] angegeben werden, wenn ein Bundle nur in bestimmten Kontexten aktiv sein soll.
4. Offizielles Repository vs. contrib-Repository
Symfony pflegt zwei getrennte Recipe-Repositories: symfony/recipes fuer offizielle Pakete, die vom Symfony-Core-Team oder eng verwandten Projekten gepflegt werden, und symfony/recipes-contrib fuer Community-Recipes von Drittanbieter-Paketen. Der wesentliche Unterschied liegt in der Vertrauensstufe: Recipes aus dem contrib-Repository werden von Flex standardmaessig nur nach expliziter Bestaetigung angewendet, waehrend offizielle Recipes automatisch ausgefuehrt werden.
Diese Bestaetigungsabfrage ist keine reine Formalitaet, sondern ein bewusster Sicherheitsmechanismus: ein contrib-Recipe kann beliebige Dateien schreiben und Umgebungsvariablen setzen, und ein Team sollte diese Aenderungen kennen, bevor sie automatisch angewendet werden. Mit der Datei symfony.lock und dem Eintrag in composer.json unter extra.symfony.allow-contrib laesst sich dieses Verhalten fuer CI-Umgebungen steuern, in denen keine interaktive Bestaetigung moeglich ist.
{
"extra": {
"symfony": {
"allow-contrib": false,
"endpoint": [
"https://api.github.com/repos/my-org/private-recipes/contents/index.json",
"flex://defaults"
]
}
}
}
Mit allow-contrib: false unterbindet ein Team contrib-Recipes global und erzwingt, dass jede Installation eines Drittanbieter-Pakets ohne automatische Konfiguration ablaeuft, was in stark regulierten Umgebungen oft gewuenscht ist. Der zusaetzliche endpoint-Eintrag zeigt bereits, wie ein eigenes privates Recipe-Repository neben den Standard-Endpoints eingebunden wird, was im naechsten Abschnitt vertieft wird.
5. Eigene private Recipes erstellen und hosten
Fuer interne Bundles lohnt sich ein eigenes Recipe, sobald mehr als ein Projekt dasselbe Bundle nutzt und die manuelle Konfiguration nach jedem composer require wiederholt wird. Ein privates Recipe-Repository ist strukturell identisch zum offiziellen: ein Git-Repository mit einer index.json, die auf einzelne manifest.json-Dateien je Paket und Version verweist. Dieses Repository kann auf GitHub, GitLab oder einem selbst gehosteten Git-Server liegen, solange es ueber eine der von Flex unterstuetzten Endpoint-Formate erreichbar ist.
Die Struktur folgt dem Muster {vendor}/{package}/{major.minor}/manifest.json. Fuer ein internes Bundle acme/audit-log-bundle in Version 2.x liegt das Recipe also unter acme/audit-log-bundle/2.0/manifest.json, ergaenzt um einen Eintrag in der zentralen index.json, der Paketname und unterstuetzte Versionsbereiche referenziert.
{
"acme/audit-log-bundle": {
"versions": {
"2.0": {
"version": "2.0",
"ref": "main"
}
}
}
}
In der composer.json jedes Projekts, das dieses private Recipe nutzen soll, wird der eigene Endpoint zusaetzlich zu den Standard-Endpoints eingetragen, wie im vorherigen Abschnitt gezeigt. Fuer Teams mit Private Packagist bietet sich an, das Recipe-Repository direkt neben den privaten Composer-Paketen zu hosten, sodass Zugriffsrechte einheitlich ueber dasselbe Authentifizierungssystem verwaltet werden koennen, statt ein separates Berechtigungskonzept fuer das Recipe-Repository zu pflegen.
6. Recipes unpacken und gezielt anpassen
Nicht jedes Recipe passt eins zu eins auf ein bestehendes Projekt, besonders wenn zusaetzliche Anpassungen an der generierten Konfiguration noetig sind. Der Befehl composer symfony:recipes:install {paket} --force -v wendet ein Recipe erneut an, auch wenn es bereits installiert war, was nuetzlich ist, wenn eine Datei versehentlich geloescht wurde. Fuer das gezielte Entfernen der Flex-Verwaltung eines Pakets, um die generierte Konfiguration frei anzupassen, ohne dass Flex sie bei zukuenftigen Updates weiter verwaltet, dient composer symfony:recipes als Uebersichtsbefehl, gefolgt von einem manuellen Eintrag entfernen aus symfony.lock.
# List all installed recipes and their current status
composer symfony:recipes
# Re-apply a specific recipe, useful after accidentally deleting generated files
composer symfony:recipes:install symfony/mailer --force -v
# Show the raw recipe manifest for a package before deciding to trust it
composer symfony:recipes:install symfony/mailer --dry-run -v
Der --dry-run-Modus zeigt exakt an, welche Dateien ein Recipe schreiben und welche Umgebungsvariablen es setzen wuerde, ohne tatsaechlich etwas zu veraendern. Das ist besonders bei contrib-Recipes ein sinnvoller Zwischenschritt, bevor man die interaktive Bestaetigung erteilt, weil man so vorab sieht, ob das Recipe ueber die erwartete Konfigurationsdatei hinaus auch andere, unerwartete Aenderungen vornehmen wuerde.
7. Post-Install-Skripte und Copy-from-Package
Neben copy-from-recipe, das Dateien aus dem Recipe selbst kopiert, gibt es copy-from-package, das Dateien direkt aus dem installierten Composer-Paket in das Projekt kopiert. Das ist nuetzlich, wenn eine Konfigurationsdatei eng an eine bestimmte Paketversion gebunden ist und im Paket selbst mitgeliefert wird, statt separat im Recipe-Repository gepflegt zu werden, was Redundanz vermeidet, wenn Paket und Recipe vom selben Team gepflegt werden.
post-install-output ist der am haeufigsten unterschaetzte Teil eines Recipes, weil er die einzige direkte Kommunikationsstelle zum Entwickler ist, der das Paket gerade installiert hat. Ein gutes Recipe nutzt diesen Hinweis, um genau zu sagen, welche manuellen Schritte noch fehlen, etwa das Setzen eines API-Schluessels in .env.local, statt den Entwickler die Dokumentation separat suchen zu lassen.
{
"copy-from-package": {
"config/audit-log.dist.yaml": "%CONFIG_DIR%/packages/audit_log.yaml"
},
"post-install-output": [
"AcmeAuditLogBundle has been installed.",
" * Set AUDIT_LOG_API_KEY in your .env.local file.",
" * Run 'bin/console acme:audit-log:init' once to create the storage table."
]
}
Ein guter Massstab fuer die Qualitaet eines eigenen Recipes: Kann ein neues Teammitglied nach composer require acme/audit-log-bundle allein anhand der post-install-output-Meldungen produktiv werden, ohne ein Wiki oder README konsultieren zu muessen? Wenn ja, ist das Recipe vollstaendig genug, um im Team standardmaessig genutzt zu werden.
8. Recipes in CI/CD und reproduzierbaren Builds
Weil symfony.lock jede angewendete Recipe-Version mit Git-Referenz festhaelt, verhaelt sich composer install in der CI-Pipeline deterministisch: dieselbe Recipe-Version wird angewendet wie beim urspruenglichen composer require, unabhaengig davon, ob sich das Recipe-Repository seither weiterentwickelt hat. Das ist ein entscheidender Unterschied zu einer manuellen Konfiguration, die stillschweigend von der Dokumentation abweichen kann, ohne dass ein Build das bemerkt.
In CI-Umgebungen ohne interaktives Terminal muss allow-contrib explizit gesetzt werden, weil Flex sonst auf eine Bestaetigung wartet, die nie kommt, und der Build haengen bleibt. Die Kombination aus COMPOSER_NO_INTERACTION=1 als Umgebungsvariable und einer expliziten allow-contrib-Einstellung in composer.json stellt sicher, dass Recipe-Anwendung in der Pipeline vorhersehbar ablaeuft, ohne manuelles Eingreifen und ohne versehentlich contrib-Recipes automatisch zu akzeptieren, die eigentlich eine bewusste Entscheidung erfordern sollten.
9. Recipes im Vergleich zu manueller Konfiguration
Die folgende Tabelle stellt Flex Recipes der klassischen manuellen Konfiguration gegenueber, wie sie vor Symfony Flex oder in Projekten ohne Flex ueblich war.
| Aspekt | Manuelle Konfiguration | Flex Recipe | Vorteil |
|---|---|---|---|
| Zeit bis lauffaehig | 5 bis 15 Minuten | Sekunden | Automatische Dateierstellung |
| Konsistenz zwischen Projekten | Abhaengig von Doku-Treue | Identisch pro Recipe-Version | Weniger Konfigurationsdrift |
| Reproduzierbarkeit in CI | Nicht garantiert | Ueber symfony.lock fixiert | Deterministische Builds |
| Transparenz der Aenderungen | Vollstaendig manuell nachvollziehbar | Sichtbar via --dry-run vor Anwendung | Beide Ansaetze pruefbar |
Der einzige Bereich, in dem manuelle Konfiguration einen echten Vorteil behaelt, ist die vollstaendige Kontrolle ohne jede Automatisierung, etwa bei sehr exotischen Projektstrukturen, die von der Standardkonvention abweichen. Fuer die grosse Mehrheit der Symfony-Projekte ueberwiegt der Nutzen von Flex Recipes deutlich, besonders sobald mehrere Projekte dieselben internen Pakete teilen.
Mironsoft
Symfony-Bundle-Entwicklung und interne Composer-Infrastruktur
Eigene Symfony Flex Recipes fuer interne Bundles bauen?
Wir entwickeln private Recipe-Repositories fuer eure internen Bundles, richten den Recipe-Endpoint fuer alle Projekte ein und sorgen dafuer, dass composer require bei euch genauso reibungslos funktioniert wie bei offiziellen Symfony-Paketen.
Recipe-Entwicklung
manifest.json fuer eure Bundles inklusive Konfigurationsdateien
Private Repository
Hosting neben eurem Private Packagist mit einheitlicher Zugriffskontrolle
CI-Integration
Reproduzierbare Builds mit korrekter allow-contrib-Konfiguration
10. Zusammenfassung
Symfony Flex Recipes automatisieren genau die Schritte, die nach einem composer require sonst manuell wiederholt werden muessten: Konfigurationsdateien anlegen, Umgebungsvariablen setzen, Bundles registrieren. Die manifest.json ist das Herzstueck jedes Recipes, mit klar definierten Schluesseln fuer Dateikopien, Umgebungsvariablen und Hinweismeldungen. Das offizielle Repository und das contrib-Repository unterscheiden sich vor allem im Vertrauensniveau, das ueber die interaktive Bestaetigung abgebildet wird.
Fuer Teams mit mehreren Projekten und internen Bundles lohnt sich ein eigenes privates Recipe-Repository, das strukturell identisch zum offiziellen aufgebaut ist und ueber einen zusaetzlichen Endpoint in der composer.json eingebunden wird. Die symfony.lock-Datei macht Recipe-Anwendungen reproduzierbar und CI-tauglich, waehrend --dry-run vor der Anwendung eines fremden Recipes volle Transparenz ueber die geplanten Aenderungen schafft. Wer diese Bausteine kombiniert, macht Flex Recipes zu einem festen Bestandteil der eigenen Symfony-Infrastruktur statt zu einer reinen Bequemlichkeit fuer offizielle Pakete.
Symfony Flex Recipes — Das Wichtigste auf einen Blick
manifest.json
Definiert bundles, copy-from-recipe, env, gitignore und post-install-output fuer die automatische Installation.
Offiziell vs. contrib
Contrib-Recipes brauchen interaktive Bestaetigung, allow-contrib steuert dieses Verhalten fuer CI-Umgebungen.
Eigene Recipes
Privates Git-Repository mit index.json, zusaetzlicher Endpoint-Eintrag in composer.json jedes Projekts.
symfony.lock
Fixiert Recipe-Version und Git-Referenz, macht composer install in CI vollstaendig reproduzierbar.