Przejdź do treści
Backend

PHP 8 - Nowoczesne funkcje i najlepsze praktyki dla programistów

Opublikowano:
·4 min czytania·Autor: MDS Software Solutions Group

PHP Nowoczesne funkcje

backend

PHP 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ąć?#

  1. Zaktualizuj zależności - sprawdź kompatybilność bibliotek z PHP 8 za pomocą composer why-not php ^8.2
  2. Włącz strict types - dodaj declare(strict_types=1) do każdego pliku
  3. Użyj narzędzi statycznej analizy - PHPStan i Psalm pomogą wykryć problemy
  4. Migruj stopniowo - zacznij od nowych plików, refaktoruj stare przy okazji
  5. 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.

Autor
MDS Software Solutions Group

Zespół ekspertów programistycznych specjalizujących się w nowoczesnych technologiach webowych.

PHP 8 - Nowoczesne funkcje i najlepsze praktyki dla programistów | MDS Software Solutions Group | MDS Software Solutions Group