Co to jest PHAR?
Celem przypomnienia zacznę od wyjaśnienia, czym PHAR jest. Skrót rozwija się w PHP Archive - jest to zunifikowane API do tworzenia i zarządzania archiwami plikowymi dla PHP 5.2, które standardowo będzie dostępne dla wszystkich od wersji 5.3. Jest to również format archiwum pozwalający spakować dowolny, wieloplikowy skrypt do postaci pojedynczego pliku, który wciąż jest uruchamialny np. z linii komend albo z przeglądarki. Może on być także dołączany do skryptów przez require i include, możliwy jest też elegancki dostęp do pojedynczych spakowanych w nim plików. Więcej o PHAR można znaleźć w mojej poprzedniej notce, której adres podaję w przypisach.
Instalacja PHAR na PHP 5.2
Aby móc cieszyć się PHAR-em na PHP 5.2, należy skorzystać z pomocy repozytorium PECL. W Linuksie ogranicza się to do wstukania jednej komendy:
pecl install phar-2.0.0RC1
Ważne jest, aby nie zapomnieć o numerze wersji, gdyż inaczej dostaniemy mocno wybrakowany moduł PHAR 1.2.cośtam, w którym brakuje sporo funkcji.
Skrypt pakujący do PHAR
Moduł nie dostarcza nam żadnego gotowego programu pakującego. Zamiast tego, do naszej dyspozycji jest API i z jego pomocą musimy sobie sami napisać stosowny skrypcik dostosowany do tego, czego potrzebujemy. Jego budowa jest dość prosta:
<?php $phar = new Phar('./mojeArchiwum.phar'); $phar->buildFromDirectory('./mojSkrypt/'); $phar->setStub('<?php Phar::webPhar(); __HALT_COMPILER(); ?>'); echo 'Finito!';
Pierwsza linijka tworzy nowy obiekt archiwum. Ścieżka może wskazywać na już istniejące archiwum, które możemy w ten sposób modyfikować, lub też być nazwą zupełnie nowego pliku. Następnie przechodzimy do dodania jakiejś zawartości. API udostępnia cały szereg metod takich, jak addFile(), ale nie musimy się tak męczyć. Mamy bowiem do dyspozycji dwie metody-kombajny: buildFromDirectory() pakująca do archiwum zawartość podanego katalogu oraz buildFromIterator() czytająca pliki do zapakowania z dowolnego iteratora.
Kolejnym etapem jest zdefiniowanie tak zwanego stuba - jest to specjalny kawałek kodu odróżniający PHAR-a od zwykłego TAR-a czy formatu ZIP. Wykonuje się on w trzech przypadkach:
php archiwum.phar http://www.jakisadres.pl/archiwum.phar require('archiwum.phar');
Pełni zatem rolę bootloadera - możemy w nim umieścić np. kod autoloadera z definicjami wszystkich klas zawartych w archiwum, by nie trzeba było odpytywać za każdym razem systemu plików lub też kod wstępnej konfiguracji oraz uruchomienia. Ważne jest, że uruchamiając archiwum w ten sposób, PHP nie parsuje całego archiwum - duże aplikacje muliłyby przez to niemiłosiernie. Interpreter wczytuje tylko i wyłącznie podany stub, który nie bez powodu kończy się specjalną dyrektywą __HALT_COMPILER(). W momencie jej napotkania kompilacja zatrzymuje się i reszta pliku po prostu nie jest czytana, dopóki nie zajdzie wyraźna potrzeba.
Niestety, w przeciwieństwie do konkurencyjnego formatu PHK, PHAR nie udostępnia żadnego domyślnego autoloadera. Musimy go stworzyć sami lub olać tę kwestię i, jak za starych dobrych czasów, ładować wszystkie pliki archiwum jawnie. Jedyną sensowną metodą pomocniczą jest statyczny Phar::webPhar(). Jego zadaniem jest odczytanie zmiennej REQUEST_URI i wybranie na jej podstawie pliku z wnętrza archiwum do odczytania oraz wysłania do przeglądarki. Otrzymujemy więc prymitywny kontroler, który jednak pozwala na szybkie i łatwe spakowanie np. phpMyAdmina bez konieczności modyfikowania jego kodu źródłowego.
Domyślnie nie musimy robić nic więcej. O ile nie zażyczymy sobie inaczej, zmiany wprowadzane są do archiwum na bieżąco, nie potrzeba więc wywoływać niczego do zapisania ich na dysku. Można to zmienić, wywołując na początku metodę $phar->startBuffering() oraz kończąc robotę przez $phar->stopBuffering().
Bardziej wyszukana pakowarka
Pokażę teraz sposoby na rozszerzenie pakowarki. Pierwsza rzecz to wywalenie z archiwum katalogów systemu kontroli wersji, które będą tylko przeszkadzać. Skorzystamy w tym celu z iteratorów przepuszczonych przez dodatkowy filtr:
class DirFilter extends FilterIterator { private $_remove; public function __construct(Iterator $it, $remove) { parent::__construct($it); $this->_remove = $remove; } // end __construct(); public function accept() { if(strpos($this->getInnerIterator()->key(), $this->_remove) === false) { return true; } return false; } // end accept(); protected function __clone() { // nie wolno klonowac. } // end __clone(); } // end DirFilter; $phar->buildFromIterator( new DirFilter( new RecursiveIteratorIterator( new RecursiveDirectoryIterator('./aplikacja/') ), '.svn' ), './aplikacja/' );
Filtr ten przepuści wszystkie pliki, które w swojej ścieżce nie zawierają słowa .svn, dzięki czemu usuniemy nieco zbędnego balastu. Gdyby coś nie działało, polecam zwrócić uwagi na ścieżki, gdyż PHAR jest dość wrażliwy na tym punkcie. Jeżeli ścieżki zwracane przez iterator nie będą się zawierać w tej, którą podaliśmy jako drugi argument do buildFromIterator(), dostaniemy parę wyjątków w prezencie.
Aby dodać bardziej wyszukane warunki, wystarczy zmodyfikować metodę accept() w klasie DirFilter. Obsługą całej iteracji zajmie się abstrakcyjna klasa FilterIterator, po której dziedziczymy.
Jak konstruować stuby?
Pierwsza i najważniejsza zasada - stub musi być zakończony wywołaniem rozkazu __HALT_COMPILER(). Poza tym mamy sporą swobodę. W przypadku OPL-a, napisałem system pakujący, który jako stub umieszcza plik z klasą Opl_Loader oraz dodaje kod automatycznie inicjujący autoloader. Dla pozostałych bibliotek stub po prostu sprawdza, czy już załadowaliśmy główne jądro, a jeśli nie - rzuca wyjątek. Więcej możliwości nadejdzie, gdy napiszę specjalny autoloader do obsługi PHAR-ów. Od razu uspokajam, że muszę takie coś zrobić jedynie z powodu specyfiki tego konkretnego projektu, gdzie każda biblioteka zapisana jest w osobnym archiwum, a do tego musi obsługiwać wtyczki i rozbudowę. Pakując typową aplikację np. Zend Frameworka, wszystko powinno działać po staremu. PHAR automatycznie będzie przekierowywać wszystkie ścieżki spakowanego skryptu tak, aby odnosiły się do plików wewnątrz archiwum.
Pakując bibliotekę, polecam skorzystanie ze statycznej metody Phar::mapPhar(), którą można wywoływać tylko z poziomu stuba. Tworzy ona alias do nowouruchomionego archiwum zapewniający łatwiejszy dostęp. Przykładowy stub:
<?php Phar::mapPhar('biblioteka.phar'); __HALT_COMPILER(); ?>
Możemy odpalić bibliotekę normalnie, jednak gdybyśmy potrzebowali później ręcznie wczytać jakiś plik z archiwum, nie musimy podawać już pełnej ścieżki, gdyż stub zarejestrował krótszy alias:
require('./sciezka/do/biblioteka.phar'); require('phar://biblioteka.phar/costam.php');
Nowości w PHAR
Od sierpniowej notki poświęconej PHAR-owi, w module zaszły pewne małe zmiany. Pierwsza z nich dotyczy wydajności. O ile normalnie praktycznie nie ma różnicy między odpalaniem skryptu wieloplikowego, a spakowanego do archiwum, zwracano uwagę, że stosowanie systemów cache takich, jak APC, w rzeczywistości spowalnia pracę archiwów, podczas gdy normalne skrypty znacznie przyspieszają. Problem z wydajnością został już rozwiązany. Według zapewnień autorów phpMyAdmin w postaci PHAR wykonuje się obecnie do sześciu razy szybciej z użyciem APC, niż bez niego.
Kolejna nowość to możliwość cyfrowego podpisania archiwum. Do wyboru mamy kilka funkcji haszujących (np. SHA-1), lecz takie coś przydaje się co najwyżej do zweryfikowania poprawności archiwum. Jeśli jednak PHP posiada zainstalowany moduł OpenSSL, możemy wykorzystać go do stworzenia podpisu wskazanym kluczem prywatnym. Służy do tego metoda Phar::setSignatureAlgorithm(). Mając archiwum /costam/archiwum.phar klucz publiczny musi być zawarty w pliku /costam/archiwum.phar.pubkey, jeśli chcemy, by PHAR go odnalazł :).
Zakończenie
Nie ukrywam, że PHAR wraz z implementacją szeregu struktur danych w SPL-u są dwiema rzeczami, które w PHP 5.3 mi się najbardziej podobają. Jednak nie zapominajmy też, że równie długo dostępny jest inny system archiwów zwany PHK, który posiada znacznie większą wysokopoziomową funkcjonalność (domyślny autoloader, tryb webinfo do przeglądania informacji o paczce, wsparcie dla systemu wtyczek, istniejące dużo wcześniej cyfrowe podpisywanie...). I podobnie, jak w przypadku PHAR-ów, interpreter nie musi mieć zainstalowanego tego rozszerzenia, aby móc uruchamiać te archiwa.






Napisał Dawior w środę, 10 grudnia 2008 o 17:53
No. Podoba mi się.