Lekcja 29 (OOP 9): Cechy (Traits) - Horyzontalne Współdzielenie Funkcjonalności
Witaj w dziewiątej lekcji naszego modułu o Programowaniu Obiektowym w PHP! W poprzedniej lekcji omówiliśmy polimorfizm i interfejsy, które pozwalają na tworzenie elastycznych kontraktów i wielopostaciowych zachowań obiektów. PHP, podobnie jak wiele innych języków obiektowych, wspiera tylko pojedyncze dziedziczenie klas (klasa może dziedziczyć bezpośrednio tylko po jednej klasie nadrzędnej). Może to być ograniczeniem, gdy chcemy współdzielić funkcjonalności między klasami, które niekoniecznie pasują do jednej hierarchii dziedziczenia. Aby rozwiązać ten problem, PHP od wersji 5.4 wprowadziło mechanizm zwany Cechami (Traits).
Cechy pozwalają na horyzontalne współdzielenie kodu – czyli na reużywanie zestawów metod w niezależnych klasach, które nie są ze sobą powiązane relacją dziedziczenia. Jest to forma kompozycji kodu, która pozwala na "wstrzykiwanie" funkcjonalności do klas bez konieczności uciekania się do dziedziczenia. W tej lekcji dokładnie przyjrzymy się, czym są cechy, jak je definiować, używać, oraz jak rozwiązywać konflikty nazw, które mogą pojawić się przy korzystaniu z wielu cech.
Czym Są Cechy (Traits)?
Cecha (Trait) to mechanizm reużywania kodu w językach z pojedynczym dziedziczeniem, takich jak PHP. Cecha jest podobna do klasy, ale ma na celu grupowanie funkcjonalności w drobnoziarnisty i spójny sposób. Metody zdefiniowane w cesze mogą być następnie "włączone" (użyte) wewnątrz definicji klasy. Użycie cechy w klasie jest podobne do skopiowania jej metod do tej klasy, ale z dodatkowymi mechanizmami zarządzania konfliktami.
Kluczowe cechy Cech (Traits):
- Deklarowane są za pomocą słowa kluczowego
trait
(np.trait NazwaCechy
). - Mogą zawierać metody (zarówno publiczne, chronione, jak i prywatne, statyczne i niestatyczne) oraz właściwości (od PHP 8.0 cechy mogą mieć właściwości, ale ich użycie wymaga ostrożności). Tradycyjnie cechy skupiały się na metodach.
- Nie można utworzyć instancji cechy bezpośrednio (
new NazwaCechy()
spowoduje błąd). - Klasa "używa" cechy za pomocą słowa kluczowego
use
wewnątrz ciała klasy (np.class MojaKlasa { use NazwaCechy; }
). - Metody z cechy stają się częścią klasy, tak jakby były w niej zdefiniowane, z zachowaniem swoich modyfikatorów dostępu.
- Klasa może używać wielu cech (oddzielonych przecinkami, np.
use CechaA, CechaB;
). - Cechy mogą używać innych cech.
- Pozwalają na rozwiązanie problemu ograniczenia pojedynczego dziedziczenia, umożliwiając współdzielenie kodu między niepowiązanymi klasami.
- Umożliwiają kompozycję funkcjonalności z różnych źródeł.
Definiowanie i Używanie Cechy
Stworzenie i użycie cechy jest stosunkowo proste. Najpierw definiujemy cechę z metodami, które chcemy współdzielić, a następnie używamy jej w klasie.
<?php
// Definicja cechy
trait Logowanie
{
// Właściwość w cesze (od PHP 8.0, wcześniej cechy głównie zawierały metody)
// Należy jednak pamiętać, że stan (właściwości) w cechach może prowadzić do problemów,
// jeśli nie jest zarządzany ostrożnie, ponieważ cecha nie wie, w jakim kontekście klasy będzie użyta.
// Lepiej, gdy cechy dostarczają zachowania (metody), a stan jest zarządzany przez klasę.
private string $prefixLogu = "[LOG]";
public function log(string $wiadomosc): void
{
$czas = date("Y-m-d H:i:s");
echo $this->prefixLogu . " [" . $czas . "]: " . $wiadomosc . "<br>";
}
public function setPrefixLogu(string $prefix): void
{
$this->prefixLogu = $prefix;
}
// Cecha może mieć metody abstrakcyjne, które muszą być zaimplementowane przez klasę używającą cechy
abstract public function getIdentyfikatorLogowania(): string;
public function logZIdentyfikatorem(string $wiadomosc): void
{
$identyfikator = $this->getIdentyfikatorLogowania(); // Wywołanie metody abstrakcyjnej
$this->log("({$identyfikator}) - {$wiadomosc}");
}
}
class UzytkownikSystemu
{
use Logowanie; // Użycie cechy Logowanie
public string $nazwa;
public function __construct(string $nazwa)
{
$this->nazwa = $nazwa;
$this->setPrefixLogu("[USER_LOG]"); // Możemy użyć metody z cechy do konfiguracji
}
public function wykonajAkcje(): void
{
// Metoda log() jest dostępna tak, jakby była zdefiniowana w klasie UzytkownikSystemu
$this->log("Użytkownik " . $this->nazwa . " wykonuje akcję.");
$this->logZIdentyfikatorem("Akcja specjalna wykonana.");
}
// Implementacja metody abstrakcyjnej wymaganej przez cechę Logowanie
public function getIdentyfikatorLogowania(): string
{
return "User:" . $this->nazwa;
}
}
class ProcesSystemowy
{
use Logowanie; // Ta sama cecha użyta w innej klasie
private int $pid;
public function __construct(int $pid)
{
$this->pid = $pid;
// Domyślny prefix logu z cechy zostanie użyty, jeśli go nie zmienimy
}
public function uruchom(): void
{
$this->log("Proces PID: " . $this->pid . " został uruchomiony.");
$this->logZIdentyfikatorem("Uruchomienie procesu zakończone.");
}
public function getIdentyfikatorLogowania(): string
{
return "PID:" . $this->pid;
}
}
$user = new UzytkownikSystemu("Alicja");
$user->wykonajAkcje();
echo "<hr>";
$proces = new ProcesSystemowy(12345);
$proces->uruchom();
/*
Przykładowy wynik:
[USER_LOG] [2023-10-27 10:00:00]: Użytkownik Alicja wykonuje akcję.
[USER_LOG] [2023-10-27 10:00:00]: (User:Alicja) - Akcja specjalna wykonana.
---
[LOG] [2023-10-27 10:00:00]: Proces PID: 12345 został uruchomiony.
[LOG] [2023-10-27 10:00:00]: (PID:12345) - Uruchomienie procesu zakończone.
*/
?>
W tym przykładzie:
- Cecha
Logowanie
dostarcza funkcjonalność logowania wiadomości. Zawiera metodęlog()
,setPrefixLogu()
, właściwość$prefixLogu
oraz metodę abstrakcyjnągetIdentyfikatorLogowania()
i metodęlogZIdentyfikatorem()
, która z niej korzysta. - Klasy
UzytkownikSystemu
iProcesSystemowy
używają cechyLogowanie
za pomocąuse Logowanie;
. - Obie klasy muszą zaimplementować metodę abstrakcyjną
getIdentyfikatorLogowania()
, ponieważ jest ona wymagana przez cechę. - Metody
log()
isetPrefixLogu()
z cechy są dostępne w obu klasach tak, jakby były ich własnymi metodami. Mogą one również uzyskać dostęp do właściwości$prefixLogu
zdefiniowanej w cesze (choć, jak wspomniano, właściwości w cechach należy używać z rozwagą).
Rozwiązywanie Konfliktów Nazw (Name Conflicts)
Problem może pojawić się, gdy klasa używa wielu cech, a niektóre z tych cech definiują metody o tej samej nazwie. PHP wymaga jawnego rozwiązania takich konfliktów. Można to zrobić za pomocą operatorów insteadof
(zamiast) i as
(jako).
<?php
trait CechaA
{
public function wspolnaMetoda(): void
{
echo "Metoda z CechaA<br>";
}
public function metodaTylkoZA(): void
{
echo "Metoda tylko z CechaA<br>";
}
}
trait CechaB
{
public function wspolnaMetoda(): void
{
echo "Metoda z CechaB<br>";
}
public function metodaTylkoZB(): void
{
echo "Metoda tylko z CechaB<br>";
}
}
class MojaKlasaKonflikt
{
use CechaA, CechaB {
// Rozwiązanie konfliktu dla wspolnaMetoda()
// Chcemy użyć implementacji z CechaB, a implementację z CechaA zignorować (lub uczynić dostępną pod inną nazwą)
CechaB::wspolnaMetoda insteadof CechaA;
// Możemy również udostępnić metodę z CechaA pod inną nazwą (aliasem)
CechaA::wspolnaMetoda as wspolnaMetodaZA;
}
// Jeśli nie rozwiążemy konfliktu, PHP zgłosi Fatal error: Trait method ... has not been applied as ... because of collision by ...
}
$objK = new MojaKlasaKonflikt();
$objK->wspolnaMetoda(); // Wywoła: Metoda z CechaB
$objK->wspolnaMetodaZA(); // Wywoła: Metoda z CechaA
$objK->metodaTylkoZA(); // Wywoła: Metoda tylko z CechaA
$objK->metodaTylkoZB(); // Wywoła: Metoda tylko z CechaB
// Inny przykład: zmiana modyfikatora dostępu metody z cechy
trait UzyteczneNarzedzia
{
private function pomocniczaFunkcja(): string
{
return "Wynik funkcji pomocniczej";
}
public function wykonajZadanie(): void
{
echo "Wykonuję zadanie używając: " . $this->pomocniczaFunkcja() . "<br>";
}
}
class ManagerZadan
{
use UzyteczneNarzedzia {
// Możemy zmienić modyfikator dostępu metody z cechy w kontekście tej klasy
// np. uczynić metodę prywatną z cechy publiczną w tej klasie (choć to rzadkie i może naruszać enkapsulację cechy)
// lub publiczną z cechy uczynić chronioną/prywatną w klasie.
// Tutaj przykład uczynienia metody publicznej z cechy chronioną w klasie:
// UzyteczneNarzedzia::wykonajZadanie as protected wykonajChronioneZadanie;
// Możemy też po prostu użyć metody publicznej i zmienić jej alias oraz modyfikator
UzyteczneNarzedzia::pomocniczaFunkcja as public getWynikPomocniczy; // Uczynienie prywatnej metody publiczną pod nową nazwą
}
public function uruchom(): void
{
$this->wykonajZadanie(); // Działa, bo wykonajZadanie jest public w cesze
// echo $this->pomocniczaFunkcja(); // Błąd: pomocniczaFunkcja jest private w cesze i nie została wyeksponowana inaczej
echo "Publiczny dostęp do wyniku: " . $this->getWynikPomocniczy() . "<br>";
}
}
$manager = new ManagerZadan();
$manager->uruchom();
/*
Przykładowy wynik:
Metoda z CechaB
Metoda z CechaA
Metoda tylko z CechaA
Metoda tylko z CechaB
Wykonuję zadanie używając: Wynik funkcji pomocniczej
Publiczny dostęp do wyniku: Wynik funkcji pomocniczej
*/
?>
W bloku use
:
NazwaCechy::metoda insteadof InnaCecha;
mówi PHP, aby użyć implementacjimetoda()
zNazwaCechy
i zignorować tę zInnaCecha
.NazwaCechy::metoda as nowyAlias;
tworzy alias dla metody. Oryginalna metoda (jeśli nie została wykluczona przezinsteadof
) nadal istnieje pod swoją oryginalną nazwą, a dodatkowo jest dostępna podnowyAlias
. Można też zmienić modyfikator dostępu dla aliasu, np.NazwaCechy::metoda as public publicznyAlias;
lubas protected chronionyAlias;
.
Kolejność Pierwszeństwa (Precedence)
Kiedy metody są dziedziczone z klasy bazowej, implementowane z interfejsu i włączane z cechy, istnieje określona kolejność pierwszeństwa:
- Metody zdefiniowane bezpośrednio w bieżącej klasie mają najwyższy priorytet i nadpisują metody z cech oraz metody odziedziczone.
- Metody z cech nadpisują metody odziedziczone z klasy bazowej.
- Metody odziedziczone z klasy bazowej mają najniższy priorytet (ale wyższy niż domyślne implementacje z interfejsów, jeśli takie istnieją od PHP 8.0).
Oznacza to, że jeśli klasa dziedziczy metodę od rodzica i używa cechy, która definiuje metodę o tej samej nazwie, to wersja z cechy zostanie użyta. Jeśli sama klasa zdefiniuje metodę o tej nazwie, to jej własna implementacja będzie miała pierwszeństwo przed cechą i klasą bazową.
<?php
class KlasaBazowaPrecedencja
{
public function przywitaj(): void
{
echo "Witaj z Klasy Bazowej!<br>";
}
}
trait CechaPrecedencja
{
public function przywitaj(): void
{
echo "Witaj z Cechy!<br>";
}
}
class KlasaPotomnaPrecedencja extends KlasaBazowaPrecedencja
{
use CechaPrecedencja;
// Jeśli odkomentujemy tę metodę, ona będzie miała najwyższy priorytet:
/*
public function przywitaj(): void
{
echo "Witaj z Klasy Potomnej!<br>";
}
*/
}
$objP = new KlasaPotomnaPrecedencja();
$objP->przywitaj(); // Wyświetli: Witaj z Cechy!
// Jeśli metoda w KlasaPotomnaPrecedencja byłaby odkomentowana, wyświetliłoby: Witaj z Klasy Potomnej!
?>
Cechy Mogą Używać Innych Cech
Jedna cecha może być skomponowana z innych cech, co pozwala na jeszcze większą modularność.
<?php
trait PomocnikStringow
{
public function czyPusty(string $str): bool
{
return empty(trim($str));
}
}
trait Walidator
{
use PomocnikStringow; // Cecha Walidator używa cechy PomocnikStringow
public function walidujEmail(string $email): bool
{
if ($this->czyPusty($email)) { // Użycie metody z PomocnikStringow
return false;
}
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
}
class RejestracjaUzytkownika
{
use Walidator; // Użycie cechy Walidator (która zawiera PomocnikStringow)
public function zarejestruj(string $email, string $haslo): void
{
if (!$this->walidujEmail($email)) {
echo "Niepoprawny email!<br>";
return;
}
if ($this->czyPusty($haslo)) { // Metoda z PomocnikStringow jest dostępna
echo "Hasło nie może być puste!<br>";
return;
}
echo "Użytkownik z emailem {$email} zarejestrowany.<br>";
}
}
$rejestracja = new RejestracjaUzytkownika();
$rejestracja->zarejestruj("test@example.com", "secret");
$rejestracja->zarejestruj("invalid-email", "password");
$rejestracja->zarejestruj("valid@example.com", " ");
/*
Przykładowy wynik:
Użytkownik z emailem test@example.com zarejestrowany.
Niepoprawny email!
Hasło nie może być puste!
*/
?>
Właściwości w Cechach (od PHP 8.0)
Od PHP 8.0 cechy mogą definiować właściwości. Jednakże, jeśli klasa używająca cechy już definiuje właściwość o tej samej nazwie (zgodną co do typu i wartości domyślnej, jeśli są zadeklarowane), nie spowoduje to błędu. Jeśli właściwości są niezgodne (np. różne typy lub różne wartości domyślne bez możliwości nadpisania), PHP zgłosi błąd. Generalnie, poleca się, aby cechy były bezstanowe (nie miały własnych właściwości) i operowały na stanie klasy, w której są używane (np. poprzez $this
lub metody abstrakcyjne dostarczające dane).
Kiedy Używać Cech?
- Współdzielenie funkcjonalności między niepowiązanymi klasami: Gdy masz zestaw metod, który chcesz użyć w różnych klasach, które nie pasują do wspólnej hierarchii dziedziczenia. Np. funkcjonalność logowania, serializacji, obsługi zdarzeń.
- Redukcja duplikacji kodu: Zamiast kopiować i wklejać te same metody do wielu klas.
- Kompozycja zachowań: Klasa może być "zbudowana" z wielu małych, spójnych zestawów funkcjonalności dostarczanych przez cechy.
- Obejście ograniczeń pojedynczego dziedziczenia: Cechy pozwalają na osiągnięcie czegoś w rodzaju "wielokrotnego dziedziczenia" zachowań, ale nie stanu czy interfejsu w pełnym tego słowa znaczeniu.
Należy jednak używać cech z umiarem. Nadmierne ich stosowanie lub tworzenie bardzo dużych, złożonych cech może prowadzić do kodu trudnego do zrozumienia i debugowania ("Trait hell"). Cechy powinny być małe, spójne i dobrze zdefiniowane.
Podsumowanie Lekcji
W tej lekcji nauczyliśmy się, czym są Cechy (Traits) w PHP i jak pozwalają one na horyzontalne współdzielenie kodu oraz reużywanie funkcjonalności w klasach, które nie są ze sobą powiązane hierarchią dziedziczenia. Zobaczyliśmy, jak definiować cechy za pomocą słowa kluczowego trait
i jak włączać je do klas za pomocą use
.
Omówiliśmy mechanizmy rozwiązywania konfliktów nazw metod przy użyciu wielu cech (insteadof
i as
) oraz zasady pierwszeństwa metod (klasa > cecha > klasa bazowa). Dowiedzieliśmy się również, że cechy mogą używać innych cech oraz że od PHP 8.0 mogą zawierać właściwości, choć zaleca się ostrożność w ich stosowaniu.
Cechy są potężnym narzędziem do kompozycji zachowań i redukcji duplikacji kodu, stanowiąc ważne uzupełnienie dla mechanizmów dziedziczenia i interfejsów w programowaniu obiektowym w PHP.
W następnej, ostatniej lekcji tego modułu, zajmiemy się organizacją kodu w większych projektach za pomocą Przestrzeni Nazw (Namespaces) oraz automatycznym ładowaniem klas (Autoloading), w tym standardem PSR-4.
Zadanie praktyczne
Stwórz cechę Timestampable
, która dodaje do klasy właściwości $createdAt
i $updatedAt
oraz metody do ich ustawiania.
- Zdefiniuj cechę
Timestampable
.- Powinna zawierać chronione właściwości
?DateTimeImmutable $createdAt = null;
i?DateTimeImmutable $updatedAt = null;
(użyj typów dopuszczających null i wartości domyślnej null). - Metodę publiczną
touch(): void
, która ustawia$createdAt
na aktualny czas (jeśli jest null) oraz zawsze ustawia$updatedAt
na aktualny czas. Użyjnew DateTimeImmutable()
. - Metody publiczne
getCreatedAt(): ?DateTimeImmutable
igetUpdatedAt(): ?DateTimeImmutable
.
- Powinna zawierać chronione właściwości
- Stwórz klasę
Artykul
, która używa cechyTimestampable
.- Dodaj właściwość publiczną
$tytul
. - Konstruktor powinien przyjmować tytuł i od razu wywoływać metodę
touch()
. - Dodaj metodę
aktualizujTytul(string $nowyTytul): void
, która zmienia tytuł i ponownie wywołujetouch()
.
- Dodaj właściwość publiczną
- Stwórz klasę
Komentarz
, która również używa cechyTimestampable
.- Dodaj właściwość publiczną
$tresc
. - Konstruktor powinien przyjmować treść i od razu wywoływać metodę
touch()
.
- Dodaj właściwość publiczną
- Przetestuj: utwórz obiekt
Artykul
, wyświetl jego czasy, zaktualizuj tytuł, ponownie wyświetl czasy. Utwórz obiektKomentarz
i wyświetl jego czasy. Sformatuj daty do czytelnej postaci (np.Y-m-d H:i:s
).
Kliknij, aby zobaczyć przykładowe rozwiązanie
<?php
trait Timestampable
{
protected ?DateTimeImmutable $createdAt = null;
protected ?DateTimeImmutable $updatedAt = null;
public function touch(): void
{
if ($this->createdAt === null) {
$this->createdAt = new DateTimeImmutable();
}
$this->updatedAt = new DateTimeImmutable();
}
public function getCreatedAt(): ?DateTimeImmutable
{
return $this->createdAt;
}
public function getUpdatedAt(): ?DateTimeImmutable
{
return $this->updatedAt;
}
public function formatTimestamp(?DateTimeImmutable $timestamp): string
{
return $timestamp ? $timestamp->format("Y-m-d H:i:s") : "N/A";
}
}
class Artykul
{
use Timestampable;
public string $tytul;
public function __construct(string $tytul)
{
$this->tytul = $tytul;
$this->touch();
}
public function aktualizujTytul(string $nowyTytul): void
{
$this->tytul = $nowyTytul;
$this->touch();
}
public function wyswietlDane(): void
{
echo "Artykuł: " . $this->tytul . "<br>";
echo "Utworzono: " . $this->formatTimestamp($this->getCreatedAt()) . "<br>";
echo "Zaktualizowano: " . $this->formatTimestamp($this->getUpdatedAt()) . "<br>";
}
}
class Komentarz
{
use Timestampable;
public string $tresc;
public function __construct(string $tresc)
{
$this->tresc = $tresc;
$this->touch();
}
public function wyswietlDane(): void
{
echo "Komentarz: " . $this->tresc . "<br>";
echo "Utworzono: " . $this->formatTimestamp($this->getCreatedAt()) . "<br>";
echo "Zaktualizowano: " . $this->formatTimestamp($this->getUpdatedAt()) . "<br>";
}
}
// Testowanie
$artykul = new Artykul("Pierwszy artykuł o PHP Traits");
$artykul->wyswietlDane();
echo "<hr>Czekamy sekundę...<br>";
sleep(1); // Pauza na 1 sekundę, aby zobaczyć różnicę w updatedAt
$artykul->aktualizujTytul("Zaktualizowany tytuł artykułu o PHP Traits");
$artykul->wyswietlDane();
echo "<hr>";
$komentarz = new Komentarz("Świetny artykuł!");
$komentarz->wyswietlDane();
/* Przykładowy wynik (daty będą aktualne):
Artykuł: Pierwszy artykuł o PHP Traits
Utworzono: 2023-10-27 10:30:00
Zaktualizowano: 2023-10-27 10:30:00
---
Czekamy sekundę...
Artykuł: Zaktualizowany tytuł artykułu o PHP Traits
Utworzono: 2023-10-27 10:30:00
Zaktualizowano: 2023-10-27 10:30:01
---
Komentarz: Świetny artykuł!
Utworzono: 2023-10-27 10:30:01
Zaktualizowano: 2023-10-27 10:30:01
*/
?>
Zadanie do samodzielnego wykonania
Stwórz cechę LicznikInstancji
, która będzie zliczać, ile instancji danej klasy (używającej tej cechy) zostało utworzonych.
- Cecha
LicznikInstancji
powinna:- Zawierać prywatną, statyczną właściwość
$licznik = 0;
. - Zawierać metodę (np. chronioną), która jest wywoływana w konstruktorze klasy używającej cechy, aby inkrementować
self::$licznik
. Pamiętaj, że cecha nie ma własnego konstruktora, który byłby automatycznie wywoływany. Klasa używająca cechy musi to obsłużyć. - Zawierać publiczną, statyczną metodę
getLiczbaInstancji(): int
zwracającą wartość licznika.
- Zawierać prywatną, statyczną właściwość
- Stwórz dwie różne klasy, np.
Produkt
iZamowienie
, które używają cechyLicznikInstancji
. Upewnij się, że konstruktory tych klas odpowiednio zarządzają licznikiem z cechy. - Przetestuj: utwórz kilka instancji każdej z klas i sprawdź, czy
NazwaKlasy::getLiczbaInstancji()
zwraca poprawne wartości. Zauważ, że licznik będzie wspólny dla wszystkich klas używających tej samej definicji cechy, jeśli właściwość statyczna jest zdefiniowana w samej cesze. Zastanów się, jak można by to zmodyfikować, aby każda klasa miała swój własny, niezależny licznik (podpowiedź: właściwość statyczna musiałaby być zdefiniowana w klasie, a cecha by na niej operowała, np. poprzez metody abstrakcyjne). Dla tego zadania wystarczy wersja z licznikiem współdzielonym przez cechę.
FAQ - Cechy (Traits)
Czy cecha może dziedziczyć po klasie lub implementować interfejs?
Nie, cechy nie mogą dziedziczyć po klasach (extends
) ani implementować interfejsów (implements
) w tradycyjny sposób. Są one mechanizmem do horyzontalnego współdzielenia kodu. Jednak klasa używająca cechy może normalnie dziedziczyć po innej klasie i implementować interfejsy.
Jaka jest różnica między cechą a klasą abstrakcyjną?
Klasa abstrakcyjna jest częścią hierarchii dziedziczenia (relacja "is-a") i może mieć stan oraz konstruktory; nie można jej instancjonować. Cecha służy do współdzielenia kodu (metod) między klasami, które nie muszą być w tej samej hierarchii (kompozycja zachowań). Klasa może dziedziczyć tylko po jednej klasie (abstrakcyjnej lub nie), ale może używać wielu cech.
Czy metody w cesze mogą być abstrakcyjne?
Tak, cecha może deklarować metody abstrakcyjne. Jeśli klasa używa takiej cechy, musi zaimplementować te metody abstrakcyjne, chyba że sama klasa jest również abstrakcyjna. Pozwala to cesze zdefiniować pewien kontrakt, który klasa używająca musi spełnić.
Czy cecha może mieć właściwości statyczne i metody statyczne?
Tak, cechy mogą zawierać zarówno właściwości statyczne, jak i metody statyczne. Działają one podobnie jak w klasach. Właściwości statyczne zdefiniowane w cesze będą współdzielone przez wszystkie klasy używające tej cechy, chyba że zostaną nadpisane w klasie.
Jak działa operator $this
wewnątrz cechy?
Wewnątrz metod cechy, $this
odnosi się do instancji klasy, która aktualnie używa tej cechy. Cecha "pożycza" kontekst $this
od klasy. Oznacza to, że metody cechy mogą uzyskiwać dostęp do innych metod i właściwości (publicznych i chronionych) tej klasy, a także do metod i właściwości z innych cech używanych przez tę samą klasę.
Czy można zmienić modyfikator dostępu metody z cechy w klasie jej używającej?
Tak, można zmienić modyfikator dostępu metody z cechy za pomocą słowa kluczowego as
w bloku use
. Na przykład: use MojaCecha { MojaCecha::metodaPubliczna as protected; }
uczyni metodę metodaPubliczna
z cechy chronioną w kontekście klasy. Można również tworzyć aliasy z nowymi modyfikatorami.
Czy cechy to to samo co "mixins" w innych językach?
Tak, cechy w PHP są bardzo podobne do koncepcji "mixins" znanej z niektórych innych języków programowania (np. Ruby, Scala). Służą one do włączania zestawów funkcjonalności do klas bez użycia dziedziczenia.