Pliki konfiguracyjne i aplikacje w PHP
Ostatnio po raz kolejny mierzę się z problemem przechowywania informacji konfiguracyjnych dla aplikacji napisanych w języku PHP. Jak to zrobić? Czego użyć? Co będzie najprostsze i najbardziej wygodne i przede wszystkim dla mnie…. co będzie najszybsze? Oczywiście chciałbym znaleźć złoty środek, który łączy wszystkie te cechy w jednym. Czy tak się da? Zobaczymy… ale najpierw mały wstęp:
Nie wszystko złoto co się świeci
PHP to taki dziwny język. Przez jednych kochany przez innych nienawidzony. Jednak jest w nim coś takiego co sprawia, że mnie cały czas zadziwia i ciągle uczę się o nim czegoś nowego. Ciekawe jest to, że pewne rzeczy, które wydawałoby się, że powinny działać lepiej/szybciej od innych zachowują się zupełnie odwrotnie.
Osoby, które mają jakieś pojęcie o językach takich jak np C i starają się pisać kod w sposób wydajny i pamięciooszczędny mają wyrobione pewne reguły postępowania, które pomagają ten cel przybliżyć takie jak:
- wskaźniki do obiektów
- zmienne właściwego typu
- zarządzanie pamięcią
- dyrektywa define
- itd
Co ciekawe… z racji tego, że język PHP jest składniowo bardzo podobny do języka C++ a ponadto występują w nim podobne konstrukcje to naturalnym odruchem wydaje się chęć ich używania. Niestety tutaj możemy wpaść w pułapkę. Przede wszystkim trzeba sobie za każdym razem przypomnieć, że aplikacja w języku PHP i jego specyfika jest zupełnie inna niż ta w języku C. Weźmy na tapetę „define”. W jeżyku C taka konstrukcja #define PI 3.14 pozwala „nazwać” pewną stałą wartość np liczbę 3.14 nazwać PI i zamiast pisać w kodzie 3.14 używamy PI. Kompilator zamieni wszystkie wystąpienia PI na liczbę 3.14. Dzięki temu uzyskamy bardzo wydajny kod, jednocześnie mając wszystko ładnie poukładane i pod kontrolą. Oczywiście można tworzyć o wiele bardziej skomplikowane rzeczy ale nie o to teraz chodzi. Wydawałoby się, że to samo można zastosować do PHP. Przecież tutaj tez mamy konstrukcję define(„PI”, 3.14); Jednakże tutaj pojawia się problem. Trzeba wziąć pod uwagę, że w aplikacji napisanej w jezyku C interesuje już nas samo uruchomienie. Natomiast w PHP na każde uruchomienie skryptu składa się komplikacja i wykonanie. W związku z tym, nie daje to żadnego przyspieszenia – a wręcz zwalnia to program. PHP więcej czasu potrzebuje na uruchomienie funkcji define niż stworzenie zwykłej zmiennej. Takie przykłady można mnożyć jednak po tym przydługim wstępie chciałem przejść do meritum:
Kod PHP wolniej działa w PHP
Nagłówek może nieco wydawać się enigmatyczny lub pozbawiony sensu ale istocie oddaje on całą prawdę. Okazuje się, że niektóre konstrukcje samego języka PHP mogą działać wolniej niż funkcje dostarczone do języka. Nawiązując do tematu tego posta stanąłem przed problemem zdecydowania się na system, za pomocą którego mógłbym przechowywać dane konfiguracyjne aplikacji w języku PHP (ściślej mówiąc projektowanego frameworka). Co mnie interesowało i wziąłem pod uwagę:
- Serializowana tablica
- JSON
- plik INI
- plik PHP z tablicą
Żeby zbyt wiele nie wróżyć postanowiłem przeprowadzić testy wydajnościowe. Format danych który chciałbym załadować to węzeł x1, który zawiera 2 pary wartości y1 = 1 oraz y2 = 2 oraz węzeł x2, który zawiera 2 pary y3= 1 oraz y4 = 2
Dla każdej z metod zapisałem dane w odpowiednim formacie:
serializowana tablica
a:2:{s:2:”x1″;a:2:{s:2:”y1″;s:1:”1″;s:2:”y2″;s:1:”2″;}s:2:”x2″;a:2:{s:2:”y3″;s:1:”1″;s:2:”y4″;s:1:”2″;}}
JSON
{„x1”:{„y1″:”1″,”y2″:”2″},”x2”:{„y3″:”1″,”y4″:”2”}}
plik INI
[x1]
y1 = 1
y2 = 2
[x2]
y3 = 1
y4 = 2
plik PHP z tablicą
$conf_2 = array(
'x1′ => array( 'y1′ => 1, 'y2′ => 2 ), 'x2′ => array( 'y3′ => 1, 'y4′ => 2 ));
Procedura testowa
Test polegał na 10000 krotnym wywołaniu danej funkcji i odczytaniu danych. Dla każdej z metod wykonałem odpowiednie procedury:
serializowana tablica
$conf_3 = unserialize(file_get_contents(’./array.ser’));
JSON
$conf_5 = json_decode(file_get_contents(’./array.json’), TRUE);
plik INI
$conf_1 = parse_ini_file(’./array.ini’, true);
plik PHP z tablicą
require(’./array.php’);
Wyniki testu
Windows 7 (PHP 5.3.1)
- Plik INI (0.9728s)
- Require (1.0595s)
- Unserialize (1.0884s)
- JSON (1.1447s)
Linux (PHP 5.3.6)
- JSON (0.1267s)
- Plik INI (0.1744s)
- Unserialize (0.1892s)
- Require (0.2466s)
Podsumowanie
W tym momencie ciężko wyłonić zwycięzce. Dziwi mnie tylko tak duża różnica w wydajności działania funkcji json_decode między platformą Windows i Linux. Co prawda nie jest to ta sama wersja PHP ale nie powinno to mieć wpływu na działanie tej funkcji. Test ten wskazuje prędkość działania konkretnych rozwiązań, ale nie mówi on nic o czytelności i łatwości korzystania a tutaj nie trudno wskazać zwycięzce – moim zdaniem plik INI jest w tej kwestii bezkonkurencyjny. Po pierwsze większość edytorów koloruje składnie plików INI. Poza tym (co może być uważane także za minus) można w nim stworzyć tylko rzeczy, które służą strice do konfiguracji – a więc nie ma obawy, że ktoś przecholuje i zacznie wrzucać tam jakieś funkcje, dziwne zależności, zmienne globalne itd. Poza tym parser plików INI w PHP potrafi także interpretować stałe (np E_NOTICE – tak jakby widział je w kodzie) oraz przechowywać tablice. Jedyny minus to taki, że nie potrafi on skonwertować danych na rzeczywiste typu… np „true” to nie będzie w PHP „true” tylko „1” .. każda inna nieprawda to będzie pusty ciąg znaków.
Ponadto widać tu także inną rzecz. Wykonanie funkcji „parse_ini_file”, która uruchamia funkcję mającą zaczynać plik z dysku, sparsować i pozmieniać jego dane na format rozpoznawalny przez PHP działa szybciej niż załadowanie już gotowego formatu danych PHP z pliku PHP za pomocą instrukcji języka. Dziwne? I tak nie. parse_ini_file jest funkcją napisaną w języku C przez co wykonuje się ona zdecydowanie szybciej niż instrukcja PHP. Myślę, że istnieje jeszcze wiele takich króczków i na pewno będę je dalej badał.
Chciałbym jeszcze wspomnieć, że oczywiście zdaję sobie sprawę z istnienia akceleratorów, optcode’u, gotowych klas parsujących (np w Zend Framework) języka YAML, memcache’a itd itd, ale chciałem w czystym środowisku ukazać specyfikę samego języka PHP aby poznać skąd się co wzięło i z czego wynika. Jeśli nie jesteśmy zmuszeni aby używać systemu poprawiającego działanie innego systemu to dobrze. W programowaniu im mniej skomplikowany algorytm tym lepiej.