Gettery i settery w PHP

Najpopularniejszym paradygmatem programowania jest programowanie zorientowane obiektowo. Obiekt posiada jakieś właściwości, które [IMO] najczęściej powinny być prywatne. Warto by było móc się do nich jakoś odwoływać, czy to podczas zapisywania danych, czy też w celu odczytu. Do tego bardzo przydatne okazać się mogą funkcje popularnie zwane getter („akcesor” – pobierająca dane) bądź setter („mutator” – ustawiająca).

W tym wpisie postaram się pokazać kilka możliwych podejść do tego zagadnienia.

Po co mi getter / setter?

Pewna szkoła programistyczna mówi, że żadne właściwości (zmienne obiektu) nie powinny być publiczne.  Jestem wyznawcą tej szkoły (choć pamiętajmy też, że nie każde pole prywatne musi posiadać g/s). Spróbuję pokazać przykład, w którym takie podejście pozwala oszczędzić sporo pracy.

Załóżmy, że mamy klasę `Kwadrat’:

[php]class Kwadrat
{
public $pole;
public $bok;
public $obwod;
}[/php]

Kiedy chcielibyśmy stworzyć instancję tej klasy:

[php]$kwadrat = new Kwadrat();
$kwadrat->bok = 10;
echo $kwadrat->pole; // brak wartości[/php]

Czyż nie lepiej, aby ustawienie nowej wartości dla właściwości `bok’ automatycznie ustawiało pozostałe pola, których wartość powinna zostać zaktualizowana?

Jak tego dokonać? Zamiast publicznych właściwości używać prywatnych i odpowiednich funkcji ustawiających i pobierających dane.

getPole, setPole – a’la Java

Jest to metoda znana z Javy. Dla każdego pola (choć nie tylko, zaraz pokażę o co mi chodzi) tworzymy dwie metody: `set{NazwaPola}’ oraz `get{NazwaPola}’. Będę zmieniał kod z poprzedniego podpunktu.

Krok 1: pola prywatne i publiczne metody

[php]class Kwadrat
{
private $pole;
private $bok;
private $obwod;

public function setPole($p) { $this->pole = $p; }
public function setBok($b) { $this->bok = $b; }
public function setObwod($o) { $this->obwod = $o; }
public function getPole() { return $this->pole; }
public function getBok() { return $this->bok; }
public function getObwod() { return $this->obwod; }
}[/php]

Póki co takie rozwiązanie nie daje zbyt wielu korzyści w porównaniu z poprzednim kodem. Nadal robiąc tak:

[php]$kwadrat = new Kwadrat();
$kwadrat->setBok(10);
echo $kwadrat->getPole(); // brak wartości[/php]

Krok 2: kaskadowa aktualizacja wartości

Ustawiając nową wartość dla jednej właściwości, automatycznie zostaną ustawione wartości dla pozostałych.

[php]class Kwadrat
{
… // pola prywatne takie same
public function setPole($p)
{
$this->pole = $p;
$this->bok = sqrt($p);
$this->obwod = $p/4;
}
public function setBok($b)
{
$this->bok = $b;
$this->obwod = $b*4;
$this->pole = $b*$b;
}
public function setObwod($o)
{
$this->obwod = $o;
$this->bok = $o/4;
$this->pole = $this->bok*$this->bok;
}
…// settery takie same
}[/php]

Dzięki takiemu zabiegowi, mając coś takiego:

[php]$kwadrat = new Kwadrat();
$kwadrat->setBok(10);
echo $kwadrat->getPole(); // 100[/php]

Otrzymamy już poprawny wynik. Ale to jeszcze można polepszyć 🙂

Krok 3: usunięcie redundancji

Tak naprawdę w poprzedniej klasie mamy 3 właściwości, które wynikają jedna z drugiej. Za każdym razem i tak obliczmy pozostałe. Czy nie można zrobić tego lepiej? Zaproponuje rozwiązanie, które w niektórych przypadkach może być szybsze.

[php]class Kwadrat
{
private $bok;

public function setPole( $pole )
{
$this->bok = sqrt($pole);
}
public function setBok( $bok )
{
$this->bok = $bok;
}
public function setObwod( $obwod )
{
$this->bok = $obwod / 4;
}
public function getPole()
{
return $this->bok*$this->bok;
}
public function getBok()
{
return $this->bok;
}
public function getObwod()
{
return $this->bok*4;
}
}[/php]

Dzięki takiemu rozwiązaniu unikamy niepotrzebnych obliczeń przy ustawianiu właściwości. Można by dodać ponownie usunięte prywatne właściwości i w getterze wyliczając je przypisywać do odpowiedniej zmiennej. To już jest kwestia tego w jakich warunkach byłby ten obiekt wykorzystywany. Być może pozwalałoby to na przyspieszenie pracy. TODO: testy wydajnościowe 😉

Dwie funkcje dla wszystkich właściwości + zbiornik na dane

Inny sposobem, oszczędzającym miejsce, choć mniej czytelnym dla programisty, który odziedziczy Twój kod jest jeden globalny setter i jeden globalny getter. Ma to zdecydowanie jedna dużą zaletę – pozwala na zmniejszenie ilości kodu. Ma też wadę – kod jest IMO mniej czytelny.

Od zmniejszania ilości kodu, jest odpowiednio przeprowadzona refaktoryzacja. Jeśli chcemy, aby nasze programy zajmowały mniej linii, zawsze możemy napisać je nie używać enterów :). Jeśli kod staje się za długi, to znaczy, że wymaga poważniejszych zmian, niż tylko skrócenie nazw zmiennych. Ale to jest temat na osobny artykuł ;).

Takie rozwiązanie jest szczególnie przydatne w klasach „konfiguracyjnych” – zbiornikach na dane:

[php]class Config
{
private $params = array();
public function get( $k )
{
return $this->params[$k];
}
public function set($key, $value)
{
$this->params[$key] = $value;
}
}[/php]

Dla czytelności przykładu zrezygnowałem z jakiejkolwiek walidacji. Warto choćby sprawdzić, czy istnieje oczekiwany klucz w `get’ i jeśli nie, rzucić jakiś zgrabny wyjątek.

Wykorzystanie kodu:

[php]$conf = new Config();
$conf->set(‚pole’, 12);
$conf->set(‚bazadanych’, new bazaDanych());

echo $conf->get(‚pole’);
$bd = $conf->get(‚bazadanych’);[/php]

Automagia

PHP w swoich nowszych (5 i nowszych) wersjach posiada także sporo magii. Przykładami na takie automagiczne metody są `__get’ i `__set’:

[php]class MagicConfig
{
private $params = array();

public function __set( $key, $value )
{
$this->params[$key] = $value;
}
public function __get( $key )
{
return $this->params[$key];
}
}[/php]

Sam kod zmienił się bardzo nieznacznie. Można go jednak „czytelniej” używać:

[php]$conf = new MagicConfig();
$conf->pole = 12;
$conf->bazadanych = new bazadanych();

echo $conf->pole;[/php]

Jak widać na powyższym listingu, kodu jest trochę mniej. I być może sprawia on wrażenie bardziej czytelnego. Ja jednak jestem zwolennikiem braku magi [takiego typu] w kodzie. Nigdy ten rodzaj g/s mnie do siebie nie przekonał.

Podobnie jak wcześniejszy przykład, może to być dobre rozwiązanie dla „worków z danymi” . Jeśli jednak chcemy uniemożliwić nadpisania z zewnątrz jakiejś wartości pojawiają się nieciekawe warunki, walidacje itp. Moim zdaniem lepiej i przejrzyściej jest ręcznie zdefiniować jakie właściwości klasy w jaki sposób mają się zmieniać oraz jak zmiana ta ma wpływać na cały stan obiektu (czytaj g/s a’la Java).

Jedna funkcja dla get i set

Jest to moje „autorskie” rozwiązanie. Piszę w cudzysłowie, gdyż nie jest to mój patent, czy też na pewno nie ja pierwszy w programowaniu takie coś wymyśliłem. Jest to używane powszechnie w jQuery oraz AFAIK przez pearlowców. Sam wykorzystuje to też w swoich skryptach JS [bliźniaczy przykład w JS].

Skoro tam się to sprawdza, dlaczego nie wykorzystać tego w PHP?

Idea

Chodzi o to, aby zamiast dwóch metod – jednej pobierającej, drugiej ustawiającej dane, stworzyć jedną metodę, która będzie zachowywać się odpowiednio do potrzeb. Czynnikiem sterującym będzie tu przekazanie [bądź nie] parametru do metody.

[php]<?php
class Kwadrat
{
private $bok;

public function pole()
{
// przekazano argument => setter
if (1 == func_num_args())
{
$this->bok = sqrt(func_get_arg(0));
return;
}
// getter
return $this->bok*$this->bok;
}

public function bok()
{
if (1 == func_num_args())
{
$this->bok = func_get_arg(0);
return;
}
return $this->bok;
}

public function obwod()
{
if (1 == func_num_args())
{
$this->bok = func_get_arg(0) / 4;
return;
}
return $this->bok*4;
}
}

$kwadrat = new Kwadrat();
$kwadrat->pole(10);
echo $kwadrat->obwod(); // 12.64
$kwadrat->bok(2);
echo $kwadrat->pole(); // 4[/php]

W kodzie tym zastosowałem dwie funkcje:

  • func_num_args – zwraca liczbę przekazanych do funkcji argumentów
  • func_get_arg – zwraca argument przekazany do funkcji. Parametrem jest indeks argumentu. 0 oznacza „pierwszy przekazany do funkcji argument

Takie podejście podoba mi się najbardziej. Dodatkowo można tu w prosty sposób zrobić wzorzec łańcuchowy, co pokazałem kiedyś na przykładzie JS.

Przykład użycia tego rozwiązania – JSCacher.

2 odpowiedzi do “Gettery i settery w PHP”

  1. Myślę, że przykład z kwadratem pokazuje po co.

    Zmienna to zmienna. Settter i getter zwraca pewien stan obiektu, a niekoniecznie wartość zmiennej.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *