Traity

🎯 Po tej lekcji stworzysz własny trait, użyjesz go w kilku klasach i rozwiążesz konflikt nazw metod przez insteadof i as.

PHP nie ma wielokrotnego dziedziczenia — klasa może mieć tylko jeden bezpośredni rodzic. Co jednak zrobić, gdy kilka niezwiązanych ze sobą klas potrzebuje tego samego kawałka kodu? Na przykład zarówno Artykul, jak i Produkt mają logowanie zmian, ale nie mają ze sobą nic wspólnego. Trait to odpowiedź PHP na ten problem.

Czym jest trait?

Trait (pol. cecha) to zbiór metod i właściwości, które możesz wkleić do dowolnej klasy. Nie jest klasą — nie tworzysz jego instancji. Możesz go traktować jak fragment kodu wielokrotnego użytku kopiowany do klasy w momencie kompilacji.

<?php
declare(strict_types=1);

trait Loggable
{
    private array $logi = [];

    public function log(string $komunikat): void
    {
        $this->logi[] = date('H:i:s') . ' ' . $komunikat;
    }

    public function getLogi(): array
    {
        return $this->logi;
    }
}

use wewnątrz klasy

Dołączasz trait do klasy przez use NazwaTrait; wewnątrz ciała klasy:

<?php
declare(strict_types=1);

trait Loggable
{
    private array $logi = [];

    public function log(string $komunikat): void
    {
        $this->logi[] = date('H:i:s') . ' ' . $komunikat;
    }

    public function getLogi(): array
    {
        return $this->logi;
    }
}

class Zamowienie
{
    use Loggable;

    public function __construct(private string $numer) {}

    public function zrealizuj(): void
    {
        // ... logika realizacji ...
        $this->log('Zamówienie ' . $this->numer . ' zostało zrealizowane.');
    }
}

class Uzytkownik
{
    use Loggable;

    public function __construct(private string $email) {}

    public function zalogujSie(): void
    {
        $this->log('Logowanie: ' . $this->email);
    }
}

$z = new Zamowienie('ZAM-001');
$z->zrealizuj();
print_r($z->getLogi());
// wynik: Array ( [0] => HH:MM:SS Zamówienie ZAM-001 zostało zrealizowane. )

$u = new Uzytkownik('[email protected]');
$u->zalogujSie();
print_r($u->getLogi());
// wynik: Array ( [0] => HH:MM:SS Logowanie: [email protected] )

Obie klasy zyskały metodę log() i getLogi() bez żadnego dziedziczenia między sobą.

Trait z właściwościami

Trait może deklarować właściwości — zostaną one dodane do klasy używającej traitu:

<?php
declare(strict_types=1);

trait Czasowany
{
    private float $czasStartu;

    public function startujTimer(): void
    {
        $this->czasStartu = microtime(true);
    }

    public function getCzasMs(): float
    {
        return round((microtime(true) - $this->czasStartu) * 1000, 2);
    }
}

class Generator
{
    use Czasowany;

    public function generuj(): string
    {
        $this->startujTimer();
        // ... intensywne obliczenia ...
        usleep(5000); // symulacja 5ms pracy
        return 'Gotowe w ' . $this->getCzasMs() . ' ms';
    }
}

$g = new Generator();
echo $g->generuj(); // wynik: Gotowe w 5.XX ms (czas wykonania)

Używanie wielu traitów

Klasa może używać wielu traitów jednocześnie — wylistuj je po przecinku lub w osobnych use:

<?php
declare(strict_types=1);

trait Loggable
{
    public function log(string $msg): void
    {
        echo '[LOG] ' . $msg;
    }
}

trait Cacheable
{
    private array $cache = [];

    public function zapamiętaj(string $klucz, mixed $wartosc): void
    {
        $this->cache[$klucz] = $wartosc;
    }

    public function pobierzZCache(string $klucz): mixed
    {
        return $this->cache[$klucz] ?? null;
    }
}

class Serwis
{
    use Loggable, Cacheable;

    public function pobierzDane(string $id): string
    {
        if ($this->pobierzZCache($id) !== null) {
            $this->log("Cache hit: $id");
            return (string) $this->pobierzZCache($id);
        }
        $dane = 'dane_dla_' . $id; // symulacja pobierania
        $this->zapamiętaj($id, $dane);
        $this->log("Pobrano: $id");
        return $dane;
    }
}

$s = new Serwis();
echo $s->pobierzDane('u1'); // wynik: [LOG] Pobrano: u1dane_dla_u1
echo $s->pobierzDane('u1'); // wynik: [LOG] Cache hit: u1dane_dla_u1

Rozwiązywanie konfliktów — insteadof i as

Gdy dwa traity mają metodę o tej samej nazwie, PHP wymaga, żebyś jawnie rozstrzygnął konflikt:

  • TraitA::metoda insteadof TraitB — użyj wersji z TraitA, zignoruj TraitB,
  • TraitB::metoda as nazwaAliasu — stwórz alias, żeby obie wersje były dostępne.
<?php
declare(strict_types=1);

trait A
{
    public function witaj(): void
    {
        echo 'Witaj z A';
    }
}

trait B
{
    public function witaj(): void
    {
        echo 'Witaj z B';
    }
}

class Klasa
{
    use A, B {
        A::witaj insteadof B; // wersja A ma pierwszeństwo
        B::witaj as witajB;   // wersja B dostępna pod aliasem witajB
    }
}

$k = new Klasa();
$k->witaj();  // wynik: Witaj z A
$k->witajB(); // wynik: Witaj z B
⚠️ Konflikty nazw właściwości: Jeśli dwa traity deklarują właściwość o tej samej nazwie i niezgodnym typie lub widoczności, PHP zgłosi Fatal Error. W razie konfliktu właściwości musisz zreorganizować traity — insteadof dotyczy tylko metod.

Trait implementujący interfejs

Trait nie może sam implementować interfejsu, ale klasa korzystająca z traitu może — i trait może dostarczyć wymaganą implementację:

<?php
declare(strict_types=1);

interface Serializowalny
{
    public function toArray(): array;
}

trait SerializacjaTrait
{
    public function toArray(): array
    {
        return get_object_vars($this);
    }
}

class Produkt implements Serializowalny
{
    use SerializacjaTrait;

    public function __construct(
        public readonly string $nazwa,
        public readonly float  $cena,
    ) {}
}

$p = new Produkt('Jabłka', 3.49);
print_r($p->toArray());
// wynik: Array ( [nazwa] => Jabłka [cena] => 3.49 )
💡 Kiedy używać traitu? Gdy ta sama logika pojawia się w kilku klasach, które nie mają ze sobą naturalnego związku dziedziczenia (np. logowanie, znaczniki czasu, walidacja). Jeśli klasy mają relację „jest rodzajem" — zostań przy dziedziczeniu.

W kolejnej lekcji poznasz enumy (PHP 8.1) — bezpieczne typy wyliczeniowe zastępujące stałe liczbowe i tekstowe.

Sprawdź się

Jak dołączasz trait do klasy?

Czym trait różni się od klasy abstrakcyjnej?

Jak rozwiązujesz konflikt, gdy dwa traity mają metodę o tej samej nazwie?

Ćwiczenie

Stwórz trait Stemplowany z właściwością private ?DateTimeImmutable $dataUtworzenia = null i metodą stempluj(): void (ustawia datę) oraz getDataUtworzenia(): string (zwraca datę jako 'Y-m-d' lub 'brak'). Użyj go w klasach Artykul i Komentarz, każda z własnym konstruktorem. Wywołaj stempluj() i wyświetl datę.

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

trait Stemplowany
{
    private ?\DateTimeImmutable $dataUtworzenia = null;

    public function stempluj(): void
    {
        $this->dataUtworzenia = new \DateTimeImmutable();
    }

    public function getDataUtworzenia(): string
    {
        return $this->dataUtworzenia?->format('Y-m-d') ?? 'brak';
    }
}

class Artykul
{
    use Stemplowany;

    public function __construct(public readonly string $tytul) {}
}

class Komentarz
{
    use Stemplowany;

    public function __construct(public readonly string $tresc) {}
}

$a = new Artykul('PHP 8 — co nowego?');
$a->stempluj();
echo $a->tytul . ' — ' . $a->getDataUtworzenia(); // wynik: PHP 8 — co nowego? — 2026-06-30 (dzisiejsza data)

$k = new Komentarz('Świetny artykuł!');
echo $k->getDataUtworzenia(); // wynik: brak (nie wywołano stempluj)