Laravel 13 ist da — das hat sich wirklich verändert
Vor drei Wochen habe ich ein Produktions-Deployment kaputt gemacht.
Nicht wegen schlechtem Code. Nicht wegen einem fehlenden Test. Weil ich am Freitagnachmittag eine Laravel 12-Anwendung auf den Laravel 13 Dev-Branch aktualisiert habe — klassischer Fehler — und eine einzelne boot()-Methode in meinem User-Model Ausnahmen warf, die ich noch nie gesehen hatte. Die Queue lief voll. Sentry leuchtete auf wie ein Weihnachtsbaum. Mein Telefon summte sechzehn Mal in vier Minuten.
Stellte sich heraus: Laravel 13 führte eine Einschränkung ein, über die ich noch nicht gelesen hatte. Neue Eloquent-Modellinstanzen können während der boot()-Methode eines Modells nicht mehr erstellt werden. Diese eine Zeile, die ein Role-Modell innerhalb von User::boot() abfragt, hatte zwei Jahre lang einwandfrei funktioniert. Jetzt war es eine tickende Zeitbombe.
Ich habe es in zwanzig Minuten behoben, sobald ich die Änderung verstanden hatte. Aber diese zwanzig Minuten lehrten mich etwas Wichtiges: Laravel 13 sieht auf den ersten Blick wie ein ruhiges Release aus. Unter der Haube verdrahtet es Annahmen neu, auf denen du seit Version 9 aufbaust. Wenn du nicht genau verstehst, was sich geändert hat, lernst du es so wie ich — um 18 Uhr an einem Freitag.
Also tat ich, was ich nach solchen Ereignissen immer tue. Ich las jeden Pull Request. Testete jedes Feature auf drei verschiedenen Projekten. Brach absichtlich Dinge, damit du das nicht musst. Das ist der vollständige Leitfaden, den ich vor drei Wochen gebraucht hätte.
Warum sich dieses Release anders anfühlt
Die meisten großen Laravel-Versionen kommen mit einem Haupt-Feature. Laravel 9 hatte die Symfony Mailer-Migration. Version 10 brachte native Typen. Laravel 11 lieferte die schlanke Anwendungsstruktur. Jede hatte ihren klaren "das ist das Ding"-Moment.
Laravel 13 hat das nicht. Und ich denke, genau das macht es zum wichtigsten Upgrade seit Version 10.
Das Team fokussierte sich auf drei Dinge gleichzeitig: modernes PHP tiefer in die DNA des Frameworks zu ziehen, Infrastrukturverhalten zu härten, das subtile Produktionsfehler verursachte, und die Entwicklererfahrung auf Weisen zu verbessern, die sich täglich summieren. PHP 8-Attribute für Modelle, Jobs und Commands. Eine Cache::touch()-Methode, die ein verschwenderisches Muster eliminiert. Ein Reverb-Datenbanktreiber, der deine Redis-Abhängigkeit für WebSockets beseitigt. Vollständig typisierte Eloquent-Properties.
Kein einzelnes Feature ist auffällig. Alle zusammen verändern, wie sich dein Code beim Schreiben anfühlt.
Das PHP 8.3-Gate
Laravel 13 erfordert PHP 8.3 als absolutes Minimum. Nicht empfohlen — erforderlich. Wenn dein Server PHP 8.2 läuft, bleibst du auf Laravel 12, bis du das zuerst behebst.
Die unterstützten Versionen sind 8.3, 8.4 und 8.5.
Prüfe deinen aktuellen Stand:
php -v
Wenn du 8.2 oder niedriger siehst:
# Ubuntu/Debian
sudo add-apt-repository ppa:ondrej/php
sudo apt update && sudo apt install php8.3
# macOS
brew install php@8.3
# Docker
FROM php:8.3-fpm
PHP-Attribute haben mein Denken umstrukturiert
PHP-Attribute in Laravel 13 sind die größte Verbesserung der Lebensqualität, die das Framework in drei Versionen ausgeliefert hat. Nicht weil sie neue Fähigkeiten hinzufügen. Weil sie grundlegend verändern, wie Laravel-Code gelesen wird.
Jahrelang begann jedes Modell gleich. Eine Wand aus protected Arrays. $fillable, $hidden, $guarded, $appends. Laravel 13 ersetzt das alles durch native PHP 8-Attribute. Und das Kritische — es ist vollständig non-breaking. Dein bestehender Code funktioniert weiter.
So sieht ein Modell jetzt aus:
#[Table('users')]
#[Connection('mysql')]
#[Fillable(['name', 'email', 'password'])]
#[Hidden(['password', 'remember_token'])]
#[Appends(['full_name'])]
class User extends Model
{
public function getFullNameAttribute(): string
{
return "{$this->first_name} {$this->last_name}";
}
}
Die vollständige Attribut-Inventarliste für Eloquent:
| Attribut | Was es ersetzt |
|---|---|
#[Table] |
$table |
#[Fillable] |
$fillable |
#[Guarded] |
$guarded |
#[Hidden] |
$hidden |
#[Visible] |
$visible |
#[Connection] |
$connection |
#[Appends] |
$appends |
#[Touches] |
$touches |
#[Unguarded] |
Neu — kein Property-Äquivalent |
Queue-Jobs bekommen endlich visuelle Logik
Queue-Konfiguration war immer ein Gedächtnispiel. Attribute lösen das vollständig:
#[Connection('redis')]
#[Queue('high-priority')]
#[Tries(3)]
#[Timeout(120)]
#[Backoff([10, 30, 60])]
#[MaxExceptions(2)]
#[UniqueFor(3600)]
class ProcessPayment implements ShouldQueue
{
public function __construct(
private readonly Order $order
) {}
public function handle(): void
{
// Jede Konfigurationsentscheidung ist bei der Klassendeklaration sichtbar
}
}
Artisan-Commands bekommen dieselbe Behandlung
#[Signature('users:cleanup {--days=30 : Anzahl Tage zum Behalten}')]
#[Description('Inaktive Benutzerkonten entfernen')]
class CleanupInactiveUsers extends Command
{
public function handle(): int
{
return self::SUCCESS;
}
}
Cache::touch() eliminiert ein verschwenderisches Muster
Jede Laravel-Produktionsanwendung hat dieses Muster irgendwo:
$data = Cache::get('user_session_123');
Cache::put('user_session_123', $data, now()->addHours(2));
Zwei Operationen. Cache::touch() macht genau das in einer:
$extended = Cache::touch('user_session_123', now()->addHours(2));
Kein Datentransfer. Kein Serialisierungs-Overhead. Ich testete das auf einem Projekt mit 12.000 aktiven Sessions über Redis. Das Ergebnis: ca. 40% weniger Cache-Netzwerkverkehr.
Sofort anwenden für:
- Session Keep-alive — aktive Sessions verlängern ohne Payload zu lesen
- Rate Limiting — Fenster bei Benutzeraktivität erneuern
- Verteilte Locks — Lock-TTLs verlängern ohne Release-and-Reacquire
Reverb ohne Redis — eine echte Infrastrukturvereinfachung
Laravel 13 führt einen Datenbanktreiber für Reverb ein. Deine bestehende MySQL- oder PostgreSQL-Datenbank verwaltet den Kanal- und Verbindungsstatus. Kein Redis erforderlich.
Für eine Anwendung mit 200 gleichzeitigen WebSocket-Verbindungen war der Datenbanktreiber nicht von Redis zu unterscheiden.
Verwende den Datenbanktreiber wenn du:
- Kleine bis mittlere Anwendungen betreibst (unter 500 gleichzeitige Verbindungen)
- Echtzeit-Features ohne Redis willst
- Chat, Live-Benachrichtigungen oder kollaboratives Bearbeiten baust
Behalte Redis wenn du:
- Tausende gleichzeitige WebSocket-Verbindungen verarbeitest
- Redis bereits für Queues und Cache nutzt
Typisierte Eloquent-Properties veränderten meine IDE-Erfahrung
class User extends Model
{
public int $id;
public string $name;
public string $email;
public ?Carbon $email_verified_at;
public bool $is_active;
public float $account_balance;
}
Kombiniere typisierte Properties mit PHP-Attributen und du bekommst vollständig selbst-dokumentierende Modelle. Ich konvertierte zwölf Modelle in einem Wochenendprojekt. PHPStan fand sechs zuvor unsichtbare Bugs.
Die Breaking Changes, die dich treffen werden
Die Boot-Methoden-Einschränkung (die mich erwischt hat)
Verbotenes Muster:
class User extends Model
{
protected static function boot()
{
parent::boot();
$defaultRole = Role::where('name', 'user')->first(); // Verboten
static::creating(function ($user) use ($defaultRole) {
$user->role_id = $defaultRole->id;
});
}
}
Die Lösung — in einen Observer verschieben:
class UserObserver
{
public function creating(User $user): void
{
$user->role_id = Role::where('name', 'user')->first()->id;
}
}
Durchsuche jetzt deine Codebase:
grep -rn "static function boot" app/Models/
grep -rn "static function booted" app/Models/
JobAttempted Event API-Änderung
// Alte API
if ($event->exceptionOccurred) { ... }
// Neue API
if ($event->exception) {
Log::error($event->exception->getMessage());
}
MySQL DELETE mit JOIN
DB::table('orders')
->join('users', 'orders.user_id', '=', 'users.id')
->where('users.is_inactive', true)
->orderBy('orders.created_at')
->limit(1000)
->delete();
Der schrittweise Upgrade-Prozess
Schritt 1: Baseline sichern. Vollständige Testsuite auf Laravel 12 ausführen.
php artisan test --parallel
Schritt 2: Codebase auf Breaking Patterns prüfen.
grep -rn "static function boot" app/Models/
grep -rn "exceptionOccurred" app/
grep -rn "morphToMany\|morphedByMany" app/Models/
Schritt 3: PHP 8.3+ verifizieren.
Schritt 4: Dependencies aktualisieren.
composer update laravel/framework --with-all-dependencies
Schritt 5: Tests erneut ausführen. Schritt 6: Statische Analyse durchführen.
./vendor/bin/phpstan analyse
Die Adoptionszeitlinie, die wirklich funktioniert
Woche 1: Upgraden, Breaking Changes beheben, deployen.
Wochen 2-3: Jedes Cache::get() → Cache::put() TTL-Verlängerungsmuster durch Cache::touch() ersetzen.
Monat 2: Modelle auf PHP-Attribute umstellen, eines pro PR.
Monat 3: Typisierte Properties zu den fünf bis zehn wichtigsten Modellen hinzufügen.
Laufend: Alle neuen Modelle, Jobs und Commands verwenden Attribute und typisierte Properties.
Was das für Laravel's Richtung bedeutet
Laravel 13 ist das erste Release, das native PHP-Features ernsthaft annimmt. Attribute sind nicht nur eine nette Option — sie sind das Signal des Teams, dass native PHP-Features der bevorzugte Weg sind.
| Detail | Wert |
|---|---|
| Release | Q1 2026 |
| PHP erforderlich | 8.3 Minimum |
| Bug Fixes bis | Q3 2027 |
| Sicherheits-Patches | Bis Q1 2028 |
| Breaking Changes | Model-Boot-Einschränkungen, Morph-Pivot-Benennung, JobAttempted Event API |
Was ich am Montagmorgen tun würde
Terminal öffnen, php -v ausführen, und wenn du 8.3 oder höher siehst: Branch erstellen und composer require laravel/framework:^13.0 --with-all-dependencies ausführen. Schau, was kaputt geht. Beheb es. Führe deine Tests aus.
Nicht deployen. Modelle nicht konvertieren. Einfach das Upgrade auf einem Branch zum Laufen bringen, damit du weißt, was zwischen dir und Laravel 13 steht.
Die besten Upgrades sind langweilig. Laravel 13 ist ein langweiliges Upgrade im bestmöglichen Sinne.