Interfejsy
🎯 Po tej lekcji zdefiniujesz interfejs, zaimplementujesz go w klasie i zrozumiesz, czym różni się od dziedziczenia klasy abstrakcyjnej.
Klasa abstrakcyjna to dobry sposób na współdzielenie kodu — ale ma jedno ograniczenie: PHP pozwala dziedziczyć tylko po jednej klasie. Co zrobić, gdy klasa powinna spełniać kilka niezależnych kontraktów? Na przykład Faktura powinna umieć być drukowana i jednocześnie eksportowana do PDF. Tu wkraczają interfejsy.
Czym jest interfejs?
Interfejs to czysta lista wymagań — deklarujesz w nim metody bez żadnej implementacji. Klasa, która implementuje interfejs, zobowiązuje się dostarczyć wszystkie te metody.
Analogia: interfejs to formularz umowy. Opisuje obowiązki strony (klasy), ale nie mówi, jak je wypełnić.
<?php
declare(strict_types=1);
interface Drukowalna
{
public function drukuj(): void;
}
Interfejs:
- definiujesz słowem
interface, - metody mają sygnatury (nazwę, parametry, typ zwracany), ale brak ciała (klamr
{}), - metody są niejawnie
public— nie musisz pisaćpublic, ale możesz, - nie może mieć zwykłych właściwości (możliwe są tylko stałe).
implements — klasa spełniająca kontrakt
Klasa deklaruje, że implementuje interfejs słowem implements. Musi wtedy dostarczyć implementację wszystkich metod interfejsu:
<?php
declare(strict_types=1);
interface Drukowalna
{
public function drukuj(): void;
}
class Faktura implements Drukowalna
{
public function __construct(private string $numer) {}
public function drukuj(): void
{
echo 'Drukuję fakturę: ' . $this->numer;
}
}
class Raport implements Drukowalna
{
public function __construct(private string $tytul) {}
public function drukuj(): void
{
echo 'Drukuję raport: ' . $this->tytul;
}
}
$dokumenty = [
new Faktura('FV/2024/001'),
new Raport('Raport sprzedaży Q4'),
];
foreach ($dokumenty as $dok) {
$dok->drukuj();
}
// wynik: Drukuję fakturę: FV/2024/001
// wynik: Drukuję raport: Raport sprzedaży Q4
$dokumenty trzyma obiekty różnych klas, bo oba spełniają interfejs Drukowalna. PHP pozwala deklarować parametry funkcji jako Drukowalna $dok — taka funkcja przyjmie dowolny obiekt implementujący ten interfejs.Wiele interfejsów jednocześnie
Jedna klasa może implementować wiele interfejsów — wymień je po przecinku:
<?php
declare(strict_types=1);
interface Drukowalna
{
public function drukuj(): void;
}
interface Exportowalna
{
public function eksportuj(string $format): string;
}
interface Archiwizowalna
{
public function archiwizuj(): void;
}
class Faktura implements Drukowalna, Exportowalna, Archiwizowalna
{
public function __construct(private string $numer) {}
public function drukuj(): void
{
echo 'Drukuję: ' . $this->numer;
}
public function eksportuj(string $format): string
{
return $this->numer . '.' . strtolower($format);
}
public function archiwizuj(): void
{
echo 'Archiwizuję: ' . $this->numer;
}
}
$f = new Faktura('FV/2024/042');
$f->drukuj();
echo $f->eksportuj('PDF'); // wynik: FV/2024/042.pdf
$f->archiwizuj();
Interfejsy mogą rozszerzać inne interfejsy
Interfejs może rozszerzać inny interfejs (lub kilka) przez extends. Klasa implementująca rozszerzony interfejs musi dostarczyć metody obu:
<?php
declare(strict_types=1);
interface Zapisywalny
{
public function zapisz(): void;
}
interface Odczytywalny
{
public function odczytaj(): string;
}
interface Persistowalny extends Zapisywalny, Odczytywalny
{
// może dodać własne metody lub być "puste" — zbiera obu poprzedników
}
class Dokument implements Persistowalny
{
private string $tresc = '';
public function zapisz(): void
{
echo 'Zapisano.';
}
public function odczytaj(): string
{
return $this->tresc;
}
}
Stałe w interfejsach
Interfejs może definiować stałe (słowem const), ale nie właściwości:
<?php
declare(strict_types=1);
interface Statusowy
{
const AKTYWNY = 'aktywny';
const NIEAKTYWNY = 'nieaktywny';
public function getStatus(): string;
}
class Uzytkownik implements Statusowy
{
private string $status = self::AKTYWNY;
public function getStatus(): string
{
return $this->status;
}
}
$u = new Uzytkownik();
echo $u->getStatus(); // wynik: aktywny
echo Statusowy::AKTYWNY; // wynik: aktywny
implements DanyInterfejs, ale nie dostarcza implementacji którejś z wymaganych metod, PHP zgłosi Fatal Error już przy wczytywaniu klasy.Interfejs vs klasa abstrakcyjna
| | Interfejs | Klasa abstrakcyjna | |---|---|---| | Implementacja metod | Brak (tylko deklaracje) | Możliwa (część może być konkretna) | | Właściwości | Brak (tylko stałe) | Możliwe | | Ile naraz? | Wiele | Tylko jedna | | Konstruktor | Brak | Możliwy | | Kiedy? | Kontrakt bez implementacji | Wspólna baza z częściową implementacją |
Praktyczna zasada: jeśli chcesz tylko opisać co klasa robi (bez jak), użyj interfejsu. Jeśli chcesz też dostarczyć część gotowego kodu, użyj klasy abstrakcyjnej.
W następnej lekcji poznasz traity — mechanizm pozwalający współdzielić konkretny kod między klasami bez dziedziczenia.
Sprawdź się
Ile interfejsów może jednocześnie implementować jedna klasa PHP?
Klasa implementuje wiele interfejsów przez wyliczenie ich po przecinku: class Faktura implements Drukowalna, Exportowalna. To jedna z kluczowych zalet interfejsów — tam gdzie PHP nie ma wielokrotnego dziedziczenia klas, można zobowiązać klasę do wielu kontraktów naraz.
Jaka jest widoczność metod zadeklarowanych w interfejsie?
Metody w interfejsie są niejawnie public. Nie możesz zadeklarować metody interfejsu jako private lub protected — interfejs opisuje publiczne zachowanie klasy.
Czym różni się interfejs od klasy abstrakcyjnej?
Interfejs to czysty kontrakt: same deklaracje metod i ewentualnie stałe. Klasa abstrakcyjna może mieć część metod zaimplementowanych i część abstrakcyjnych. Ponadto klasa może rozszerzać tylko jedną klasę (abstrakcyjną lub nie), ale implementować wiele interfejsów.
Ćwiczenie
Zdefiniuj interfejs Porównywalny z metodą porownajDo(mixed $inny): int (zwraca ujemną, zero lub dodatnią, jak w usort). Zdefiniuj interfejs Opisywalny z metodą opisz(): string. Utwórz klasę Produkt (nazwa, cena), która implementuje oba interfejsy. porownajDo sortuje po cenie. Posortuj tablicę produktów używając usort i wyświetl nazwy.
Pokaż rozwiązanie
<?php
declare(strict_types=1);
interface Porownywalny
{
public function porownajDo(mixed $inny): int;
}
interface Opisywalny
{
public function opisz(): string;
}
class Produkt implements Porownywalny, Opisywalny
{
public function __construct(
public readonly string $nazwa,
public readonly float $cena,
) {}
public function porownajDo(mixed $inny): int
{
return $this->cena <=> $inny->cena;
}
public function opisz(): string
{
return $this->nazwa . ' — ' . $this->cena . ' zł';
}
}
$produkty = [
new Produkt('Arbuz', 12.99),
new Produkt('Jabłka', 3.49),
new Produkt('Chleb', 4.99),
];
usort($produkty, fn(Produkt $a, Produkt $b) => $a->porownajDo($b));
foreach ($produkty as $p) {
echo $p->opisz(); // wynik kolejno: Jabłka — 3.49 zł / Chleb — 4.99 zł / Arbuz — 12.99 zł
}