Konstruktor i property promotion

🎯 Po tej lekcji będziesz używać __construct do inicjalizacji obiektów i skrótu property promotion z PHP 8.

W poprzedniej lekcji tworzyłeś obiekt i ręcznie ustawiałeś jego właściwości jedna po drugiej. To działa, ale jest nieporęczne — szczególnie gdy obiekt ma kilka wymaganych pól. PHP rozwiązuje ten problem przez konstruktor: metodę specjalną wykonywaną automatycznie zaraz po new.

__construct — metoda inicjalizująca

Konstruktor nosi zawsze tę samą nazwę: __construct. Możesz w nim przyjąć parametry i przypisać ich wartości do właściwości obiektu:

<?php
declare(strict_types=1);

class Ksiazka
{
    public string $tytul;
    public string $autor;
    public float  $cena;

    public function __construct(string $tytul, string $autor, float $cena)
    {
        $this->tytul = $tytul;
        $this->autor = $autor;
        $this->cena  = $cena;
    }

    public function opisz(): string
    {
        return '"' . $this->tytul . '" — ' . $this->autor . ' (' . $this->cena . ' zł)';
    }
}

$k1 = new Ksiazka('Pan Tadeusz', 'Adam Mickiewicz', 29.99);
$k2 = new Ksiazka('Solaris', 'Stanisław Lem', 34.90);

echo $k1->opisz(); // wynik: "Pan Tadeusz" — Adam Mickiewicz (29.99 zł)
echo $k2->opisz(); // wynik: "Solaris" — Stanisław Lem (34.9 zł)

Argumenty przekazujesz w nawiasach new Ksiazka(...) — dokładnie jak do zwykłej funkcji.

Parametry z wartościami domyślnymi

Konstruktor może mieć parametry opcjonalne (z wartościami domyślnymi). Obowiązuje ta sama zasada co w funkcjach — parametry z wartościami domyślnymi muszą być na końcu:

<?php
declare(strict_types=1);

class Produkt
{
    public string $nazwa;
    public float  $cena;
    public bool   $dostepny;

    public function __construct(string $nazwa, float $cena, bool $dostepny = true)
    {
        $this->nazwa    = $nazwa;
        $this->cena     = $cena;
        $this->dostepny = $dostepny;
    }
}

$p1 = new Produkt('Jabłka', 3.49);          // dostepny = true (domyślnie)
$p2 = new Produkt('Arbuz', 12.99, false);   // jawnie: niedostępny

echo $p1->dostepny; // wynik: 1
echo $p2->dostepny; // wynik: (puste — false nie wyświetla się jako tekst)

Constructor property promotion — skrót PHP 8

Wzorzec „zadeklaruj właściwość → przyjmij parametr → przypisz" jest tak częsty, że PHP 8 wprowadził property promotion (promocję właściwości w konstruktorze). Wystarczy dodać modyfikator widoczności (public, private, protected) przed parametrem konstruktora — PHP automatycznie tworzy właściwość i przypisuje wartość:

<?php
declare(strict_types=1);

// Przed PHP 8 (tradycyjny styl):
class SamochodStary
{
    public string $marka;
    public string $model;
    public int    $rokProdukcji;

    public function __construct(string $marka, string $model, int $rokProdukcji)
    {
        $this->marka        = $marka;
        $this->model        = $model;
        $this->rokProdukcji = $rokProdukcji;
    }
}

// PHP 8+ (property promotion):
class Samochod
{
    public function __construct(
        public string $marka,
        public string $model,
        public int    $rokProdukcji,
    ) {}
}

$auto = new Samochod('Toyota', 'Corolla', 2022);
echo $auto->marka;        // wynik: Toyota
echo $auto->model;        // wynik: Corolla
echo $auto->rokProdukcji; // wynik: 2022

Obie klasy zachowują się identycznie — promotion to czysty skrót składniowy. Zwróć uwagę na pusty blok {} w konstruktorze z promotion: nie ma już potrzeby nic tam pisać.

💡 readonly — właściwości tylko do odczytu (PHP 8.1): Dodaj słowo readonly do promoted property, żeby uniemożliwić jej zmianę po inicjalizacji: public readonly string $marka. Próba zapisu po konstruktorze skończy się błędem. Świetne do modelowania obiektów wartości (ang. value objects).

Mieszanie promotion z tradycyjnym stylem

Możesz łączyć promoted i niepromoted parametry w jednym konstruktorze. Czasem masz właściwość obliczaną podczas inicjalizacji:

<?php
declare(strict_types=1);

class Zamowienie
{
    public float $wartoscBrutto;

    public function __construct(
        public readonly string $numerZamowienia,
        public readonly float  $wartoscNetto,
        public readonly float  $stawkaVat = 0.23,
    ) {
        // właściwość obliczana — nie nadaje się do promotion
        $this->wartoscBrutto = $wartoscNetto * (1 + $stawkaVat);
    }
}

$z = new Zamowienie('ZAM-2024-001', 100.0);
echo $z->numerZamowienia; // wynik: ZAM-2024-001
echo $z->wartoscNetto;    // wynik: 100
echo $z->wartoscBrutto;   // wynik: 123
⚠️ Kiedy NIE używać promotion: Jeśli konstruktor ma skomplikowaną logikę albo właściwość jest obliczana z wielu źródeł, tradycyjny styl bywa czytelniejszy. Promotion sprawdza się najlepiej przy prostych obiektach danych (ang. data transfer objects).

Konstruktor z walidacją

Konstruktor to dobre miejsce na sprawdzenie, czy dane mają sens — zanim zostaną zapisane:

<?php
declare(strict_types=1);

class Wiek
{
    public function __construct(
        public readonly int $wartosc,
    ) {
        if ($wartosc < 0 || $wartosc > 150) {
            throw new \InvalidArgumentException(
                "Wiek musi być w zakresie 0–150, podano: $wartosc"
            );
        }
    }
}

$w = new Wiek(25);
echo $w->wartosc; // wynik: 25

// new Wiek(200); // rzuci InvalidArgumentException

O wyjątkach (throw, catch) piszemy dokładnie w lekcji 9. Na razie zapamiętaj, że konstruktor może przerywać tworzenie obiektu, gdy dane są nieprawidłowe.

W kolejnej lekcji poznasz widoczność właściwości i metod — czyli dlaczego private jest ważniejsze niż public.

Sprawdź się

Jak PHP nazywa metodę wywoływaną automatycznie przy tworzeniu obiektu przez new?

Co robi constructor property promotion w PHP 8?

Jak w metodzie klasy odwołasz się do właściwości $imie zadeklarowanej przez property promotion?

Ćwiczenie

Zdefiniuj klasę Pracownik z właściwościami: string $imie, string $stanowisko i float $wynagrodzenie. Użyj constructor property promotion (PHP 8). Dodaj metodę miesiecznyKoszt(): float, która zwraca wynagrodzenie powiększone o 20% (koszty pracodawcy). Stwórz dwa obiekty i wyświetl miesięczny koszt każdego.

Pokaż rozwiązanie
<?php
declare(strict_types=1);

class Pracownik
{
    public function __construct(
        public readonly string $imie,
        public readonly string $stanowisko,
        public readonly float  $wynagrodzenie,
    ) {}

    public function miesiecznyKoszt(): float
    {
        return $this->wynagrodzenie * 1.20;
    }
}

$anna  = new Pracownik('Anna Kowalska',  'Programistka', 8000.0);
$tomek = new Pracownik('Tomek Nowak',    'Tester',       6000.0);

echo $anna->imie  . ': ' . $anna->miesiecznyKoszt()  . ' zł'; // wynik: Anna Kowalska: 9600 zł
echo $tomek->imie . ': ' . $tomek->miesiecznyKoszt() . ' zł'; // wynik: Tomek Nowak: 7200 zł