Was sich in PHP 8.4 geändert hat und warum es wichtig ist
PHP 8.4 ist nicht nur ein weiteres Minor-Release mit ein paar syntaktischen Zuckerergänzungen. Drei fundamentale Änderungen verändern den Ansatz beim Schreiben von Code: Property Hooks, Asymmetric Visibility und ausgereifte Fiber-Unterstützung. Dazu ein Dutzend kleinere Verbesserungen, die einzeln betrachtet trivial erscheinen, aber zusammen ganze Kategorien von Boilerplate eliminieren.
Ich arbeite mit PHP seit meinen ersten kommerziellen Projekten — Laravel, reines PHP, etwas Symfony. In den letzten zwei Jahren hat sich mein Hauptstack in Richtung Rails verschoben, aber PHP-Projekte sind geblieben: zwei große Laravel-Monolithen in der Produktion, plus Microservices. Beide wurden auf 8.4 aktualisiert — und hier ist, was sich tatsächlich als nützlich erwiesen hat.
Property Hooks — Auf Wiedersehen, magische Getter
Das Problem, das sie lösen
Vor 8.4 hatte PHP zwei Möglichkeiten, den Zugriff auf Eigenschaften zu steuern: öffentliche Felder (keine Kontrolle) und magische __get/__set (Kontrolle, aber ohne Autovervollständigung, Typisierung und brauchbare Stack-Traces).
// Alter Ansatz: __get/__set
class User
{
private string $email;
public function __get(string $name): mixed
{
return match ($name) {
'email' => $this->email,
default => throw new \InvalidArgumentException("Unbekannte Eigenschaft: {$name}"),
};
}
public function __set(string $name, mixed $value): void
{
match ($name) {
'email' => $this->email = strtolower(trim($value)),
default => throw new \InvalidArgumentException("Unbekannte Eigenschaft: {$name}"),
};
}
}
Funktioniert es? Ja. Aber die IDE kennt die Eigenschaft email nicht, PHPStan beschwert sich, und der Stack-Trace bei einem Fehler zeigt auf __get, nicht auf die spezifische Eigenschaft. In einem Projekt mit 200+ Models wird das zum Albtraum.
Neue Syntax
class User
{
public string $email {
get => $this->email;
set(string $value) => strtolower(trim($value));
}
public string $displayName {
get => "{$this->firstName} {$this->lastName}";
}
public function __construct(
public string $firstName,
public string $lastName,
string $email,
) {
$this->email = $email;
}
}
$user = new User('Vadim', 'Bobkov', ' Vadim@Example.COM ');
echo $user->email; // vadim@example.com
echo $user->displayName; // Vadim Bobkov
Property Hooks sind native Getter und Setter, die direkt in die Eigenschaftsdeklaration eingebaut sind. Ohne Magie, mit vollständiger Typisierung, Autovervollständigung und normalen Stack-Traces.
Reale Production-Anwendungsfälle
1. Datennormalisierung beim Schreiben
class Product
{
public string $sku {
set(string $value) => strtoupper(trim($value));
}
public int $priceInCents {
set(int $value) {
if ($value < 0) {
throw new \DomainException('Preis kann nicht negativ sein');
}
$this->priceInCents = $value;
}
}
// Berechnete Eigenschaft — kein Backing Store
public float $priceFormatted {
get => $this->priceInCents / 100;
}
}
Beachten Sie: $priceFormatted speichert keinen Wert. Es ist eine reine berechnete Eigenschaft. Im Gegensatz zu __get kennt die IDE den Typ, PHPStan validiert ihn, und der Zugriff in Templates {{ $product->priceFormatted }} funktioniert ohne zusätzliche Accessoren.
2. Lazy Loading mit Caching
class OrderReport
{
private ?array $cachedMetrics = null;
public array $metrics {
get {
if ($this->cachedMetrics === null) {
$this->cachedMetrics = $this->calculateMetrics();
}
return $this->cachedMetrics;
}
}
private function calculateMetrics(): array
{
// Aufwändige Abfrage an ClickHouse
return DB::connection('clickhouse')
->select('SELECT sum(amount), count(*) FROM orders WHERE report_id = ?', [$this->id]);
}
}
3. Validierung mit Domänenregeln
class Employee
{
public string $taxId {
set(string $value) {
// Steuer-ID: Validierungsformat
if (!preg_match('/^\d{10,12}$/', $value)) {
throw new \DomainException('Ungültiges Steuer-ID-Format');
}
$this->taxId = $value;
}
}
public \DateTimeImmutable $hireDate {
set(\DateTimeImmutable $value) {
if ($value > new \DateTimeImmutable()) {
throw new \DomainException('Einstellungsdatum kann nicht in der Zukunft liegen');
}
$this->hireDate = $value;
}
}
}
Property Hooks vs get/set — Vergleich
| Kriterium | Property Hooks | get/set |
|---|---|---|
| IDE-Autovervollständigung | Vollständig | Erfordert @property PHPDoc |
| Statische Analyse | PHPStan Level 9 | Teilweise, über Plugins |
| Stack-Trace | Zeigt auf Eigenschaft | Zeigt auf __get |
| Performance | Nativer Opcode | Fallback über Magic |
| Vererbung | Einzelne Hooks überschreibbar | Gesamtes __get überschreiben |
| Interfaces | Eigenschaften in Interfaces | Nur über @property |
Eigenschaften in Interfaces
Dies ist ein separates großes Thema. PHP 8.4 erlaubt die Deklaration von Eigenschaften in Interfaces:
interface HasFullName
{
public string $fullName { get; }
}
class User implements HasFullName
{
public string $fullName {
get => "{$this->firstName} {$this->lastName}";
}
public function __construct(
public readonly string $firstName,
public readonly string $lastName,
) {}
}
class Company implements HasFullName
{
public string $fullName {
get => $this->legalName;
}
public function __construct(
public readonly string $legalName,
) {}
}
// Jetzt kann man ordentlich Type-Hinten
function greet(HasFullName $entity): string
{
return "Hallo, {$entity->fullName}!";
}
Früher brauchte man eine getFullName()-Methode im Interface. Eigenschaften in Interfaces sind kein Syntaxzucker — sie sind eine neue Ebene von Verträgen.
Asymmetric Visibility — Schreibzugriff kontrollieren, ohne Lesezugriff einzuschränken
Das Problem
Klassische Situation: Eine Eigenschaft muss von außen lesbar, aber nur innerhalb der Klasse beschreibbar sein. Vor 8.4 — zwei Optionen:
// Option 1: readonly (aber nach dem Konstruktor nicht mehr änderbar)
class User
{
public function __construct(
public readonly string $name,
) {}
}
// Option 2: private + Getter
class User
{
private string $name;
public function getName(): string
{
return $this->name;
}
public function changeName(string $name): void
{
$this->name = $name;
}
}
readonly ist zu strikt — der Wert kann auch innerhalb der Klasse nach der Konstruktion nicht geändert werden. Private + Getter funktioniert, aber es ist Boilerplate für jede Eigenschaft.
Neue Syntax
class User
{
public private(set) string $name;
public protected(set) string $email;
public private(set) \DateTimeImmutable $createdAt;
public function __construct(string $name, string $email)
{
$this->name = $name;
$this->email = $email;
$this->createdAt = new \DateTimeImmutable();
}
public function changeName(string $newName): void
{
// Funktioniert — wir sind innerhalb der Klasse
$this->name = $newName;
}
}
$user = new User('Vadim', 'vadim@example.com');
echo $user->name; // Vadim — Lesen funktioniert
$user->name = 'Ivan'; // Error: Cannot modify private(set) property
$user->changeName('Ivan'); // OK — über Methode
Wo das wirklich gebraucht wird
1. DTOs und Value Objects
class MoneyTransfer
{
public private(set) float $amount;
public private(set) string $currency;
public private(set) string $status = 'pending';
public function __construct(float $amount, string $currency)
{
if ($amount <= 0) {
throw new \DomainException('Betrag muss positiv sein');
}
$this->amount = $amount;
$this->currency = strtoupper($currency);
}
public function markCompleted(): void
{
$this->status = 'completed';
}
public function markFailed(string $reason): void
{
$this->status = 'failed';
}
}
// Von außen — nur Lesen
$transfer = new MoneyTransfer(100.50, 'eur');
echo $transfer->amount; // 100.5
echo $transfer->currency; // EUR
echo $transfer->status; // pending
// IDE und Templates sehen alle Eigenschaften
// aber Änderungen nur über Geschäftsmethoden
2. Eloquent-Models (Laravel)
class Order extends Model
{
// Öffentliches Lesen, Schreiben nur über Geschäftslogik
public private(set) string $status = 'draft';
public function submit(): void
{
if ($this->items()->count() === 0) {
throw new \DomainException('Leere Bestellung kann nicht eingereicht werden');
}
$this->status = 'submitted';
$this->save();
event(new OrderSubmitted($this));
}
public function cancel(): void
{
if (!in_array($this->status, ['draft', 'submitted'])) {
throw new \DomainException("Bestellung im Status {$this->status} kann nicht storniert werden");
}
$this->status = 'cancelled';
$this->save();
}
}
3. Kombination mit Property Hooks
class Temperature
{
public private(set) float $celsius {
set(float $value) {
if ($value < -273.15) {
throw new \DomainException('Temperatur unter dem absoluten Nullpunkt');
}
$this->celsius = $value;
}
}
public float $fahrenheit {
get => $this->celsius * 9/5 + 32;
}
public float $kelvin {
get => $this->celsius + 273.15;
}
public function __construct(float $celsius)
{
$this->celsius = $celsius;
}
}
$temp = new Temperature(36.6);
echo $temp->celsius; // 36.6
echo $temp->fahrenheit; // 97.88
echo $temp->kelvin; // 309.75
$temp->celsius = 100; // Error: private(set)
readonly vs private(set) — Wann was verwenden
| Szenario | readonly | private(set) |
|---|---|---|
| Wert ändert sich nie nach Erstellung | Ja | Übertrieben |
| Wert ändert sich über Methoden | Nein — unmöglich | Ja |
| DTO von API/Datenbank | Ja | Ja |
| Eloquent/Doctrine Models | Nein — ORM ändert Felder | Ja |
| Event Sourcing State | Nein | Ja |
| Config / unveränderliche Einstellungen | Ja | Übertrieben |
Einfache Regel: Wenn der Wert einmal gesetzt wird — readonly. Wenn er sich ändern kann, aber nur über Geschäftslogik — private(set).
Fibers — Asynchronität ohne Callback-Hölle
Was sind Fibers
Fibers erschienen in PHP 8.1, aber in 8.4 sind sie wirklich ausgereift. Ein Fiber ist eine kooperative Koroutine: eine Funktion, die ihre Ausführung pausieren und die Kontrolle an den Aufrufer zurückgeben kann.
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('erste Pause');
echo "Empfangen: {$value}\n";
Fiber::suspend('zweite Pause');
echo "Fertig\n";
});
$result1 = $fiber->start(); // 'erste Pause'
$result2 = $fiber->resume('hallo'); // gibt "Empfangen: hallo" aus, liefert 'zweite Pause'
$fiber->resume(); // gibt "Fertig" aus
Ein Fiber pausiert über Fiber::suspend() und gibt dabei einen Wert nach außen zurück. Der aufrufende Code setzt über resume() fort und übergibt Daten zurück in den Fiber.
Fibers vs Generatoren
Generatoren (yield) sind ähnlich, aber eingeschränkt:
// Generator — gibt nur Werte nach außen
function readLines(string $file): Generator
{
$handle = fopen($file, 'r');
while ($line = fgets($handle)) {
yield trim($line);
}
fclose($handle);
}
// Fiber — vollständige bidirektionale Kommunikation
$fiber = new Fiber(function (): void {
$url = Fiber::suspend(); // URL von außen empfangen
$response = file_get_contents($url);
$parsed = json_decode($response, true);
Fiber::suspend($parsed); // Ergebnis nach außen senden
// mit neuen Daten fortfahren...
});
| Kriterium | Generator | Fiber |
|---|---|---|
| Bidirektionale Datenübertragung | Eingeschränkt (send) | Vollständig |
| Verschachtelung | yield from | Frei |
| Stackful | Nein | Ja |
| Einsatzbereich | Iteratoren, Lazy Collections | Async I/O, Concurrency |
| Speicher-Overhead | ~400 Bytes | ~8 KB |
Praktisches Beispiel: Parallele HTTP-Anfragen
class AsyncHttpClient
{
private array $fibers = [];
private array $results = [];
public function addRequest(string $name, string $url, array $options = []): void
{
$this->fibers[$name] = new Fiber(function () use ($url, $options): array {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $options['timeout'] ?? 30,
CURLOPT_HTTPHEADER => $options['headers'] ?? [],
]);
if (isset($options['post'])) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($options['post']));
}
// Fiber pausieren — Curl-Handle bereit, Anfrage noch nicht ausgeführt
Fiber::suspend($ch);
// Wenn Fiber fortgesetzt wird, hat curl_multi die Anfrage bereits ausgeführt
$response = curl_multi_getcontent($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'status' => $httpCode,
'body' => $response,
'time' => curl_getinfo($ch, CURLINFO_TOTAL_TIME),
];
});
}
public function execute(): array
{
$multiHandle = curl_multi_init();
$handles = [];
// Alle Fibers starten — jeder erstellt einen Curl-Handle und pausiert
foreach ($this->fibers as $name => $fiber) {
$ch = $fiber->start();
curl_multi_add_handle($multiHandle, $ch);
$handles[$name] = $ch;
}
// Alle Anfragen parallel ausführen
do {
$status = curl_multi_exec($multiHandle, $active);
curl_multi_select($multiHandle);
} while ($active && $status === CURLM_OK);
// Fibers fortsetzen — jeder verarbeitet sein Ergebnis
foreach ($this->fibers as $name => $fiber) {
$this->results[$name] = $fiber->resume();
}
curl_multi_close($multiHandle);
return $this->results;
}
}
// Verwendung
$client = new AsyncHttpClient();
$client->addRequest('users', 'https://api.example.com/users');
$client->addRequest('orders', 'https://api.example.com/orders');
$client->addRequest('products', 'https://api.example.com/products');
$results = $client->execute();
// Alle 3 Anfragen wurden parallel ausgeführt, nicht sequenziell
Fibers in Laravel / Symfony
Laravel: Parallele Datensammlung für das Dashboard
class DashboardController extends Controller
{
public function index(): View
{
$fibers = [
'stats' => new Fiber(fn() => Fiber::suspend(
Cache::remember('dashboard.stats', 60, fn() => [
'users' => User::count(),
'orders' => Order::whereMonth('created_at', now()->month)->count(),
'revenue' => Order::whereMonth('created_at', now()->month)->sum('total'),
])
)),
'chart' => new Fiber(fn() => Fiber::suspend(
Order::selectRaw('DATE(created_at) as date, SUM(total) as total')
->where('created_at', '>=', now()->subDays(30))
->groupByRaw('DATE(created_at)')
->get()
)),
'recent' => new Fiber(fn() => Fiber::suspend(
Order::with('user')->latest()->limit(10)->get()
)),
];
$data = [];
foreach ($fibers as $key => $fiber) {
$data[$key] = $fiber->start();
}
return view('dashboard', $data);
}
}
Wann man Fibers NICHT verwenden sollte
Fibers sind keine Wunderwaffe. Sie sind unnötig, wenn:
- Einzelne HTTP-Anfrage an eine API — ein einfaches
Http::get()ist einfacher - CPU-gebundene Aufgaben — Fibers erstellen keine Threads, sie sind kooperativ. Für CPU braucht man
pcntl_forkoder Queues - Einfache sequenzielle Logik — der Overhead der Fiber-Erstellung (~8KB) ist nicht gerechtfertigt
- Alles bereits in der Queue — wenn die Logik in einem
Jobist, sind Fibers innerhalb des Jobs meist überflüssig
Fibers sind nützlich, wenn mehrere I/O-Operationen parallel ausgeführt werden können: Anfragen an mehrere APIs, parallele Datenbankabfragen, Benachrichtigungsversand.
Neue Array-Funktionen
PHP 8.4 fügt Funktionen hinzu, die seit Jahren gefehlt haben.
arrayfind, arrayfind_key
$users = [
['id' => 1, 'name' => 'Alice', 'role' => 'admin'],
['id' => 2, 'name' => 'Bob', 'role' => 'editor'],
['id' => 3, 'name' => 'Charlie', 'role' => 'admin'],
];
// Vorher: array_filter + reset (oder current)
$admin = current(array_filter($users, fn($u) => $u['role'] === 'admin'));
// PHP 8.4
$admin = array_find($users, fn($u) => $u['role'] === 'admin');
// ['id' => 1, 'name' => 'Alice', 'role' => 'admin']
$adminKey = array_find_key($users, fn($u) => $u['role'] === 'admin');
// 0
arrayany, arrayall
$prices = [100, 250, 50, 800, 120];
// Vorher: count(array_filter(...)) > 0
$hasExpensive = count(array_filter($prices, fn($p) => $p > 500)) > 0;
// PHP 8.4
$hasExpensive = array_any($prices, fn($p) => $p > 500); // true
$allCheap = array_all($prices, fn($p) => $p < 1000); // true
$allExpensive = array_all($prices, fn($p) => $p > 500); // false
Reales Beispiel: Warenkorb-Validierung
class CartValidator
{
public function validate(Cart $cart): ValidationResult
{
$items = $cart->items;
// Gibt es Artikel ohne Preis
$hasMissingPrice = array_any(
$items,
fn(CartItem $item) => $item->price === null
);
// Sind alle Artikel auf Lager
$allInStock = array_all(
$items,
fn(CartItem $item) => $item->stock > 0
);
// Ersten altersbeschränkten Artikel finden
$ageRestricted = array_find(
$items,
fn(CartItem $item) => $item->ageRestriction !== null
);
return new ValidationResult(
valid: !$hasMissingPrice && $allInStock,
ageVerificationRequired: $ageRestricted !== null,
firstRestricted: $ageRestricted,
);
}
}
Neue String-Methoden und Closure-Verbesserungen
mbtrim, mbltrim, mb_rtrim
Endlich nativer Trim für Multibyte-Strings:
$input = "\u{00A0}Hallo, Welt!\u{00A0}"; // Non-Breaking Spaces
echo trim($input); // "\u{00A0}Hallo, Welt!\u{00A0}" — trim sieht NBSP nicht
echo mb_trim($input); // "Hallo, Welt!" — mb_trim schafft es
In meinen Projekten hat dies ein Dutzend benutzerdefinierte Hilfsfunktionen wie mb_trim($str, "\xC2\xA0") eliminiert.
Closure::fromCallable-Verbesserungen
class EventDispatcher
{
private array $listeners = [];
public function on(string $event, callable $listener): void
{
$this->listeners[$event][] = Closure::fromCallable($listener);
}
public function emit(string $event, mixed ...$args): void
{
foreach ($this->listeners[$event] ?? [] as $listener) {
$listener(...$args);
}
}
}
$dispatcher = new EventDispatcher();
// Alle diese Varianten funktionieren einheitlich
$dispatcher->on('user.created', $logger->info(...));
$dispatcher->on('user.created', UserNotification::send(...));
$dispatcher->on('user.created', function (User $user) {
Cache::forget("users.{$user->id}");
});
Deprecations und Breaking Changes
Was beim Upgrade von 8.3 bricht
1. Implizite Nullable-Typisierung entfernt
// PHP 8.3: funktionierte (aber deprecated)
function process(string $name = null): void {}
// PHP 8.4: Fehler. Muss sein:
function process(?string $name = null): void {}
Dies ist die häufigste Breaking Change. In einem großen Laravel-Projekt hatten wir ~150 solche Stellen. Auto-Fix:
# PHP CS Fixer Regel
php-cs-fixer fix --rules=nullable_type_declaration_for_default_null_value src/
2. CURLOPT_BINARYTRANSFER entfernt
// War
curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
// Jetzt — einfach entfernen, diese Option hat seit PHP 5.1.2 nichts bewirkt
3. sessionregister, sessionunregister — vollständig entfernt
Falls irgendwo noch Legacy-Code mit diesen Funktionen existiert — Zeit für die Migration zu $_SESSION[].
Vollständige Migrations-Checkliste von 8.3
# 1. Abhängigkeiten aktualisieren
composer update --dry-run
# Paketkompatibilität mit 8.4 prüfen
# 2. Rector für automatische Migration ausführen
composer require rector/rector --dev
vendor/bin/rector process --set php84
# 3. PHPStan auf höchstem Level
vendor/bin/phpstan analyse -l 9 src/
# 4. Tests
php artisan test --parallel
Performance: Benchmarks
Alle Benchmarks auf derselben Maschine (AMD Ryzen 7, 32GB RAM, Ubuntu 24.04, PHP kompiliert mit -O2).
Property Hooks vs get/set
Benchmark: 1M Property-Lesezugriffe
__get magic: 0.847s
Property hook (get): 0.312s
Direct public: 0.298s
Property Hooks sind ~2.7x schneller als magische Methoden.
Unterschied zum direkten Zugriff — im Rauschbereich (~5%).
arrayfind vs arrayfilter + reset
Benchmark: Ersten Fund in Array mit 10000 Elementen (Ziel bei Index 50)
array_filter + reset: 0.0034s (verarbeitet ALLE Elemente)
array_find: 0.000018s (stoppt beim ersten Treffer)
~190x schneller für Early-Exit-Szenarien.
Fiber-Erstellungs-Overhead
Benchmark: Erstellen + Starten + Fortsetzen von 100K Fibers
Fiber create + start: 0.089s
Generator create: 0.043s
Function call: 0.011s
Overhead pro Fiber — ~2x gegenüber Generator, ~8x gegenüber normalem Aufruf.
Aber: Das ist Erstellungs-Overhead. Für I/O-gebundene Aufgaben (HTTP, DB) irrelevant.
Weitere Änderungen in PHP 8.4
new ohne Klammern in Methodenketten
// PHP 8.3: Klammern erforderlich
$name = (new ReflectionClass($obj))->getName();
$result = (new Collection([1, 2, 3]))->map(fn($x) => $x * 2)->toArray();
// PHP 8.4: ohne Klammern
$name = new ReflectionClass($obj)->getName();
$result = new Collection([1, 2, 3])->map(fn($x) => $x * 2)->toArray();
Kleinigkeit, aber in Fluent-API-Ketten entfernt es visuelles Rauschen.
#[\Deprecated] Attribut
class PaymentGateway
{
#[\Deprecated("Verwende processPayment() stattdessen", since: "2.0")]
public function pay(float $amount): bool
{
return $this->processPayment($amount);
}
public function processPayment(float $amount): bool
{
// neue Implementierung
}
}
// Beim Aufruf von $gateway->pay(100) — E_USER_DEPRECATED mit der Nachricht
Neuer HTML5-Parser: \Dom\HTMLDocument
// PHP 8.3: DOMDocument mit XML-Parser (bricht HTML5)
$doc = new DOMDocument();
@$doc->loadHTML($html); // @ zur Unterdrückung von Warnings
// PHP 8.4: nativer HTML5-Parser
$doc = \Dom\HTMLDocument::createFromString($html);
$doc = \Dom\HTMLDocument::createFromFile('page.html');
// Volle HTML5-Unterstützung: <template>, <slot>, <details>, Inline-SVG
// Keine Warnings, keine @-Hacks
BCMath: Objekt-API
// PHP 8.3
$result = bcadd('1.5', '2.3', 2); // '3.80'
$total = bcmul(bcadd('100', '50'), '1.2', 2); // '180.00'
// PHP 8.4: Objekt-API
use BCMath\Number;
$a = new Number('1.5');
$b = new Number('2.3');
$result = $a + $b; // Number('3.8')
$price = new Number('100');
$tax = new Number('1.2');
$total = ($price + new Number('50')) * $tax; // Number('180.0')
Für Finanzberechnungen, wo Float-Arithmetik inakzeptabel ist — endlich eine ordentliche API.
php.ini-Konfiguration: Was zu prüfen ist
; Neue Direktiven in 8.4
; Property Hooks verwenden etwas mehr Speicher pro Klasse (vernachlässigbar)
; Bei striktem memory_limit — nach Migration prüfen
memory_limit = 256M
; Fiber Stack-Größe (Standard 8KB, möglicherweise erhöhen)
; Relevant bei tiefer Rekursion in Fibers
; fiber.stack_size = 8192
; opcache — Property Hooks erzeugen neue Opcodes
; Empfohlen den Puffer zu erhöhen bei vielen Klassen mit Hooks
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 20000
Abschließende Migrations-Checkliste für PHP 8.4
- [ ]
rector process --set php84ausführen — automatische Syntax-Migration - [ ] Alle
function foo(Type $param = null)→function foo(?Type $param = null)korrigieren - [ ]
composer updateprüfen — alle Abhängigkeiten kompatibel mit 8.4 - [ ] PHPStan Level 8+ ausführen — veraltete Konstrukte finden
- [ ] Tests ausführen — besonders wo
__get/__setverwendet werden - [ ]
DOMDocument::loadHTML()durch\Dom\HTMLDocumentfür HTML5-Parsing ersetzen - [ ] Property Hooks für Value Objects und DTOs in Betracht ziehen — statt Boilerplate-Getter
- [ ]
private(set)für Eloquent/Doctrine Models in Betracht ziehen — stattreadonly - [ ]
array_filter + resetdurcharray_findersetzen wo Early Exit zutrifft - [ ]
php.iniprüfen — opcache-Puffer, memory_limit - [ ] CI aktualisieren — PHP 8.4 Image in GitHub Actions / GitLab CI
- [ ] Docker-Image aktualisieren —
php:8.4-fpmoderphp:8.4-cli - [ ] Monitoring nach dem Deploy — Fehlerrate, Speicherverbrauch, Antwortzeit
Kommentare (0)