PHP 8 - Nowoczesne funkcje i najlepsze praktyki dla programistów
PHP Nowoczesne funkcje
backendPHP 8 - Nowoczesne funkcje i najlepsze praktyki
PHP przeszedł w ostatnich latach ogromną transformację. Wersje 8.0, 8.1 i 8.2 wprowadziły zestaw funkcji, które stawiają ten język na równi z najnowocześniejszymi rozwiązaniami backendowymi. Jeśli nadal piszesz kod w stylu PHP 5 lub 7, ten artykuł pokaże Ci, dlaczego warto zaktualizować swoje podejście i jak wykorzystać pełen potencjał nowoczesnego PHP.
Named arguments - koniec z zagadkowymi parametrami#
Jedną z najbardziej oczekiwanych nowości PHP 8.0 były nazwane argumenty. Pozwalają one na przekazywanie wartości do funkcji z jawnym wskazaniem nazwy parametru, co znacząco poprawia czytelność kodu.
Problem w starym podejściu#
// PHP 7 - co oznaczają te wartości?
array_slice($array, 0, 3, true);
setcookie('session', $value, 0, '/', '', true, true);
Bez zaglądania do dokumentacji trudno powiedzieć, co oznacza każdy z tych argumentów.
Rozwiązanie z named arguments#
// PHP 8 - wszystko jest jasne
array_slice(array: $array, offset: 0, length: 3, preserve_keys: true);
setcookie(
name: 'session',
value: $value,
path: '/',
secure: true,
httponly: true,
);
Named arguments doskonale sprawdzają się w połączeniu z wartościami domyślnymi - możesz pominąć parametry, które nie wymagają zmiany:
function createUser(
string $name,
string $email,
string $role = 'user',
bool $active = true,
?string $avatar = null,
string $locale = 'pl',
) {
// ...
}
// Ustawiamy tylko to, co potrzebne
$user = createUser(
name: 'Jan Kowalski',
email: 'jan@example.com',
locale: 'en',
);
Union types i intersection types - precyzyjne typowanie#
PHP 8.0 wprowadził union types, a PHP 8.1 rozszerzył system typów o intersection types. Razem tworzą potężny mechanizm wyrażania oczekiwań wobec danych.
Union types (PHP 8.0)#
Union type pozwala na określenie, że parametr lub wartość zwracana może być jednym z kilku typów:
function processInput(string|int|float $value): string|false
{
if (is_numeric($value)) {
return number_format((float) $value, 2, ',', ' ');
}
return false;
}
// Typ nullable jako skrót
function findUser(int $id): User|null
{
// Równoważne z ?User
return User::find($id);
}
Intersection types (PHP 8.1)#
Intersection types wymagają, aby wartość implementowała jednocześnie wiele interfejsów:
interface Loggable
{
public function toLogEntry(): string;
}
interface Serializable
{
public function serialize(): string;
}
// Parametr musi implementować OBA interfejsy
function processEntity(Loggable&Serializable $entity): void
{
logger()->info($entity->toLogEntry());
cache()->put('entity', $entity->serialize());
}
Disjunctive Normal Form types (PHP 8.2)#
PHP 8.2 pozwala łączyć union i intersection types w jednym wyrażeniu:
function handleInput((Stringable&Countable)|string $input): string
{
if (is_string($input)) {
return $input;
}
return (string) $input;
}
Match expression - elegancka alternatywa dla switch#
Wyrażenie match zastępuje konstrukcję switch bardziej zwięzłą i bezpieczniejszą składnią. W przeciwieństwie do switch, match używa ścisłego porównania (===), nie wymaga break i zwraca wartość.
// Stare podejście z switch
switch ($statusCode) {
case 200:
case 201:
$message = 'Sukces';
break;
case 404:
$message = 'Nie znaleziono';
break;
case 500:
$message = 'Błąd serwera';
break;
default:
$message = 'Nieznany status';
}
// Nowoczesne podejście z match
$message = match($statusCode) {
200, 201 => 'Sukces',
404 => 'Nie znaleziono',
500 => 'Błąd serwera',
default => 'Nieznany status',
};
match sprawdza się szczególnie dobrze z enumami i w bardziej złożonych scenariuszach:
$result = match(true) {
$age < 13 => 'dziecko',
$age < 18 => 'nastolatek',
$age < 65 => 'dorosły',
default => 'senior',
};
Enums - natywne typy wyliczeniowe (PHP 8.1)#
PHP 8.1 w końcu wprowadził natywne enumy, eliminując potrzebę stosowania klas ze stałymi lub bibliotek zewnętrznych.
Podstawowe enumy (pure enums)#
enum Status
{
case Draft;
case Published;
case Archived;
}
function updateArticle(int $id, Status $status): void
{
// Type-safe - nie da się przekazać nieprawidłowej wartości
}
updateArticle(1, Status::Published);
Backed enums - enumy z wartościami#
enum Color: string
{
case Red = '#FF0000';
case Green = '#00FF00';
case Blue = '#0000FF';
// Enumy mogą mieć metody
public function label(): string
{
return match($this) {
self::Red => 'Czerwony',
self::Green => 'Zielony',
self::Blue => 'Niebieski',
};
}
}
// Tworzenie z wartości
$color = Color::from('#FF0000'); // Color::Red
$color = Color::tryFrom('#FFFFFF'); // null - bez wyjątku
// Dostęp do wartości
echo Color::Red->value; // #FF0000
echo Color::Red->name; // Red
echo Color::Red->label(); // Czerwony
Enumy z interfejsami#
interface HasDescription
{
public function description(): string;
}
enum PaymentMethod: string implements HasDescription
{
case CreditCard = 'credit_card';
case BankTransfer = 'bank_transfer';
case Blik = 'blik';
public function description(): string
{
return match($this) {
self::CreditCard => 'Karta kredytowa/debetowa',
self::BankTransfer => 'Przelew bankowy',
self::Blik => 'Płatność BLIK',
};
}
public function processingTime(): string
{
return match($this) {
self::CreditCard => 'natychmiast',
self::BankTransfer => '1-2 dni robocze',
self::Blik => 'natychmiast',
};
}
}
Fibers - lekka współbieżność (PHP 8.1)#
Fibers to mechanizm współbieżności na poziomie użytkownika, który pozwala na zawieszanie i wznawianie wykonywania kodu. Stanowią fundament dla nowoczesnych frameworków asynchronicznych w PHP, takich jak AMPHP czy ReactPHP.
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('pierwsze zawieszenie');
echo "Otrzymano: $value\n";
$value = Fiber::suspend('drugie zawieszenie');
echo "Otrzymano: $value\n";
});
// Rozpocznij wykonywanie fibera
$result = $fiber->start();
echo $result . "\n"; // "pierwsze zawieszenie"
// Wznów z wartością
$result = $fiber->resume('hello');
echo $result . "\n"; // "drugie zawieszenie"
$fiber->resume('world');
Praktyczny przykład - asynchroniczne zapytania HTTP#
function asyncHttpRequest(string $url): Fiber
{
return new Fiber(function () use ($url): array {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
// Zawieszamy fiber - pozwalamy innym zadaniom działać
Fiber::suspend();
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'status' => $httpCode,
'body' => json_decode($response, true),
];
});
}
// Uruchom wiele zapytań "równolegle"
$fibers = [
asyncHttpRequest('https://api.example.com/users'),
asyncHttpRequest('https://api.example.com/products'),
asyncHttpRequest('https://api.example.com/orders'),
];
foreach ($fibers as $fiber) {
$fiber->start();
}
// Zbierz wyniki
$results = [];
foreach ($fibers as $fiber) {
$results[] = $fiber->resume();
}
W praktyce fibers są najczęściej używane pod spodem przez frameworki, ale zrozumienie ich działania pozwala pisać wydajniejszy kod asynchroniczny.
Readonly properties i klasy (PHP 8.1 / 8.2)#
Readonly properties (PHP 8.1)#
Właściwości readonly mogą być ustawione tylko raz - zwykle w konstruktorze. Po inicjalizacji próba modyfikacji skutkuje błędem:
class Invoice
{
public function __construct(
public readonly string $number,
public readonly float $amount,
public readonly DateTimeImmutable $issuedAt,
public readonly string $currency = 'PLN',
) {}
}
$invoice = new Invoice(
number: 'FV/2025/001',
amount: 1500.00,
issuedAt: new DateTimeImmutable(),
);
echo $invoice->number; // FV/2025/001
// $invoice->number = 'FV/2025/002'; // Error: Cannot modify readonly property
Readonly classes (PHP 8.2)#
PHP 8.2 pozwala oznaczyć całą klasę jako readonly - wszystkie właściwości automatycznie stają się readonly:
readonly class Money
{
public function __construct(
public float $amount,
public string $currency,
) {}
public function add(Money $other): self
{
if ($this->currency !== $other->currency) {
throw new InvalidArgumentException('Waluty muszą się zgadzać');
}
return new self($this->amount + $other->amount, $this->currency);
}
public function multiply(float $factor): self
{
return new self($this->amount * $factor, $this->currency);
}
}
$price = new Money(100.00, 'PLN');
$tax = $price->multiply(0.23);
$total = $price->add($tax);
echo "$total->amount $total->currency"; // 123 PLN
Readonly klasy idealnie pasują do wzorca Value Object i pomagają tworzyć niemutowalne struktury danych.
Constructor promotion - mniej kodu, więcej przejrzystości#
Promocja konstruktora, wprowadzona w PHP 8.0, radykalnie redukuje ilość kodu potrzebnego do definiowania prostych klas:
// PHP 7 - dużo powtórzeń
class Product
{
private string $name;
private float $price;
private string $sku;
private int $stock;
public function __construct(
string $name,
float $price,
string $sku,
int $stock
) {
$this->name = $name;
$this->price = $price;
$this->sku = $sku;
$this->stock = $stock;
}
}
// PHP 8 - zwięźle i czytelnie
class Product
{
public function __construct(
private string $name,
private float $price,
private string $sku,
private int $stock = 0,
) {}
public function isAvailable(): bool
{
return $this->stock > 0;
}
}
Połączenie constructor promotion z readonly daje niezwykle zwięzły zapis DTO i value objectów:
readonly class OrderDTO
{
public function __construct(
public string $customerEmail,
public array $items,
public float $totalAmount,
public string $currency = 'PLN',
public ?string $couponCode = null,
) {}
}
Null-safe operator - bezpieczna nawigacja po obiektach#
Operator ?-> eliminuje kaskadę warunków null check, które wcześniej zaśmiecały kod:
// PHP 7 - łańcuch sprawdzeń
$country = null;
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) {
$city = $address->getCity();
if ($city !== null) {
$country = $city->getCountry();
}
}
}
// PHP 8 - jedna linia
$country = $user?->getAddress()?->getCity()?->getCountry();
Null-safe operator można łączyć z innymi nowościami PHP 8:
// Połączenie z named arguments i match
$shippingLabel = match($order?->getShippingMethod()?->getType()) {
'express' => 'Przesyłka ekspresowa',
'standard' => 'Przesyłka standardowa',
'pickup' => 'Odbiór osobisty',
default => 'Brak informacji o dostawie',
};
// Null-safe z wywołaniami metod
$length = $user?->getProfile()?->getBio()?->length() ?? 0;
First-class callable syntax (PHP 8.1)#
PHP 8.1 wprowadził elegancką składnię do tworzenia referencji do funkcji i metod za pomocą operatora (...):
// Stare podejście
$lengths = array_map(function ($str) {
return strlen($str);
}, $strings);
// PHP 8.1 - first-class callable
$lengths = array_map(strlen(...), $strings);
// Działa z metodami obiektów
class StringHelper
{
public function capitalize(string $str): string
{
return mb_strtoupper(mb_substr($str, 0, 1)) . mb_substr($str, 1);
}
}
$helper = new StringHelper();
$capitalized = array_map($helper->capitalize(...), ['php', 'javascript', 'python']);
// ['Php', 'Javascript', 'Python']
// Metody statyczne
$filtered = array_filter($items, Validator::isValid(...));
// Łańcuch z array_reduce
$pipeline = [
trim(...),
strtolower(...),
htmlspecialchars(...),
];
$result = array_reduce(
$pipeline,
fn($carry, $fn) => $fn($carry),
$input,
);
Kompilator JIT - PHP na sterydach#
Just-In-Time (JIT) compiler w PHP 8.0 to fundamentalna zmiana w sposobie wykonywania kodu. JIT kompiluje kod PHP do natywnego kodu maszynowego, co w niektórych scenariuszach daje spektakularne przyspieszenie.
Konfiguracja JIT#
; php.ini
opcache.enable=1
opcache.jit_buffer_size=256M
opcache.jit=1255
; Tryby JIT:
; 1205 - function JIT (bezpieczny, dobry start)
; 1235 - tracing JIT z optymalizacjami
; 1255 - tracing JIT z pełnymi optymalizacjami (max wydajność)
Kiedy JIT naprawdę pomaga?#
JIT daje największe korzyści w kodzie intensywnie wykorzystującym CPU:
// Operacje matematyczne - przyspieszenie nawet 3-4x
function fibonacci(int $n): int
{
if ($n <= 1) return $n;
return fibonacci($n - 1) + fibonacci($n - 2);
}
// Przetwarzanie obrazów, kryptografia, algorytmy ML
// - wszystko co jest CPU-bound zyskuje na JIT
// Typowe aplikacje webowe (I/O-bound) zyskają mniej,
// ale i tak warto włączyć JIT dla ogólnej poprawy wydajności
Benchmarki PHP 8 vs PHP 7#
W typowych aplikacjach webowych PHP 8 z JIT oferuje:
- Aplikacje Symfony/Laravel: 5-15% szybsze odpowiedzi
- Operacje na stringach: do 30% szybsze przetwarzanie
- Obliczenia matematyczne: do 300% przyspieszenia
- Zużycie pamięci: porównywalne z PHP 7.4, lekki wzrost przez bufor JIT
Dodatkowe funkcje warte uwagi#
Atrybuty (PHP 8.0)#
#[Route('/api/users', methods: ['GET'])]
#[Middleware('auth')]
public function listUsers(): JsonResponse
{
// ...
}
#[Deprecated('Use newMethod() instead')]
public function oldMethod(): void
{
// ...
}
Typ never (PHP 8.1)#
function redirect(string $url): never
{
header("Location: $url");
exit;
}
function throwNotFound(): never
{
throw new NotFoundException();
}
Stałe w interfejsach i traitach (PHP 8.2)#
enum DatabaseDriver: string
{
case MySQL = 'mysql';
case PostgreSQL = 'pgsql';
case SQLite = 'sqlite';
public function defaultPort(): int
{
return match($this) {
self::MySQL => 3306,
self::PostgreSQL => 5432,
self::SQLite => 0,
};
}
}
Praktyczny przykład - nowoczesna klasa serwisowa#
Połączmy wszystkie poznane funkcje w jednym, praktycznym przykładzie:
readonly class OrderService
{
public function __construct(
private OrderRepository $orders,
private PaymentGateway $payments,
private NotificationService $notifications,
private LoggerInterface $logger,
) {}
public function placeOrder(OrderDTO $dto): Order
{
$order = new Order(
items: $dto->items,
total: new Money(amount: $dto->totalAmount, currency: $dto->currency),
status: OrderStatus::Pending,
createdAt: new DateTimeImmutable(),
);
$paymentResult = match($dto->paymentMethod) {
PaymentMethod::CreditCard => $this->payments->chargeCard(order: $order),
PaymentMethod::BankTransfer => $this->payments->initTransfer(order: $order),
PaymentMethod::Blik => $this->payments->processBlik(order: $order),
};
$order = $paymentResult?->isSuccessful()
? $order->withStatus(OrderStatus::Paid)
: $order->withStatus(OrderStatus::PaymentFailed);
$this->orders->save($order);
$this->notifications->send(
recipient: $dto->customerEmail,
template: $order->status->notificationTemplate(),
context: ['order' => $order],
);
$this->logger->info('Order placed', [
'orderId' => $order->id,
'status' => $order->status->value,
'amount' => "$order->total->amount $order->total->currency",
]);
return $order;
}
}
Migracja na PHP 8 - od czego zacząć?#
- Zaktualizuj zależności - sprawdź kompatybilność bibliotek z PHP 8 za pomocą
composer why-not php ^8.2 - Włącz strict types - dodaj
declare(strict_types=1)do każdego pliku - Użyj narzędzi statycznej analizy - PHPStan i Psalm pomogą wykryć problemy
- Migruj stopniowo - zacznij od nowych plików, refaktoruj stare przy okazji
- Włącz JIT - skonfiguruj opcache dla środowiska produkcyjnego
Podsumowanie#
PHP 8 to nie jest drobna aktualizacja - to fundamentalna zmiana w sposobie pisania kodu PHP. Named arguments, union types, enums, readonly klasy, match expressions i JIT compiler tworzą ekosystem, w którym PHP jest konkurencyjny wobec języków takich jak TypeScript czy Kotlin.
Nowoczesny PHP to język bezpieczny typowo, ekspresywny, wydajny i przyjemny w użyciu. Jeśli jeszcze nie korzystasz z tych funkcji, najwyższy czas zacząć.
Potrzebujesz pomocy z migracją na PHP 8 lub budową nowoczesnej aplikacji backendowej? Zespół MDS Software Solutions Group specjalizuje się w tworzeniu wydajnych, skalowalnych rozwiązań w PHP, Node.js i .NET. Skontaktuj się z nami - pomożemy Ci wykorzystać pełen potencjał nowoczesnych technologii.
Zespół ekspertów programistycznych specjalizujących się w nowoczesnych technologiach webowych.