Jakiś czas temu napisałem artykuł o setterach i getterach w PHP5. Może warto poruszyć podobny temat dotyczący Pythona? :)

Przede wszystkim, model obiektowy Pythona jest zupełnie inny niż PHP. Nie będę go tu omawiał, ponieważ to temat na dość obszerną książkę :) Generalnie obiektówka Pythona jest pełniejsza, ma większe możliwości przeciążania zarówno metod, jak i operatorów (których w PHP przeciążyć nie można).

Przejdź do reszty tego wpisu »


W PHP5 został wprowadzony nowy, zupełnie odmienny, i znacznie pełniejszy model obiektowy (w porównaniu do PHP4). W modelu tym znalazło się miejsce dla kilku magicznych metod, które chciałbym tutaj omówić.

Dwoma z najprzyjemniejszych metod magicznych w PHP5 są __set() i __get(), które pozwalają na przechwytywanie odwołań do nieistniejących (lub niedostępnych z powodu akcesorów) zmiennych obiektu.

Wcześniej, przed wersją piątą PHP, jedynym sposobem na pobranie/ustawienie właściwości w pewien uniwersalny i umożliwiający rozwój sposób było stosowanie znanych m.in. z Javy setterów/getterów, które zwyczajowo mają postać:

class Test1 {
    var $elem1;
    function getElem1();
    function setElem1($value);
}

Używałem tej samej metody, jako że innej nie było. Ale magiczne właściwości __set() i __get() załatwiają dla nas masę pustego klepania kodu. Sposób użycia:

class Test1 {
    private $elem1;
    function __get($var) {
        return $this->$var;
    }
    function __set($var, $value) {
        $this->$var = $value;
    }
}

Oczywiście powyżej można zawrzeć także sprawdzanie czy dana właściwość istnieje w obiekcie, ale to już osobna kwestia. Co nam daje powyższy zapis? To, że dla drugiej właściwości, w metodzie “starej”, trzeba by utworzyć kolejne metody setXXX() i getXXX(), natomiast przy wykorzystaniu metod magicznych pozostaje nam dodać tylko kolejne pole do definicji klasy:

[...]
    private $elem1;
    private $elem2;
[...]

Ktoś może powiedzieć: ale ręczne tworzenie setterów i getterów daje nam możliwość bardziej zaawansowanej walidacji poprawności wprowadzanych danych etc. Owszem, zawsze do bardziej zaawansowanej walidacji potrzebna będzie osobna metoda (no dobrze, można to ominąć kosztem zaciemnienia kodu, ale to nie jest dobre rozwiązanie…), ale to nie powód żeby zrezygnować z magii:

class Test2 {
	private $integer;
	private $string;

	public function __get($var) {
		return $this->$var;
	}
	public function __set($var, $value) {
		if (method_exists($this, '_check__'.$var)) {
			if (call_user_func_array(array($this, '_check__'.$var), $value)) {
				$this->$var = $value;
			} else {
				throw new Exception('zła wartość właściwości '. $var);
			}
		} else {
			$this->$var = $value;
		}
	}
	private function _check__integer($data) {
		return is_int($data);
	}
	private function _check__string($data) {
		return is_string($data);
	}
}

Powyższy kod pozwala na dodawanie dowolnej ilości właściwości klasy, a po dodaniu funkcji sprawdzającej także walidację tejże właściwości. Trzeba tylko pamiętać o właściwej nazwie funkcji sprawdzającej, ale to samo dotyczy tradycyjnych getterów :).

Inną magiczną funkcją w PHP5 jest __call(), która pełni podobną funkcję jak __set() i __get(), tylko że dotyczy metod obiektu: przechwytuje odwołania do nieistniejących metod obiektu (niestety, nie dotyczy to metod o zastrzeżonym dostępie). Przykład zastosowania:

class Test3 {
	public $data;
	public function __call($method, $args) {
		if (preg_match('/^get([A-Z][a-z]+)$/', $method, $matches)) {
			settype($this->data, strtolower($matches[1]));
			return $this->data;
		}
	}
}

Przykład powyżej pozwala na pobranie właściwości o typie takim jaką metodą ją wywołamy. Dla wywołania: $test3->getInteger() otrzymamy wartość o typie integer, dla $test3->getString() - ciąg znaków etc.

Jeśli korzystamy z magicznych __set() i __get() jako funkcji dostępowych do właściwości, trzeba czasem sprawdzać czy dana właściwość jest ustawiona czy też jej nie ma. Nie pomoże tutaj ani property_exists() (oczywiście zakładając tutaj że nasze właściwości obiektu mają ustawiony akcesor private lub protected), ani nawet get_object_vars(). Jedyną możliwością jest dodanie metody __isset(), która będzie zwracała odpowiednio prawdę lub fałsz. Dzięki niej będzie możliwe wykorzystanie wbudowanej instrukcji isset() na właściwości klasy, żeby można było stwierdzić czy dana właściwość istnieje:

class Test4 {
	private $data;
	public function __isset($var) {
		return (property_exists($this, $var));
	}
}

Wystarczy teraz użyć:

isset($test4->data)

żeby można było sprawdzić istnienie właściwości.

Skoro istnieje możliwość sprawdzania istnienia właściwości, powinna być też możliwość jej usunięcia - do tego służy metoda __unset(), której przykład można zobaczyć poniżej:

class Test5 {
	private $_data = array();
	public function __set($var, $value) {
		$this->_data[$var] = $value;
	}
	public function __get($var) {
		return $this->_data[$var];
	}
	public function __isset($var) {
		return array_key_exists($var, $this->_data);
	}
	public function __unset($var) {
		unset($this->_data[$var]);
	}
}

Dzięki zastosowaniu __unset() stało się możliwe usuwanie poszczególnych właściwości obiektu za pomocą wbudowanej funkcji unset():

$o = new Test5();
$o->a = 1;
$o->b = 2;
$o->c = 3;

echo 'właściwość "a": '; var_dump (isset($o->a)); echo '<br />';
echo 'właściwość "b": '; var_dump (isset($o->b)); echo '<br />';
echo 'właściwość "c": '; var_dump (isset($o->c)); echo '<br />';

echo '<br />usuwamy a:<br />';
unset($o->a);

echo 'właściwość "a": '; var_dump (isset($o->a)); echo '<br />';
echo 'właściwość "b": '; var_dump (isset($o->b)); echo '<br />';
echo 'właściwość "c": '; var_dump (isset($o->c)); echo '<br />';

echo '<br />usuwamy c:<br />';
unset($o->c);

echo 'właściwość "a": '; var_dump (isset($o->a)); echo '<br />';
echo 'właściwość "b": '; var_dump (isset($o->b)); echo '<br />';
echo 'właściwość "c": '; var_dump (isset($o->c)); echo '<br />';

Ostatnią z opisywanych przeze mnie magicznych funkcji jest użyteczne szczególnie przy debugowaniu metoda __toString(). Pozwala ona na zdefiniowanie ciągu znaków jaki zostanie wyświetlony lub zwrócony podczas rzutowania naszego obiektu na typ string, czyli np. gdy zechcemy wyświetlić nasz obiekt. Przykład:

class Test6 {
	public function __toString() {
		return 'To moja klasa ' . get_class($this);
	}
}

Gdy teraz chcemy sprawdzić instancję jakiej klasy mamy w zmiennej, wystarczy wyprintować ją za pomocą print() lub echo():

$test6 = new Test6();
echo $test6;

To nie wszystkie magiczne właściwości jakich dostarcza nam PHP5, ale pozostałych nie będę tu omawiał ze względu na rozwlekłość artykułu, a także na nieco bardziej zaawansowaną tematykę. W powyższych przykładach brakuje oczywiście dokładnego sprawdzania błędów i innych udoskonaleń, ale w końcu nie o tym był ten artykuł :)

PS. Kilka przykładów użycia magicznych właściwości znajduje się też w innym moim wpisie: Obiekt jako tablica (wprowadzenie do SPL).