Translate: 
EnglishFrenchGermanItalianPolishPortugueseRussianSpanish

Silne typowanie danych w PHP, część I

Logo PHPW 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: , , , ,

Dodaj odpowiedź