> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Upgrade von Laravel 8 auf 9

> Erläuterung der Upgrade-Schritte und wichtigsten Änderungen beim Wechsel von Laravel 8.x auf 9.x

## Einleitung

Laravel 9 wurde am 8. Februar 2022 veröffentlicht. Dieser Leitfaden fasst die Upgrade-Schritte von Laravel 8.x auf 9.x sowie die einflussreichsten Änderungen zusammen.

<Info>
  Der geschätzte Zeitaufwand für das Upgrade beträgt **etwa 30 Minuten**. Je nach Nutzung von Mailversand, Dateispeicher, benutzerdefinierten Casts und Overrides von Core-Klassen kann der Aufwand jedoch steigen.
</Info>

### Automatisches Upgrade mit Laravel Shift

Sie können das Upgrade auch mit [Laravel Shift](https://laravelshift.com/) automatisieren. Shift unterstützt Sie beim Aktualisieren von `composer.json` und Konfigurationsdateien und ist ein guter Ausgangspunkt für den Diff-Vergleich.

***

## Änderungen nach Auswirkung

### Auswirkung: Hoch

* Aktualisierung der Abhängigkeiten
* Umstieg auf Flysystem 3.x
* Umstieg auf Symfony Mailer

### Auswirkung: Mittel

* Methoden `firstOrNew` / `firstOrCreate` / `updateOrCreate` bei `BelongsToMany`
* Verhalten von Custom Casts bei `null`
* Standard-Timeout des HTTP-Clients
* Ergänzung von PHP-Rückgabetypen
* Umbenennung des Postgres-`schema`-Settings
* Entfall der `assertDeleted`-Methode
* Verlagerung des `lang`-Verzeichnisses
* Änderung der Password-Rule
* Änderung der Methoden `when` / `unless`
* Umgang mit nicht validierten Array-Keys in der Validierung

***

## Upgrade-Schritte

### Abhängigkeiten aktualisieren

**Auswirkung: Hoch**

Laravel 9 erfordert **PHP 8.0.2 oder höher**. Prüfen Sie zuerst die Abhängigkeiten in `composer.json`.

```json theme={null}
{
  "require": {
    "php": "^8.0.2",
    "laravel/framework": "^9.0",
    "spatie/laravel-ignition": "^1.0"
  },
  "require-dev": {
    "nunomaduro/collision": "^6.1"
  }
}
```

Für einige Anwendungen sind zusätzlich diese Updates nötig:

* `facade/ignition` entfernen und durch `spatie/laravel-ignition:^1.0` ersetzen
* Bei Nutzung von `pusher/pusher-php-server` auf `^5.0` aktualisieren
* Prüfen, ob die verwendeten Dritt-Pakete eine Laravel-9-kompatible Version haben
* Bei Nutzung des Vonage-Notification-Channels dessen separaten Upgrade-Guide beachten

Installieren Sie anschließend die Abhängigkeiten:

```shell theme={null}
composer update
```

***

## PHP-Version

**Auswirkung: Hoch**

Laravel 9 benötigt PHP 8.0.2 oder höher. Vereinheitlichen Sie die PHP-Version in CI, lokaler und produktiver Umgebung, bevor Sie das Upgrade fortsetzen.

***

### Umstieg auf Symfony Mailer

**Auswirkung: Hoch**

Eine der zentralen Änderungen in Laravel 9 ist der Wechsel von SwiftMailer (Maintenance-Ende Dezember 2021) zu Symfony Mailer. Für Anwendungen, die nur `Mail::to()->send()` verwenden, sind die Auswirkungen gering. Wer die Low-Level-API von SwiftMailer direkt nutzt, muss prüfen.

#### Treiber-Abhängigkeiten

```shell theme={null}
# Nur bei Nutzung von Mailgun ergänzen
composer require symfony/mailgun-mailer symfony/http-client

# Bei Nutzung von Postmark das SwiftMailer-Paket entfernen und ersetzen
composer remove wildbit/swiftmailer-postmark
composer require symfony/postmark-mailer symfony/http-client
```

#### Von `withSwiftMessage` zu `withSymfonyMessage`

```php theme={null}
// Laravel 8.x: SwiftMailer-basiert
$this->withSwiftMessage(function ($message) {
    $message->getHeaders()->addTextHeader('Custom-Header', 'Header Value');
});

// Laravel 9.x: Symfony-Mailer-basiert
use Symfony\Component\Mime\Email;

$this->withSymfonyMessage(function (Email $message) {
    $message->getHeaders()->addTextHeader('Custom-Header', 'Header Value');
});
```

`send`, `html`, `raw` und `plain` von `Illuminate\Mail\Mailer` liefern nun `Illuminate\Mail\SentMessage` statt `void`. Auch die Eigenschaft `message` im Event `MessageSent` enthält jetzt `Symfony\Component\Mime\Email` statt `Swift_Message`.

#### SMTP-Konfiguration überarbeiten

Bei Symfony Mailer entfällt die `stream`-Option in der SMTP-Konfiguration; die unterstützten Einstellungen wandern auf die oberste Ebene.

```php theme={null}
return [
    'mailers' => [
        'smtp' => [
            // Konfiguration in Laravel 8.x
            'stream' => [
                'ssl' => [
                    'verify_peer' => false,
                ],
            ],

            // Konfiguration in Laravel 9.x
            'verify_peer' => false,
        ],
    ],
];
```

Die explizite `auth_mode`-Konfiguration ist ebenfalls nicht mehr nötig. Statt ungültige Mails im Nachhinein zurückzuholen, ist es sicherer, sie bereits vor dem Versand zu validieren.

***

### Umstieg auf Flysystem 3.x

**Auswirkung: Hoch**

Laravel 9 hat die interne Implementierung der `Storage`-Facade von Flysystem 1.x auf 3.x aktualisiert. Die Dateimethoden bleiben so kompatibel wie möglich; es gibt jedoch Unterschiede bei Exceptions, Rückgabewerten und Adapter-Registrierung.

#### Zusätzliche Treiber installieren

```shell theme={null}
# Amazon-S3-Treiber
composer require -W league/flysystem-aws-s3-v3 "^3.0"

# FTP-Treiber
composer require league/flysystem-ftp "^3.0"

# SFTP-Treiber
composer require league/flysystem-sftp-v3 "^3.0"
```

#### Wichtige Verhaltensänderungen von `Storage`

* `put` / `write` / `writeStream` überschreiben bestehende Dateien standardmäßig
* Bei Schreibfehlern wird `false` statt einer Exception zurückgegeben
* Beim Lesen nicht vorhandener Dateien wird `null` statt einer Exception zurückgegeben
* `delete` auf einer nicht existierenden Datei gibt `true` zurück
* Der Cached Adapter wurde entfernt; der Key `cache` in `disk`-Konfigurationen kann gelöscht werden

Wenn Sie wie bisher bei Schreibfehlern Exceptions möchten, setzen Sie die Option `throw`.

```php theme={null}
return [
    'disks' => [
        'public' => [
            'driver' => 'local',
            // Nur aktivieren, wenn Schreibfehler eine Exception auslösen sollen
            'throw' => true,
        ],
    ],
];
```

Wenn Sie eigene Filesystem-Treiber registrieren, passen Sie den Callback von `Storage::extend()` so an, dass er direkt einen `Illuminate\Filesystem\FilesystemAdapter` zurückgibt.

***

### `firstOrNew` / `firstOrCreate` / `updateOrCreate` bei `BelongsToMany`

**Auswirkung: Mittel**

In Laravel 8 wurden die im ersten Argument übergebenen Attribute mit der Pivot-Tabelle verglichen. In Laravel 9 werden sie mit der Tabelle des verwandten Modells verglichen.

```php theme={null}
// Suche und Aktualisierung von name in der Tabelle des verwandten Modells
$user->roles()->updateOrCreate([
    'name' => 'Administrator',
]);
```

Außerdem akzeptiert `firstOrCreate` nun als zweites Argument `$values` und richtet sich damit nach dem Verhalten der anderen Relationen.

```php theme={null}
// Beim Anlegen zusätzlich created_by mergen
$user->roles()->firstOrCreate([
    'name' => 'Administrator',
], [
    'created_by' => $user->id,
]);
```

***

### Custom Casts und `null`

**Auswirkung: Mittel**

In Laravel 9 wird die `set`-Methode eines Custom-Casts auch dann aufgerufen, wenn `null` zugewiesen wird. Casts, die `null` nicht vorsehen, können nach dem Upgrade Exceptions werfen.

```php theme={null}
public function set($model, $key, $value, $attributes)
{
    // In Laravel 9 kann null übergeben werden
    if ($value === null) {
        return [
            'address_line_one' => null,
            'address_line_two' => null,
        ];
    }

    if (! $value instanceof AddressModel) {
        throw new InvalidArgumentException('Bitte ein AddressModel übergeben.');
    }

    return [
        'address_line_one' => $value->lineOne,
        'address_line_two' => $value->lineTwo,
    ];
}
```

***

### Standard-Timeout des HTTP-Clients

**Auswirkung: Mittel**

Der Default-Timeout des HTTP-Clients liegt nun bei 30 Sekunden. Zuvor wartete er im Zweifel unbegrenzt.

```php theme={null}
use Illuminate\Support\Facades\Http;

// Nur für langlaufende API-Aufrufe den Timeout verlängern
$response = Http::timeout(120)->get('https://example.com/api/status');
```

***

### Ergänzung von PHP-Rückgabetypen

**Auswirkung: Mittel**

In Laravel 9 wurden entsprechend den PHP- und Symfony-Anforderungen Rückgabetypen in zahlreichen Core-Klassen ergänzt. Wenn Sie Core-Klassen erweitern und dabei `offsetGet`, `offsetSet`, `jsonSerialize`, `open`, `read` u. Ä. überschreiben, ergänzen Sie in Ihrer Implementierung dieselben Rückgabetypen.

```php theme={null}
// Bei Override einer Core-Klasse den Rückgabetyp angleichen
public function offsetGet($key): mixed
{
    return parent::offsetGet($key);
}
```

***

### Umbenennung des Postgres-`schema`-Settings

**Auswirkung: Mittel**

Wenn Sie den Search-Path in Postgres-Verbindungen konfigurieren, benennen Sie in `config/database.php` den Key von `schema` in `search_path` um.

```php theme={null}
return [
    'pgsql' => [
        // Laravel 8.x
        'schema' => 'public',

        // Laravel 9.x
        'search_path' => 'public',
    ],
];
```

***

### `assertDeleted` → `assertModelMissing`

**Auswirkung: Mittel**

Ersetzen Sie `assertDeleted` zur Prüfung eines gelöschten Modells durch `assertModelMissing`.

```php theme={null}
// Vorher
$this->assertDeleted($user);

// Nachher
$this->assertModelMissing($user);
```

***

### Verlagerung des `lang`-Verzeichnisses

**Auswirkung: Mittel**

In neuen Laravel-9-Anwendungen liegen die Sprachdateien nun im Projekt-Root unter `lang` und nicht mehr unter `resources/lang`. Bestehende Anwendungen laufen weiterhin ohne größere Auswirkungen, doch bei Angleichung an das neue Skelett oder wenn ein Paket Übersetzungen veröffentlicht, sollten Sie das prüfen.

```php theme={null}
// Ziel-Pfad statt fest codiert über langPath() auflösen
$this->publishes([
    __DIR__.'/../lang' => app()->langPath('vendor/package-name'),
]);
```

***

### Änderung der Password-Rule

**Auswirkung: Mittel**

Die Rule `password`, die den Abgleich mit dem Passwort des angemeldeten Nutzers durchführt, wurde in `current_password` umbenannt.

```php theme={null}
// Vorher
'password' => ['required', 'password'],

// Nachher
'password' => ['required', 'current_password'],
```

***

### Änderung der Methoden `when` / `unless`

**Auswirkung: Mittel**

In Laravel 8 galten Closures, die an `when` oder `unless` übergeben wurden, als truthy – der Zweig wurde unbeabsichtigt ausgeführt. In Laravel 9 wird die Closure ausgeführt und ihr Rückgabewert als Bedingung verwendet.

```php theme={null}
$collection->when(function ($collection) {
    // Der hier zurückgegebene Wert wird als Bedingung ausgewertet
    return false;
}, function ($collection) {
    // Da false zurückkam, wird dies nicht ausgeführt
    $collection->merge([1, 2, 3]);
});
```

***

### Umgang mit nicht validierten Array-Keys

**Auswirkung: Mittel**

In Laravel 9 werden nicht validierte Array-Keys in der Rückgabe von `validated()` stets ausgeschlossen. Wenn Sie das alte Verhalten aus Laravel 8 wünschen, rufen Sie explizit `includeUnvalidatedArrayKeys()` auf.

```php theme={null}
use Illuminate\Support\Facades\Validator;

public function boot()
{
    // Nur aktivieren, wenn das alte Laravel-8-Verhalten benötigt wird
    Validator::includeUnvalidatedArrayKeys();
}
```

***

## Fazit

Beim Upgrade von Laravel 8 auf 9 stehen die Aktualisierung auf PHP 8.0.2, der Wechsel zu Symfony Mailer und die Umstellung auf Flysystem 3.x im Zentrum. Wenn Sie zuerst Mailversand, Storage, eigene Casts und Test-Helper prüfen, verringern sich die Nachwirkungen nach dem Upgrade.

| Änderung                                | Auswirkung | Maßnahme                                                                  |
| --------------------------------------- | ---------- | ------------------------------------------------------------------------- |
| PHP 8.0.2 / `laravel/framework:^9.0`    | Hoch       | `composer.json` und Laufzeitumgebung aktualisieren                        |
| Umstieg auf Symfony Mailer              | Hoch       | SwiftMailer-APIs und Mail-Konfiguration prüfen                            |
| Flysystem 3.x                           | Hoch       | Rückgabewerte, Exceptions und Treiber-Abhängigkeiten von `Storage` prüfen |
| Upsert-Methoden bei `BelongsToMany`     | Mittel     | Vergleichsziel liegt nun bei der Tabelle des verwandten Modells           |
| Custom Casts und `null`                 | Mittel     | `set()` gegen `null`-Werte robust machen                                  |
| 30-s-Timeout beim HTTP-Client           | Mittel     | Nur bei Bedarf `timeout()` explizit setzen                                |
| Entfall von `assertDeleted`             | Mittel     | Durch `assertModelMissing()` ersetzen                                     |
| Verlagerung des `lang`-Verzeichnisses   | Mittel     | Feste Pfade durch `app()->langPath()` ersetzen                            |
| Änderung der `password`-Rule            | Mittel     | Zu `current_password` migrieren                                           |
| Ausschluss nicht validierter Array-Keys | Mittel     | Bei Bedarf `includeUnvalidatedArrayKeys()` nutzen                         |

***

## Referenzen

* [Offizieller Upgrade-Guide (Englisch)](https://laravel.com/docs/9.x/upgrade)
* [laravel/docs 9.x `upgrade.md`](https://github.com/laravel/docs/blob/9.x/upgrade.md)
* [Diff im Repository laravel/laravel (8.x → 9.x)](https://github.com/laravel/laravel/compare/8.x...9.x)
* [Laravel Shift](https://laravelshift.com) – Community-Dienst zur Upgrade-Automatisierung
* [Symfony-Mailer-Dokumentation](https://symfony.com/doc/6.0/mailer.html)
* [Flysystem-3-Dokumentation](https://flysystem.thephpleague.com/v3/docs/)


## Related topics

- [Upgrade von Laravel 9 auf 10](/de/blog/upgrade-9-to-10.md)
- [Upgrade von Laravel 10 auf 11](/de/blog/upgrade-10-to-11.md)
- [Upgrade-Anleitung von Laravel 11 auf 12](/de/blog/upgrade-11-to-12.md)
- [Upgrade-Anleitung von Laravel 12 auf 13](/de/blog/upgrade-12-to-13.md)
