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 »


Developerzy PHP, projektując “nową” implementację OOP (nową w cudzysłowiu, bo raz że to było już kilka lat temu, a dwa, że dotyczy tylko i wyłącznie samego PHP), dość mocno wzorowali się na Javie. Czy to dobrze czy to źle to kwestia gustu, jednak wynika z tego kilka znaczących drobiazgów. Jednym z nich jest jednodziedziczenie (czyli że jedna klasa w PHP może mieć tylko jedną klasę bazową). Od zawsze uważałem to za dużą niewygodę ;) Jak widać, nie tylko ja.

Padła propozycja dołączenia systemu “Cech” (ang. Traits) do PHP (podejrzewam że do wersji 6, ale to tylko moje zgadywanie – w każdym bądź razie są gotowe łatki m.in. do 5.2 i 5.3). Do czego służą Traitsy? W sumie… do obejście problemu jednodziedziczenia, czyli do wprowadzenia wielodziedziczenia.

Trzeba pamiętać, że ten mechanizm został podobno (nie sprawdzałem osobiście) sportowany także do Javy i C#, natomiast nie istnieje jeszcze ofiacjalna wersja dla PHP. Sam “wygląd” kodu, słowa kluczowe etc nie są jeszcze ustalone, ale to tylko szczegóły.

Szczegóły są dostępne w oficjalnym RFC: stefan-marr.de/artikel/rfc-traits-for-php.html. Ja tylko powiem, że da się często obejść niedogodność wynikajacą z braku wielodziedziczenia, ale problem leży właśnie w konieczności obchodzenia braków w języku (tak, wiem, to akurat jedna z typowych wojen wyższości jednych świąt nad drugimi ;) ale to akurat moje zdanie ;) ). Prostszym wyjściem byłoby wprowadzenie po prostu możliwości posiadania kilku klas bazowych, ale w to nie wnikam. Ja ogólnie jestem na tak.


Co prawda, od około roku nie używam za dużo PHP, ale generalnie śledzę co się w tym języku dzieje. Podobają mi się nawet zmiany zaplanowane na wersję 5.3:

  • dodanie obsługi przestrzeni nazw (namespaces)
  • usunięcie w końcu safe_mode, register_globals i magic_quotes – szczególnie tych ostatnich nie trawię…
  • dodanie poziomu raportowania błędów E_DEPRECATED – będą tym, jak się domyślam, oznaczane funkcje/moduły/klasy mające być wycofane w niedługim czasie z core języka (jak np. moduł ereg)
  • nowa magiczna metoda: _callStatic, wywoływana w momencie gdy zostanie użyta metoda statyczna nie istniejąca w danej klasie

To nie są jeszcze końcowe ustalenia, wszystko może się jeszcze zmienić, ale to co powyżej prawdopodobnie zostanie wdrożone.

Jednak ja, jak i spore stadko innych osób używających PHP, oczekuje jednej, bardzo ważnej zmiany: uporządkowania w końcu core języka: ujednolicenie nazewnictwa funkcji, nauczenie funkcji wbudowanych wyjątków etc. To jest rzecz najbardziej potrzebna na tą chwilę, gdyż sądzę że bez tego przyszłość “pehapa” pójdzie elegancko po równi pochyłej w dół…


Jakiś czas temu zacząłem się bawić Blip!em. Wstawiłem sobie nawet na główną stronę myGeeBloga wklejkę z ostatnim statusem ;) Ichnich wklejki sa jak dla mnie kiepskie (flasha nie lubię, a wersja jsowa pozwala na pobranie tylko ostatniego statusu), więc pomyślałem o pluginie do WordPressa. Żeby móc to urzeczywistnić, potrzeba mi jakiejś klasy do obsługi tego – a z pomocą udostępnionego API zrobiłem sobie taką. Na razie wersja pre-alpha ;)

do podejrzenia:
repo.urzenia.net/files/blipapi.phps
do pobrania:
http://repo.urzenia.net/files/get.php?f=blipapi.php

O feedback poproszę za pomoca formularza: urzenia.net/email lub na liście blip-api: groups.google.com/group/blip-devel/browse_thread/thread/3096e1724d036f55.

UPDATE: wprowadziłem kilka zmian w klasie blipapi, częściowo kosmetycznych, częściowo optymalizacyjnych. Do pobrania pod wymienionymi wyżej adresami :)


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).


Stanąłem dziś przed takim dylematem: chciałem utworzyć listę obiektów (każdy obiekt to konkretny wpis w bazie danych). No OK, samo utworzenie listy to niewielki problem. Problemem jest to, że w całej aplikacji używam dość zaawansowanych “ułatwiaczy”: przeładowywanie __isset(), __unset(), __set() i __get(). Np. mam obiekt $p, instancję klasy Post. Ma on różne własności, np tytuł (Post::$title), treść (Post::$body). Dostęp do nich jest za pomocą przeładowanych metod __set() i __get(), co pozwala mi na zautomatyzowanie kilku rzeczy – jak na przykład niewidoczną dla programisty zamianę znaków nowej linii na HTMLowe znaczniki <br />. Ale ja nie o tym miałem…

Chciałem zachować naturalność rozwiązań także w klasie agregującej obiekty Post. Czyli potraktować de facto obiektu agregatu (który ma kilka zadań, jak np utworzenie tej listy obiektów) jako tablicy. Tworząc instancję klasy, chcę mu zapodać: utwórz listę wpisów z kategorii o ID = 2, obiektów ma być 30. No i z tego korzystać. Mogę oczywiście dać sobie dostęp do jednej z własności tegoż obiektu-agregatu, która będzie tablicą i zawierać w sobie będzie wszystkie pobrane wpisy. Ale czy wspominałem coś o naturalności rozwiązań? :)

PHP 5 dostarcza nam coś, co nazywa się SPL, czyli Standard PHP Library. Jedną z klas (a konkretnie intefejsów) modułu SPL jest ArrayAccess.

Aby tego użyć, należy zadeklarować klasę jako implementującą z tego interfejsu:

class Aggregate implements ArrayAccess {}

A w niej trzeba zaimplementować 4 metody: offsetExists($offset), offsetGet($offset), offsetSet($offset, $value), offsetUnset($offset).

Przykład tego, jak powinna wyglądać cała klasa:

class Aggregate implements ArrayAccess
{
    private $data = array();

    public function __construct($array=null)
    {
        if (!is_null($array)) {
            $this->data = $array;
        }
    }

    public function offsetExists($offset)
    {
        return array_key_exists($offset, $this->data);
    }

    public function offsetGet($offset)
    {
        return $this->data[$offset];
    }

    public function offsetSet($offset, $value)
    {
        $this->data[$offset] = $value;
    }

    public function offsetUnset($offset)
    {
        unset($this->data[$offset]);
    }
}

Do tego przydałby się prosty sposób na sprawdzenie liczby obiektów które są na liście (kazaliśmy pobrać 30, ale to jest ilość maksymalna – w bazie danych może być ich mniej). Wykonamy to poprzez interfejs Countable i metodę count(). Nagłówek naszej klasy zmieniamy na taki:

class Aggregate implements ArrayAccess, Countable

I dodajemy metodę count():

    public function count()
    {
        return count($this->data);
    }

Żeby poczuć się jakbyśmy pracowali z prawdziwą tablicą, brakuje nam jeszcze możliwości iteracji po poszczególnych elementach tablicy. Do tego trzeba zaimplementować interfejs Iterator. Do listy implementowanych interfejsów dodajemy Iterator:

class Aggregate implements ArrayAccess, Countable, Iterator

I implementujemy jego metody:

    public function current()
    {
        return current($this->data);
    }

    public function key()
    {
        return key($this->data);
    }

    public function next()
    {
        return next($this->data);
    }

    public function rewind()
    {
        return reset($this->data);
    }

    public function valid()
    {
        return current($this->data);
    }

Od tego momentu możemy każdą instancję klasy Aggregate traktować jak tablicę. Pobieramy dowolny element tak jak z normalnej tablicy: $aggregate[2], tak samo zmieniać jego zawartosć: $aggregate[2] = 'something';, iterować za pomocą foreach() {}, usuwać elementy za pomocą unset() etc. Jedyne czego nie osiągnęłem, ale też specjalnie nie próbowałem, to dodawać elementów za pomocą notacji []. Ale myślę że w większości zastosowań nie jest to potrzebne.

Cała, działająca klasa wraz z przykładem użycia: urzenia.net/wp-content/class_aggregate.php.

UPDATE:
Udoskonalona wersja klasy, odporna na błąd z metody valid (), a także pozwalająca na dodawanie kolejnych elementów tablicy PHPowym idiomem $aggregate[] = 'wartosc' znajduje się pod adresem: urzenia.net/wp-content/class_aggregate_new.php.


Mało kto wie, że funkcji z pakietu ob_* można użyć na przykład do konwersji z jednego kodowania do drugiego (np. strona de facto zapisana w ISO-8859-2 może pójść do przeglądarki jako UTF-8), albo do automatycznej kompresji za pomocą gzip, o ile przeglądarka wspiera tą metodę.

Do zmiany strony kodowej, oprócz funkcji z pakietu ob_* potrzebujemy rozszerzenia albo iconv, albo mb. Najpierw trzeba zdefiniować, z jakiego kodowania chcemy “tłumaczyć”:

iconv_set_encoding('internal_encoding', $enc_from); //jeśli korzystamy z iconv
mb_internal_encoding($enc_from); //jeśli korzystamy z mbstring

gdzie $enc_from może być np. “ISO-8859-2″ (lista obsługiwanych kodowań na stronie PHP (dla mbstring) lub na stronie biblioteki iconv (dla iconv).

Nastepnym krokiem jest zdefiniowanie kodowania wyjściowego:

iconv_set_encoding('output_encoding', $enc_to); //dla iconv
mb_http_output($enc_to); //dla mbstring

gdzie $enc_to w naszym przykładzie to “UTF-8″.

Teraz pozostaje tylko na początku strony wstawić instrukcję uruchamiającą buforowanie wyjścia:

ob_start('ob_iconv_handler'); //w przypadku iconv
ob_start('mb_output_handler'); //gdy używamy mbstring

I już możemy się cieszyć zmienionym kodowaniem danej strony :)

Uruchomienie kompresji jest równie proste, trzeba tylko pamiętać aby był załadowany moduł zlib. Samo uruchomienie kompresji to podanie funkcji ob_start() odpowiedniego parametru:

ob_start('ob_gzhandler');

Za pomocą instrukcji ini_set() możemy ustawić także stopień kompresji:

ini_set('zlib.compresss_level', $comp_level);

gdzie $comp_level jest to liczba całkowita z zakresu od 0 do 9 (domyślnie 6).

Pozostaje jeszcze tylko jeden problem: jak połączyć razem konwersję z jednego kodowania na drugie wraz z kompresją? Proszę:

ob_start(array('ob_gzhandler', 'ob_iconv_handler'));

Ważna powyżej jest kolejność parametrów podawanych w tablicy: gdy podasz je w odwrotnej kolejności, nie będzie działać kompresja.


Jak wiadomo, przestrzenie nazw w PHP nie istnieją. Czasem ich brakuje, czasem nie robi różnicy… Ale jest dość prosty sposób na emulację tego braku.

Definiujemy sobie klasę o nazwie takiej, jak pożądana przestrzeń nazw, na przykład Path. W niej definiujemy sobie jakieś metody, np. join(), split() etc. Metody te definiujemy jako statyczne (static- nie chcemy musieć inicjalizować obiektu i korzystać z jego instancji, tylko chcemy mieć dostęp do samych metod. Klasę warto zdefiniować jako abstrakcyjną (abstract) – wtedy nie będziemy mieć możliwości utworzyć instancji jej obiektu. Efekt końcowy:

abstract class Path {
    public static function join()
    {
        // tresc funkcji
    }
    public static function split()
    {
        //tresc funkcji
    }
}

A korzystamy z niej:

Path::join('a', 'b');
Path::split('a/b/c');

Voila! :)

Jedynym problemem jest tutaj brak możliwości zaimportowania danej funkcji do ‘zwykłej’ przestrzeni nazw, czyli czegoś co w C++ robi dyrektywa using. Ale myślę, że nie po to chcemy stworzyć własną przestrzeń nazw, żeby z niej później wychodzić :)


Strona 1 z 2