Versionierungsstrategie für interne Symfony Bundles: SemVer richtig anwenden
AI generated
SF
{ }
Symfony · Versionierungsstrategie · SemVer · Composer
Versionierungsstrategie für interne Symfony Bundles
Semantic Versioning, Breaking Changes und Deprecation-Pfade richtig planen

Eine klare Versionierungsstrategie entscheidet darueber, ob ein internes Symfony Bundle vertrauensvoll per composer update aktualisiert werden kann oder bei jedem Update fuer Ueberraschungen sorgt. Dieser Artikel zeigt, wie Semantic Versioning konsequent angewendet wird, wie Breaking Changes erkannt und wie Deprecation-Pfade fuer sanfte Migrationen gestaltet werden.

18 Min. Lesezeit SemVer · Changelog · Deprecation · BC-Checks Symfony 7.x · Composer 2.x

1. Warum Versionierung bei internen Bundles besonders wichtig ist

Bei einem oeffentlichen Composer-Paket zwingt die Community durch Issues und Pull Requests meist schnell zu einer sauberen Versionierungsstrategie. Bei einem internen Symfony Bundle fehlt dieser externe Druck, was dazu fuehrt, dass viele Teams Versionsnummern zunaechst nachlaessig behandeln, etwa mit staendig wachsenden Patch-Versionen fuer alles, inklusive echter Breaking Changes. Das raecht sich, sobald mehrere Projekte mit unterschiedlichem Update-Rhythmus dasselbe Bundle konsumieren.

Eine durchdachte Versionierungsstrategie ist deshalb kein bürokratisches Beiwerk, sondern die Grundlage dafuer, dass composer update in einem konsumierenden Projekt vorhersagbar bleibt. Wenn ein Team weiss, dass eine neue Minor-Version niemals bestehenden Code bricht, kann es Updates automatisiert einspielen, ohne jedes Mal den kompletten Diff manuell zu pruefen. Fehlt diese Verlaesslichkeit, bleiben Projekte aus Vorsicht auf veralteten, potenziell unsicheren Versionen stehen.

Fuer ein Symfony Bundle, das von mehreren Teams mit unterschiedlichem Kontextwissen genutzt wird, ist eine klare Versionierungsstrategie zudem eine Form von Dokumentation: Die Versionsnummer selbst kommuniziert bereits, wie riskant ein Update voraussichtlich ist, noch bevor jemand den Changelog liest.

Ein weiterer, oft uebersehener Aspekt: Eine unklare Versionierungsstrategie erschwert auch das Debugging in der Produktion erheblich. Wenn ein Fehler auftritt und niemand mit Sicherheit sagen kann, welche Bundle-Version zu welchem Zeitpunkt in welchem Projekt aktiv war, wird die Fehlersuche unnoetig aufwendig. Klare, konsistent vergebene Versionsnummern sind deshalb auch ein Werkzeug fuer Incident-Response, nicht nur fuer die Update-Planung.

2. Semantic Versioning: Major, Minor, Patch im Bundle-Kontext

Semantic Versioning definiert drei Zahlen im Format MAJOR.MINOR.PATCH mit klaren Bedeutungen: Ein Patch-Release behebt Fehler ohne Verhaltensaenderung der oeffentlichen API, ein Minor-Release fuegt neue, abwaertskompatible Funktionalitaet hinzu, und ein Major-Release enthaelt mindestens eine abwaertsinkompatible Aenderung. Fuer ein Symfony Bundle ist die oeffentliche API dabei bewusst weiter gefasst als nur PHP-Methodensignaturen: Sie umfasst auch den Configuration Tree, alle als public markierten Service-IDs, Event-Namen und die Struktur eventuell mitgelieferter Routen.

Diese Erweiterung des API-Begriffs ist entscheidend, weil viele Breaking Changes in Symfony Bundles nicht im PHP-Code selbst passieren, sondern in der Konfiguration. Wird ein Konfigurationsschluessel im Configuration Tree umbenannt oder ein Default-Wert veraendert, ist das aus Sicht eines konsumierenden Projekts ein Breaking Change, auch wenn kein einziger Methodenaufruf betroffen ist. Eine gute Versionierungsstrategie behandelt Konfigurationsaenderungen deshalb mit derselben Sorgfalt wie Code-Aenderungen.


{
  "name": "acme/audit-bundle",
  "version": "3.2.0",
  "extra": {
    "branch-alias": {
      "dev-main": "3.x-dev"
    }
  }
}

3. Was bei einem Symfony Bundle als Breaking Change zaehlt

Fuer die praktische Versionierungsstrategie eines Bundles ist eine explizite Liste hilfreich, was als Breaking Change gilt: das Entfernen oder Umbenennen eines public markierten Service, das Aendern der Signatur einer public API-Methode, das Entfernen eines Konfigurationsschluessels ohne Deprecation-Phase, das Aendern eines Default-Werts, der das Laufzeitverhalten aendert, und das Erhoehen der minimal unterstuetzten PHP- oder Symfony-Version.

Nicht jede Aenderung, die sich wie ein Breaking Change anfuehlt, ist auch tatsaechlich einer im Sinne von SemVer. Interne, als private markierte Services duerfen sich in jeder Minor-Version aendern, weil sie explizit nicht Teil der oeffentlichen API sind. Diese Unterscheidung funktioniert aber nur, wenn Services konsequent und korrekt als public oder private markiert werden, was wiederum ein sauberes Extension-Design voraussetzt, wie es eine gut geschnittene Bundle Extension liefert.

Eine praktische Faustregel fuer Grenzfaelle: Sobald ein konsumierendes Projekt eine Aenderung nur durch eigenen Code-Anpassungsaufwand ueberleben kann, handelt es sich um einen Breaking Change, unabhaengig davon, ob die Aenderung technisch im PHP-Code, in der Konfiguration oder im Datenbankschema stattfindet. Diese konsumentenzentrierte Sichtweise ist verlaesslicher als eine rein technische Definition, die sich zu leicht auf Implementierungsdetails verengt.

4. Version-Constraints in der composer.json des Bundles

Ein haeufig unterschaetzter Aspekt der Versionierungsstrategie: Die eigenen Abhaengigkeiten des Bundles in composer.json sollten nicht zu eng gefasst sein. Eine Anforderung wie symfony/framework-bundle mit exakt einer Version zwingt jedes konsumierende Projekt auf genau diese Symfony-Version, was bei mehreren Bundles im selben Projekt schnell zu unloesbaren Versionskonflikten fuehrt. Eine Spanne wie ^6.4 || ^7.0 gibt dem Composer-Resolver ausreichend Spielraum, ohne die Kompatibilitaet zu gefaehrden.

Gleichzeitig sollte die Untergrenze der Spanne bewusst gewaehlt werden: Sie markiert die aelteste Symfony-Version, gegen die das Bundle tatsaechlich getestet wird. Eine zu grosszuegig nach unten offene Spanne, die niemals in der CI-Matrix getestet wird, ist keine echte Kompatibilitaetsgarantie, sondern lediglich eine unbelegte Behauptung, die im schlimmsten Fall erst beim Kunden zum Problem wird.


{
  "require": {
    "php": ">=8.2",
    "symfony/framework-bundle": "^6.4 || ^7.0",
    "symfony/dependency-injection": "^6.4 || ^7.0"
  },
  "conflict": {
    "acme/legacy-audit-bundle": "*"
  }
}

5. Deprecation-Pfade statt abruptem Entfernen

Statt eine Funktion in einem Major-Release abrupt zu entfernen, sollte eine solide Versionierungsstrategie immer einen Deprecation-Pfad ueber mindestens eine Minor-Version vorsehen. Symfony selbst nutzt dieses Muster konsequent: Eine als deprecated markierte Methode bleibt funktionsfaehig, loest aber bei Aufruf eine deprecation notice ueber trigger_deprecation() aus der symfony/deprecation-contracts-Bibliothek aus, die in Tests und im Symfony Profiler sichtbar wird.

Dieser Ansatz gibt konsumierenden Teams Zeit, ihren Code anzupassen, bevor die naechste Major-Version die alte Funktionalitaet tatsaechlich entfernt. Wichtig ist, dass die Deprecation-Meldung konkret sagt, was stattdessen zu verwenden ist, nicht nur, dass etwas veraltet ist. Eine gute Meldung nennt die Ersatzmethode oder den neuen Konfigurationsschluessel direkt in der Meldung selbst.


// src/Service/AuditLogger.php
declare(strict_types=1);

namespace Acme\AuditBundle\Service;

final class AuditLogger
{
    /**
     * @deprecated since 3.2, use logEntry() instead, will be removed in 4.0
     */
    public function log(string $message): void
    {
        trigger_deprecation(
            'acme/audit-bundle',
            '3.2',
            'The "%s()" method is deprecated, use "logEntry()" instead.',
            __METHOD__
        );

        $this->logEntry($message, []);
    }

    public function logEntry(string $message, array $context): void
    {
        // Actual implementation of the audit log entry
    }
}

6. Ein Changelog, das Entwickler tatsaechlich lesen

Ein CHANGELOG.md nach dem Format von Keep a Changelog ist der zentrale Kommunikationskanal einer Versionierungsstrategie. Jede Version bekommt einen eigenen Abschnitt mit den Kategorien Added, Changed, Deprecated, Removed, Fixed und Security. Diese Struktur macht es einem konsumierenden Team moeglich, in wenigen Sekunden zu erfassen, ob ein Update fuer sie relevant ist, ohne die komplette Commit-Historie zu durchsuchen.

Besonders wichtig fuer ein Symfony Bundle ist ein eigener Abschnitt Upgrade Notes bei jedem Major-Release, der Schritt fuer Schritt beschreibt, was ein konsumierendes Projekt anpassen muss. Ohne diese explizite Anleitung verlassen sich Teams auf Trial and Error, was bei komplexer Bundle-Konfiguration schnell zu frustrierenden Fehlversuchen fuehrt, obwohl die eigentliche Migration oft nur wenige Zeilen betrifft.

Ein Changelog lohnt sich zudem nur, wenn er tatsaechlich bei jedem Release aktualisiert wird, nicht erst im Nachhinein rekonstruiert. Ein bewaehrter Trick: Jeder Pull Request, der eine oeffentlich sichtbare Aenderung enthaelt, muss bereits einen Changelog-Eintrag im Unreleased-Abschnitt mitbringen, bevor er gemerged werden darf. So entsteht der Changelog kontinuierlich statt in einer muehsamen Nachbetrachtung kurz vor dem Release.


## [3.2.0] - 2026-07-15
### Added
- New logEntry() method with structured context array support

### Deprecated
- log() is deprecated, use logEntry() instead, will be removed in 4.0

### Fixed
- Retention cleanup command no longer skips the last day of a month

## [3.1.0] - 2026-05-02
### Changed
- Default table_name changed from "audit" to "audit_log" for new installs only

7. Automatisierte Breaking-Change-Erkennung mit Tooling

Manuelles Einschaetzen, ob eine Aenderung ein Breaking Change ist, bleibt fehleranfaellig, besonders unter Zeitdruck vor einem Release. Tools wie roave/backward-compatibility-check analysieren zwei Git-Referenzen eines PHP-Projekts und listen automatisch alle erkannten BC-Brueche auf, von entfernten Methoden bis zu geaenderten Typ-Deklarationen. In einer CI-Pipeline eingebunden, verhindert dieses Tool, dass ein versehentlicher Breaking Change als Minor- oder Patch-Version veroeffentlicht wird.

Diese automatisierte Pruefung ersetzt nicht die bewusste Entscheidung ueber Konfigurationsaenderungen, die das Tool nicht analysieren kann, ergaenzt aber die rein codebasierte Pruefung sinnvoll. Ein realistischer Workflow kombiniert das Tool mit einer manuellen Checkliste fuer Configuration-Tree-Aenderungen, damit beide Kategorien von Breaking Changes zuverlaessig erkannt werden, bevor ein Release getaggt wird.


# Compare the current branch against the last tagged release for BC breaks
vendor/bin/roave-backward-compatibility-check \
  --from=3.1.0 \
  --to=main

# Exit code is non-zero when a breaking change is detected,
# use this in a CI job to block accidental major changes in a minor release

Wer dieses Tool in die CI-Pipeline integriert, sollte den Check als verpflichtenden Schritt vor jedem Merge in den main-Branch einrichten, nicht erst kurz vor dem Release-Tagging. So faellt ein versehentlicher Breaking Change bereits im Pull Request auf, wenn eine Korrektur noch guenstig ist, statt erst nach dem Release, wenn bereits konsumierende Projekte betroffen sein koennten.

8. Ein wiederholbarer Release-Prozess

Ein Bundle-Release sollte kein manueller, fehleranfaelliger Vorgang sein, sondern ein wiederholbarer Prozess: Changelog aktualisieren, Version in composer.json anpassen falls dort explizit gepflegt, BC-Check ausfuehren, Git-Tag setzen und den Tag pushen. Bei Anbindung an Private Packagist oder Satis loest der Tag automatisch die Synchronisation aus, sodass konsumierende Projekte die neue Version sofort per composer update erhalten koennen.

Fuer Teams mit haeufigen Releases lohnt sich ein Release-Skript oder ein CI-Job, der diese Schritte automatisiert und dabei erzwingt, dass ein Release ohne aktualisierten Changelog-Eintrag gar nicht erst getaggt werden kann. Diese Automatisierung reduziert die Haupt-Fehlerquelle bei manueller Versionierung: das Vergessen des Changelogs oder eine falsch gewaehlte Versionsnummer unter Zeitdruck.


#!/usr/bin/env bash
# release.sh — repeatable release workflow for an internal Symfony bundle
set -euo pipefail

VERSION="$1"

# Refuse to tag without an updated changelog entry for this version
grep -q "## \[$VERSION\]" CHANGELOG.md || {
  echo "Add a changelog entry for $VERSION before releasing." >&2
  exit 1
}

vendor/bin/roave-backward-compatibility-check --from="$(git describe --tags --abbrev=0)" --to=HEAD
git tag -a "$VERSION" -m "Release $VERSION"
git push origin "$VERSION"

9. Versionierungsstile im Vergleich

Nicht jede Versionierungsstrategie passt zu jedem Bundle. Die folgende Tabelle vergleicht drei gaengige Ansaetze.

Ansatz Vorhersagbarkeit Aufwand Geeignet fuer
Striktes SemVer mit Deprecation Sehr hoch Hoch, braucht Disziplin Bundles mit mehreren Konsumenten-Teams
Kalender-Versionierung (CalVer) Mittel Gering Bundles mit einem einzigen Konsumenten
dev-main ohne echte Tags Sehr niedrig Keiner Nichts, nur fuer lokale Entwicklung

Fuer die meisten internen Symfony Bundles mit mehr als einem konsumierenden Projekt ist striktes Semantic Versioning mit sauberen Deprecation-Pfaden der einzige Ansatz, der langfristig Vertrauen schafft. dev-main-Referenzen ohne echte Versionsnummern sind fuer produktive Abhaengigkeiten grundsaetzlich ungeeignet, weil sie jede Vorhersagbarkeit aufgeben.

Kalender-basierte Versionierung kann eine sinnvolle Alternative sein, wenn ein Bundle wirklich nur von einem einzigen Team in einem einzigen Projekt genutzt wird und die formale Strenge von SemVer keinen praktischen Zusatznutzen bringt. Sobald aber ein zweites Team oder ein zweites Projekt hinzukommt, lohnt sich der Umstieg auf striktes Semantic Versioning fast immer, weil die Kosten einer falschen Erwartungshaltung an Kompatibilitaet schnell die anfaengliche Ersparnis an Disziplin uebersteigen.

Mironsoft

Symfony Bundle-Governance, Versionierung und Release-Prozesse

Jedes Bundle-Update sorgt fuer boese Ueberraschungen?

Wir etablieren eine klare Versionierungsstrategie fuer eure internen Symfony Bundles, mit automatisierter BC-Erkennung, sauberen Deprecation-Pfaden und einem wiederholbaren Release-Prozess.

SemVer-Einfuehrung

Klare Regeln, was als Breaking Change in eurem Bundle-Kontext zaehlt

BC-Check-Automatisierung

Tooling in der CI-Pipeline, das versehentliche Breaking Changes verhindert

Release-Automatisierung

Changelog, Tagging und Synchronisation als wiederholbarer Prozess

10. Zusammenfassung

Eine solide Versionierungsstrategie fuer interne Symfony Bundles beruht auf konsequentem Semantic Versioning, das nicht nur PHP-Methodensignaturen, sondern auch den Configuration Tree, public Services und Event-Namen als Teil der oeffentlichen API betrachtet. Breaking Changes werden ueber Deprecation-Pfade mit trigger_deprecation() angekuendigt, statt Funktionalitaet abrupt zu entfernen, und ein CHANGELOG.md nach Keep-a-Changelog-Format macht jede Version fuer konsumierende Teams in Sekunden einschaetzbar.

Automatisierte Breaking-Change-Erkennung mit Tools wie roave/backward-compatibility-check faengt versehentliche BC-Brueche vor dem Release ab, waehrend ein wiederholbarer Release-Prozess menschliche Fehler bei Versionsnummer und Changelog minimiert. Wer diese Bausteine kombiniert, macht composer update fuer ein internes Bundle zu einer Routineaufgabe statt zu einer riskanten Wette auf unbekanntes Verhalten.

Versionierungsstrategie für interne Symfony Bundles — Das Wichtigste auf einen Blick

API-Begriff erweitern

Configuration Tree, public Services und Event-Namen zaehlen als oeffentliche API, nicht nur PHP-Signaturen.

Deprecation statt Entfernen

trigger_deprecation() mindestens eine Minor-Version vor dem tatsaechlichen Entfernen.

Changelog-Disziplin

Keep-a-Changelog-Format mit eigenem Upgrade-Notes-Abschnitt bei Major-Releases.

Automatisierung

BC-Check-Tooling in der CI-Pipeline verhindert versehentliche Breaking Changes.

11. FAQ: Versionierungsstrategie für interne Symfony Bundles

1Was zaehlt zur oeffentlichen API eines Bundles?
Nicht nur PHP-Signaturen, auch Configuration Tree, public Services, Event-Namen und Routen.
2Ist ein geaenderter Default-Wert ein Breaking Change?
Ja, sobald sich dadurch das Laufzeitverhalten fuer bestehende Konsumenten aendert.
3Wie kuendigt man Entfernungen sauber an?
Mit trigger_deprecation() mindestens eine Minor-Version vorher, inklusive Hinweis auf die Ersatzfunktion.
4Eng oder weit versionierte Abhaengigkeiten?
Weit genug fuer weniger Konflikte, aber begrenzt auf tatsaechlich getestete Symfony-Versionen.
5Welches Changelog-Format?
Keep a Changelog mit Added, Changed, Deprecated, Removed, Fixed, Security, plus Upgrade Notes bei Majors.
6Breaking Changes automatisiert erkennen?
Mit roave/backward-compatibility-check als Pflicht-Check in der CI-Pipeline.
7Erkennt Tooling auch Config-Aenderungen?
Meist nicht vollstaendig, dafuer braucht es zusaetzlich eine manuelle Review-Checkliste.
8Warum keine dev-main-Referenz produktiv?
Der Code kann sich jederzeit aendern, ohne bewusste, nachvollziehbare Versionsentscheidung.
9Wie lange sollte eine Deprecation-Phase dauern?
Mindestens eine vollstaendige Minor-Version vor dem tatsaechlichen Entfernen.
10Was gehoert in einen Release-Prozess?
Changelog aktualisieren, BC-Check, Git-Tag setzen und pushen, idealerweise automatisiert.