Wstęp
Osobiście z jedną rzeczy jakich mi brakowało w PHP był brak tzw. „Property hooks”, na szczęście to się zmieniło. Dzięki nowemu rozwiązaniu będzie przyjdzie łatwiej programować, a sam PHP staje się nieco bardziej nowoczesny. Oczywiście wiele drogi przed nim, ale kroki są czynione i jest coraz lepiej
Czym są Property hooks
Są to m.in. gettery i settery, chociaż niewykluczone że powstanie ich więcej. Takie hooki występują zawsze przy własności i są to po prostu metody. Hook Get – zawsze gdy pobierasz (zaczytujesz) wartość własności, a hook Set – zawsze gdy ustawiasz wartość własności i w tym artykule skupimy się tylko na tych dwóch.
Przykłady
Podstawy
Przed czasami PHP 8.4 gettery i settery w PHP trzeba było tworzyć jako osobne metody, które nie były hookami i pamiętać o tym, że istnieją i pilnować, aby były zawsze wykorzystywane. Tworzone były przez to prywatne własności, aby wymusić używanie tych metod.
class Rectangle
{
private int $x;
private int $y;
public function setX(int $x): self
{
$this->x = $x;
return $this;
}
public function getX(): int
{
return $this->x;
}
public function setY(int $y): self
{
$this->y = $y;
return $this;
}
public function getY(): int
{
return $this->y;
}
}
Oczywiście dla powyższego przykładu można pokusić się o wywalenie setterów i użyć konstruktora do przypisania wartości, ale załóżmy że chcemy mieć możliwość modyfikowania X i Y w locie.
Też do powyższego przykładu własności mogły by być publiczne ponieważ gettery i settery nie wykonują żadnej logiki, ale załóżmy, że będą, a nawet samemu można pokusić się o modyfikację tych metod.
Więc: mamy trochę długą klasę, która przechowuje wymiary prostokąta: klasa umożliwia ustawienie, zmianę i pobranie długości boków.
Napiszmy teraz to samo za pomocą Property Hooks
class Rectangle
{
public int $x
{
get => $this->x;
set(int $x) => $this->x = $x;
}
public int $y
{
get => $this->y;
set(int $y) => $this->y = $y;
}
}
Też zrobił nam się porządek i też pójdzie nam szybko znaleźć logikę danych hooków.
Oczywiście powyższe wykorzystanie wygląda na mega błahe, ale też daję prosty przykład, aby łatwiej załapać o co chodzi.
Prawie bym zapomniał: powyższe hooki wykorzystuje się po prostu używając własności:
$r = new Rectangle;
$r->x = 2;
$r->y = 3;
var_dump($r);
Wypluło mi taki wynik:
object(Rectangle)#1 (2) {
["x"]=>
int(2)
["y"]=>
int(3)
}
No to co? Gotów na bardziej poważne przykłady?
Logika w getterach i setterach
Jak wcześniej użyliśmy prostych metod strzałkowych do ustawienia hooków, tak teraz użyjemy nieco więcej logiki.
Oczywiście należy traktować hooki get i set jako metody, więc w zasadzie da się w nich kodzić jak w każdej innej metodzie. Pomęczmy jeszcze nasz prostokąt i dodajmy co nie co smaczków.
Z tego co udało mi się zapamiętać ze szkoły to prostokąty jako długość boku przyjmują liczbę dodatnią niezerową. Settery x i y mogą to sprawdzać i rzucać wyjątek w razie niepoprawnej wartości:
class Rectangle
{
public int $x
{
get => $this->x;
set(int $x) {
if($x <= 0) {
throw new Exception("Side length x must be greater than 0");
}
$this->x = $x;
}
}
public int $y
{
get => $this->y;
set(int $y) {
if($y <= 0) {
throw new Exception("Side length y must be greater than 0");
}
$this->y = $y;
}
}
}
$r = new Rectangle;
$r->x = -2;
$r->y = 3;
var_dump($r);
Proszę zauważyć że jako X ustawiłem celowo -2, aby sprawdzić działanie kodu. Oczywiście zadziałał poprawnie i zwrócił oto wyjątek:
Side length x must be greater than 0 in /app/tmp.php:10
Tak oto w locie mamy walidację własności ogarniętą w dosyć banalny sposób.
Jeszcze pasowałoby ogarnąć inne własności prostokąta. Bo tak że on ma tylko X i Y mało daje. Może by tak dodać jego pole?
Jak wiadomo z podstawówki, pole prostokąta liczy się X * Y. Znając te zaawansowane techniki liczenia spróbujmy je wdrożyć za pomocą gettera. Niech pole liczy się w locie przy pobieraniu własności. Co ciekawsze, ta własność nie będzie posiadała settera co uczyni ją Virtual Property (wirtualną własnością).
Tak będzie wyglądał kod z dodanym polem prostokąta:
class Rectangle
{
public int $x
{
get => $this->x;
set(int $x) {
if($x <= 0) {
throw new Exception("Side length x must be greater than 0");
}
$this->x = $x;
}
}
public int $y
{
get => $this->y;
set(int $y) {
if($y <= 0) {
throw new Exception("Side length y must be greater than 0");
}
$this->y = $y;
}
}
public int $area
{
get => $this->x * $this->y;
}
}
$r = new Rectangle;
$r->x = 8;
$r->y = 3;
var_dump($r->area);
Oczywiście powyższy kod wykazał wynik = 24.
Własność area
jest wyliczana za każdym razem, gdy pobieramy jej wartość, zatem próba ręcznego nadpisania np.
$r->area = 33;
nie powiedzie się. Błąd może nam na twarz nie wyskoczy, ale wynik zostanie na nowo przeliczony – to jest dobre zabezpieczenie przed niepożądanym nadpisaniem wartości własności.
Co dalej?
Dokumentacja na temat tego jak działają Property Hooks czyli póki co gettery i settery jest bardziej rozbudowana niż o to co dzisiaj przedstawiłem. Łaknących większej wiedzy zapraszam na https://www.php.net/manual/en/language.oop5.property-hooks.php gdzie m.in. opisane jest jak zadziałają hooki w przypadku dziedziczenia i jakie modyfikatory można używać przy nich.
Na koniec
Dziękuję za przeczytanie artykułu i mam nadzieję że spodobał się. W przyszłości planuję nadal opisywać nowości ze świata PHP, więc zachęcam do śledzenia bloga. Jeśli macie pomysły lub uwagi co do treści, prosiłbym o kontakt przez Discord – link do serwera znajduje się w nawigacji