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
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 )
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?
Trait włącza się słowem use wewnątrz ciała klasy: class MojaKlasa { use MojTrait; }. Nie używa się extends ani implements — te służą odpowiednio do dziedziczenia i implementowania interfejsów.
Czym trait różni się od klasy abstrakcyjnej?
Trait nie jest klasą ani interfejsem — jest mechanizmem ponownego użycia kodu. PHP 'wkleja' metody traitu bezpośrednio do klasy, która go używa. Nie można instancjonować traitu ani typować przez niego parametrów (np. function foo(MojTrait $x) jest błędem).
Jak rozwiązujesz konflikt, gdy dwa traity mają metodę o tej samej nazwie?
Gdy dwa traity eksponują metodę o tej samej nazwie, PHP żąda jawnego rozstrzygnięcia: TraitA::metoda insteadof TraitB; wskazuje, której wersji użyć. TraitB::metoda as metodaB; tworzy alias, dzięki któremu obie wersje są dostępne.
Ć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)