W wielu dyskusjach porównawczych pomiędzy programistami różnych języków programowania często pojawia się zarzut w stylu „a u was w PHP nie ma silnego typowania danych!”.
Czy aby na pewno?
Okazuje się jednak, że w PHP można zaimplementować mechanizm silnego typowania danych znany z takich języków jak C, C++, C#, Java…
Rzeczy które znamy…
W języku PHP od czasu pojawienia się wersji 5.0 obsługiwane jest mechanizm tzw. type hintów, np:
<?php // An example class class MyClass { public $var = 'Hello World'; } /** * A test function * * First parameter must be an object of type MyClass */ function MyFunction (MyClass $foo) { echo $foo->var; } // Works $myclass = new MyClass; MyFunction($myclass); ?> |
W powyższym przykładzie przesłanie do funkcji MyFunction czegokolwiek, co nie jest instancją klasy MyClass wyrzuci błąd typu CATCHABLE_FATAL_ERROR i przerwie wykonywanie programu.
To krok w dobrą stronę, lecz ma swoje wady: wymusza wyłącznie typy obiektów lub tablice. Nie możemy nakazać w tym miejscu, by parametrem wejściowym był tylko string, integer, lub inny typ prosty. I na to jest jednak wyjście, wystarczy tylko… przewinąć dokumentację PHP do komentarzy, by zobaczyć gotowy skrypt działający z typami prostymi.
Jak to działa?
Mechanizm jest w swym założeniu dosyć prosty. Jako że język PHP zgłasza w przypadku obiektowych type hintów błędy typu CATCHABLE_FATAL_ERROR, możemy je przechwycić własnym handlerem obsługującym błędy (funkcja set_error_handler()).
Teraz wystarczy już tylko dodać, że type hinty mogą wskazywać na nazwy klas, które nie zostały wcześniej zadeklarowane w kodzie. Nic nie stoi więc na przeszkodzie by funkcje i metody deklarować w następujący sposób:
function test(string $text, int $offset, int $length) { return substr($text, $offset, $length); } |
W takim przypadku po stronie własnego error handlera, wystarczy tylko zbadać czy typ danych w zgłoszonym błędzie zgadza się z oczekiwanym. Najprościej można to osiągnąć czytając treść zgłoszonego błędu:
Catchable fatal error: Argument 1 passed to test() must be an instance of string, string given, called in C:\lotos\public_html\example.php on line 8 and defined in C:\lotos\public_html\example.php on line 3
Jak widać PHP sam podpowiada nam z czym mamy do czynienia. Wyrażenie regularne odpowiedzialne za pobranie typu danych z komunikatu o błędzie wygląda następująco:
preg_match('/^Argument \d+ passed to (?<namespace>(([a-zA-Z_]{1}[a-zA-Z0-9_]+)\\\)+)?[:a-zA-Z0-9_]+\(\) must be an instance of (?<hintName>[a-zA-Z0-9_\\\]+), (?<typeName>[a-z]+) given/AUD', $errorMessage, $matches) |
W error handlerze wystarczy tylko przyrównać ze sobą te dwie wartości, i w przypadku zgodności typów zwrócić wartość FALSE oznajmującą, że błędu jednak nie ma.
Problemy
Głównym problemem tego rozwiązania jest jego wydajność. Gotowe error handlery, które możemy znaleźć w komentarzach dokumentacji PHP potrafią spowolnić wywołanie funkcji z type hintami nawet 40-to krotnie!.
Dopiero zastosowanie dodatkowych optymalizacji zmniejsza te opóźnienia do wielkości rzędu 4-8x. W tym przypadku jest to wielkość niezauważalna dla funkcji które nie są wykonywane więcej niż kilkanaście/-set razy per jedno wywołanie całego skryptu. Nie jest to wtedy żaden problem, szczególnie że częściej wykorzystywane funkcje i tak musiałyby najprawdopodobniej zostać zoptymalizowane do granic możliwości (czyli m.n. nie używać type hintów).
Skąd pobrać?
Także i w tym przypadku przygotowałem gotowy skrypt do wykorzystania. Jest to wysoce zoptymalizowana wersja error handlera, która oprócz wbudowanych optymalizacji posiada także wsparcie dla namespace’ów języka PHP 5.3 oraz możliwość bezproblemowej współpracy z ewentualnymi error handlerami wbudowanymi w pozostałe aplikacje użytkownika.
Error handler można pobrać z serwisu softpedia (bez logowania), lub phpclasses (wymaga zalogowania)
Czego nam jeszcze brakuje?
Silne typowanie danych to nie tylko sposób ich deklaracji w metodach i funkcjach, to także pilnowanie, by dane te nie zmieniły swojego typu w trakcie wykonywania kodu, już po zadeklarowaniu typu zmiennej. Mechanizm type hintów tego zagadnienia nie porusza, jednak i to można uzyskać bez problemu innymi środkami, np po zaimplementowaniu w PHP mechanizmu autoboxingu wprowadzonego przez język C#.
Jak to wykonać? To już temat, o którym napisałem w artykule Silne typowanie danych w PHP, część II: autoboxing oraz niezniszczalne obiekty…
Tagi: autoboxing, Java, silne typowanie danych, strong data typing, type hinting