Phing jest narzędziem służącym do automatycznego budowania projektów ideologicznie bazującym na Apache Ant dystrybuowanym w postaci rozszerzenia PEAR. Narzędzie to umożliwia budowanie projektu / aplikacji tak samo jak przy pomocy make z jego plikami Makefile,ułatwiając w ten sposób przeprowadzanie złożonych procesów, które często są narażone na błędy ze strony wykonawcy.

Autorzy na stronie projektu wymieniają jego podstawowe zalety:

  • Proste pliki XML opisu budowy
  • Dostarcza bogaty zestaw zadań
  • Łatwo rozszerzalny poprzez klasy PHP
  • Niezależny od platformy – działa na UNIX, Windows, MacOSX
  • Nie posiada dodatkowych zależności
  • Zbudowany i zoptymalizowany dla ZendEngine2/PHP5

Ponadto dostarczyli oni bardzo bogatą i klarowną dokumentację zarówno samych zadań dla mechanizmu budowy, jak i API klas PHP, z których rozszerzenie zostało skonstruowane.

Rozpoczęcie pracy

Koszt wdrożenia Phing’a do projektu jest tak marginalny, że operacja ta może być przeprowadzona w dowolnym etapie projektu – niezależnie od tego czy pracujemy nad rozwiązaniem autorskim, czy też bazujemy na kodzie innej aplikacji.

Po określeniu wymagań projektu oraz wyodrębnieniu często powtarzanych lub wymagających precyzji operacji można przenieść je do postaci pliku opisu budowy (ang. buildfile) o określonej składni i ich wykonanie powierzyć Phing’owi.

W celu rozpoczęcia pracy z Phing’iem (po instalacji rozszerzenia PEAR) wystarczy pomiędzy plikami projektu umieścić plik XML nazwany build.xml, który będzie zawierał cele dla builder’a (inna nazwa pliku może zostać obsłużona przy pomocy przełącznika -f). Następnie znajdując się w tym samym katalogu co buildfile wystarczy wywołać polecenie phing.

Przykładowy layout projektu z uwzględnieniem buildfile przedstawia poniższy rysunek.

Przykład umieszczenia pliku opisu budowy w katalogu projektu

W minimalnej wersji plik powinien zawierać zdefiniowany projekt z co najmniej jednym celem budowy (target), który z kolei może składać się z szeregu zadań stanowiących podstawowy element wykonawczy Phing. Poszczególne cele mogą być od siebie wzajemnie zależnie – atrybut depends pozwala te zależności określić, przez co zyskujemy pewność, że przed zbudowaniem konkretnego celu zostaną zbudowane wszystkie cele poboczne.

Modelowanie procesu polega zatem na identyfikacji poszczególnych celów i opisaniu sposobu ich realizacji przy pomocy zadań wykonywanych w określonej kolejności. Phing umożliwia również stosowanie konstrukcji warunkowych pozwalających na zmianę procesu w zależności od środowiska (np. w zależności od systemu operacyjnego mogą być używane inne biblioteki, programy), co czyni to narzędzie jeszcze bardziej uniwersalnym.

Przykład zastosowania

Plik opisu budowy

Zakładamy realizację następujących celów:

Cel Działanie
permissions Nadanie odpowiednich uprawnień do wybranych katalogów
configuration Skopiowanie wzorca pliku konfiguracyjnego z podstawieniem wartości
określonych w pliku właściwości build.properties
setup-db Wykonanie kodu SQL zawartego w pliku przeznaczonym dla konkretnego RDBM przy pomocy
PDO z zastosowaniem danych serwera i logowania zawartych w pliku właściwości

Niżej przedstawiono kod przykładowego pliku build.xml realizującego wymienione wyżej cele.

[xml]
<?xml version="1.0" encoding="utf-8" ?>
<project name="example" default="development">

<!–
Wczytanie pliku z właściwościami (właściwość = wartość)
umożliwia skonfigurowanie procesu budowania bez konieczności
ingerencji w kod XML pliku opisu budowy

Odwołanie do wartości przez ${właściwość}
–>
<property file="build.properties" />

<!– Ustala prawa dostępu do wybrancyh katalogów –>
<target name="permissions" >
<chmod file="./assets/" mode="0777" />
<chmod file="./protected/runtime/" mode="0777" />
</target>

<!– Przygotowanie konfiguracij aplikacji –>
<target name="configuration">

<!– Kopiuje plik file do tofile zawsze nadpisując –>
<copy file="./protected/config/main.template.php"
tofile="./protected/config/main.php"
overwrite="true">
<filterchain> <!– Kopiowany plik przepuszczamy przez filtr… –>
<expandproperties /> <!– … podstawiania wartości –>
</filterchain>
</copy>

</target>

<!– Insatluje tabele z dodatkowych plików SQL –>
<target name="setup-db">
<pdosqlexec
url="${app.database.connector}:host=${app.database.host};dbname=${app.database.name}"
userid="${app.database.user}"
password="${app.database.password}">
<transaction src="data/schema-${app.database.connector}.sql" />
</pdosqlexec>
</target>

<target name="development" description="Buduje wersję developerską"
depends="permissions, configuration, setup-db">
<!– Pozostałe wywołania zadań / innych celów –>
</target>

</project>
[/xml]

Plik właściwości

Dla ułatwienia pracy z Phing’iem, czy też zwykłej konfiguracji procesu budowy można wykorzystać plik właściwości, zawierający pary właściwość=wartość, do których można się odwołać wewnątrz pliku budowy. Poniżej przedstawiono przykładowe wpisy używane do konfiguracji połączenia z bazą danych przy pomocy PDO, ale równie dobrze można w takim pliku podać adresy serwerów FTP na które mają zostać automatycznie wgrane pliki.

[bash]
# Database type
app.database.connector = pgsql

# Database server
app.database.host = localhost

# Database server port
# MySQL = 3306; PgSQL = 5432
app.database.port = 5432

# Database name
app.database.name = db_name

# Database user
app.database.user = my_webapp

# Database user password
app.database.password = ~t0p!S3CreT~
[/bash]

Warto pamiętać o tym, że buildfile może pobrać informacje z większej ilości plików właściwości jak również o opcjonalności jego stosowania. Wszystkie wymienione wartości można bowiem zapisać bezpośrednio w pliku budowy przy pomocy zadania property.

Przeprowadzenie procesu budowy

Do przeprowadzenia budowy – jak wspomniano wcześniej – wystarczy dostęp do powłoki i wywołanie polecenia phing z opcjonalnym podaniem nazwy celu budowy. Jeżeli nie zostanie określony żaden cel, Phing podejmie się wykonania domyślnego celu określonego w właściwości default tagu project.

[bash]
# Spowoduje wykonanie ‚permissions’, ‚configuration’,
# ‚setup-db’ i ‚development’
$ phing

# To samo co wyżej
$ phing development

# Tylko wybrany cel – nadanie uprawnień katalogom
$ phing configuration

# Tylko instalacja bazy danych
$ phing setup-db
[/bash]

Filtry, sterowanie wykonaniem

W przedstawionym przykładowym pliku opisu budowy zawarto zadanie kopiowania pliku z łańcuchem filtrów zawierającym filtr expandproperties. Zadaniem tego filtru jest podstawienie wartości właściwości widocznych w danym zakresie (ang. scope) do pliku, który jest przez zadanie przetwarzany. Plik właściwości został załączony na poziomie całego projektu (a nie pojedynczego celu), przez co zawarte w nim właściwości są widoczne w obrębie każdego z celów.

Mechanizm w połączeniu z szablonem pliku konfiguracyjnego przedstawionym poniżej skutkuje zatem utworzeniem w pełni przygotowanego pliku konfiguracji połączeń z bazą danych. W przypadku systemów, wykorzystujących gotowe komponenty często zachodzi konieczność odrębnego konfigurowania każdego z tych komponentów, dlatego przygotowanie szablonu konfiguracji poszczególnych składników systemu wykorzystujących np. tę samą bazę danych i zamknięcie procesu konfiguracji w pojedynczym celu budowy pozwala na rekonfigurację owych komponentów w dowolnym momencie. W tym celu wystarczy jedynie zaktualizować plik właściwości o nowe dane połączenia z bazą i uruchomić polecenie phing podając w pierwszym parametrze nazwę celu budowy odpowiedzialnego za konfigurację połączeń z bazą danych.

[php]
<?php return array(
‚connectionString’ => ‚${app.database.connector}:host=${app.database.host};’
. ‚dbname=${app.database.name}’,
‚user’ => ‚${app.database.user}’,
‚password’ => ‚${app.database.password}’,
);
[/php]

Phing wyposażony jest w szereg filtrów umożliwiających między innymi konwersję tabulacji na spacje, usuwania komentarzy PHP, czy też podmiany ciągów występujących w plikach na podstawie wyrażeń regularnych.

Przede wszystkim Phing posiada wiele zadań pozwalających na zamodelowanie niemalże każdego procesu, włączając w to pobieranie danych wejściowych z konsoli czy też integrację z systemami wersjonowania kodu Git i SVN.

Rozszerzalność

Phing jest narzędziem stworzonym przy pomocy języka PHP. Pomimo mnogości funkcji dostarczanych przez autorów rozszerzenie Phing nie pokrywa wszystkich możliwych obszarów zastosowań dla narzędzi tego rodzaju. Dlatego właśnie autorzy pozostawili furtkę dla bardziej wymagających użytkowników w postaci obiektowej, a przez to rozszerzalnej architektury.

Jeżeli Phing nie posiada wbudowanej funkcjonalności, która akurat jest potrzebna w danym projekcie to nic nie stoi na przeszkodzie, aby dodać do niego własne zadanie, które w istocie jest klasą PHP rozszerzającą klasę Task – wszystko to opisane w przejrzystej i dokładnej dokumentacji.

Dlaczego miałbym / miałabym to stosować?

W cyklu życia projektu istnieje wiele traumatycznych momentów – włączając w to mniej lub bardziej drastyczne zmiany architektury, które często wiążą się z koniecznością przebudowania sporego kawałka projektu.

Po ciężkich bojach przychodzi czas wyjścia z środowiska deweloperskiego do środowiska testowego, a dalej produkcyjnego. W przypadku udanych i perspektywicznych projektów, lub też zawierających usterki, które trzeba usunąć (czyli zawsze) przychodzi moment aktualizacji.

Na każdym z tych etapów, dokładność oraz czas jego realizacji jest bardzo istotny. Nie można przecież pozwolić na to, aby wyłączyć na kilka dobrych godzin intranet, który stanowi aortę przedsiębiorstwa, czy też przynoszącego kluczowe dla firmy zyski sklepu internetowego.

W środowiskach deweloperskich stosowanie Phing’a umożliwia skrócenie czasu przeprowadzania często powtarzanych czynności. Dla przykładu proces instalacji bazy danych aplikacji wymaga albo dużej ilości klikania, lub w lepszych przypadkach napisanie i uruchomienie skryptu shell’owego z bajeczną ilością parametrów i koniecznością pamiętania nazw użytkowników i haseł.

Innym przypadkiem może być tworzenie autorskiego silnika wyszukiwania, który wcześniej indeksuje możliwe wyniki zapytań w postaci plików na dysku twardym. Debugowanie, czy też testowanie wprowadzanych w silniku zmian pociąga za sobą konieczność wykonania dodatkowych czynności (np. ponownego indeksowania itp), które warto zautomatyzować. Idąc dalej – stworzenie celu przeznaczonego dla środowiska rozwojowego obejmującego wypełnianie bazy testowymi danymi pozwala na szybsze przeprowadzanie badań przyczyn niepożądanych zachowań aplikacji (zresetowanie systemu do stanu sprzed kontrolowanej awarii zajmuje o wiele mniej czasu i zawsze jest to ten sam stan początkowy).

W obrębie procesu budowy możliwe jest również zawarcie swojego rodzaju sprawdzeń wymagań projektu. Przed instalacją aplikacji warto sprawdzić na przykład czy:

  • Podane dane logowania do bazy danych są poprawne
  • Czy wymagane przez aplikację katalogi są dostępne do zapisu
  • Czy na danym serwerze zostało zainstalowane i włączone wymagane do funkcjonowania rozszerzenie

Kiedy przychodzi czas aktualizacji aplikacji, mamy często do czynienia z grą o wielką stawkę: migracją danych które zostały zgromadzone przez dotychczasowe funkcjonowanie aplikacji w środowisku produkcyjnym. Kluczowym zadaniem podczas aktualizowania jest niedopuszczenie do utraty / uszkodzenia tych skrzętnie zgromadzonych informacji. W tym przypadku błąd ludzki może być tragiczny w skutkach, dlatego stara się prawdopodobieństwo wystąpienia takiego błędu minimalizować przez stosowanie np.
opisywanych prez Piotra Karwatkę checklist. Procedura aktualizacji może składać się z następujących operacji:

  1. Przestaw aplikację w stan maintenance, który będzie informował użytkowników o pracach konserwacyjnych
  2. Wykonaj zrzut bazy danych do archiwum .tar.gz i przenieś to archiwum do wskazanego katalogu
    / wgraj przez SFTP na serwer kopii bezpieczeństwa
  3. Wykonaj kopię zapasową katalogów zawierających wgrane przez użytkowników pliki oraz logi błędów aplikacji w postaci archiwum .tar.gz i umieść we wskazanym katalogu / wgraj przez SFTP na serwer kopii bezpieczeństwa
  4. Wykonaj migrację bazy danych przez wykonanie wskazanych skryptów SQL w takiej kolejności jakiej zostały przedstawione na liście
  5. Wykonaj migrację testowej bazy danych przez wykonanie wskazanych skryptów SQL w takiej kolejności jakiej zostały przedstawione na liście.
  6. Uruchom następujące skrypty weryfikujące integralność informacji w podanej kolejności (lista)
  7. Uruchom następujące skrypty dokonujące dodatkowej konfiguracji systemu w podanej kolejności (lista)
  8. Uruchom testy jednostkowe kodu i upewnij się, że wszystkie zakończyły się z powodzeniem

Na podstawie tak określonej listy, można wyznaczyć poszczególne cele wraz z wzajemnymi zależnościami i przenieść to do pliku opisu budowy nazwanego update.xml. W takim wypadku może on zostać wywołany poprzez:

[bash]$ phing -f update.xml[/bash]

Jedną z zalet tego rozwiązania jest to, że scenariusz aktualizacji może być wielokrotnie testowany w kontrolowanym środowisku deweloperskim i testowym przed uruchomieniem w środowisku produkcyjnym.

Zakres stosowalności automatyzacji

Stosowanie tego rodzaju podejścia może znacznie poprawić jakość procesu wytwarzania aplikacji, a przez to jakość produktu finalnego. Jednak możliwości i zakres stosowania obwarowane są wieloma czynnikami niezależnymi – takimi jak stopień (dez)organizacji pracy w firmie lub natłok zgłoszeń o niepoprawnym funkcjonowaniu produktu wprowadzający perturbacje w procesie tworzenia nowego kodu.

Metodyka ta da się zastosować w pewnym zakresie wszędzie – zarówno w mocno niezorganizowanej firmie gdzie programista, lub grupa programistów może ułatwiać sobie tym narzędziem pracę na etapie rozwoju, lub przedsiębiorstwie o wysokim stopniu organizacji pracy kładącym nacisk na jakość i stabilność tworzonych rozwiązań.

Najefektywniej wykorzystywana jest w przypadku wytwarzania produktów z zamysłu „pudełkowych”, gdzie operuje się na kolejnych wersjach zawierających nowe funkcjonalności i poprawki dla starszych wersji.

Co warto automatyzować

Konfiguracja aplikacji

Przygotowanie wzorców plików konfiguracyjnych aplikacji / modułów w zależności od środowiska (deweloperskie, testowe, produkcyjne), uwzględniających różne poziomy dokładności logów (trace level) czy też ustawień PHP (takich jak error_reporting itp).

Nadawanie praw do katalogów

Pozwala na wyeliminowanie problemu wysypywania się aplikacji przez brak możliwości utworzenia nowego pliku logu, czy też brak uprawnień do zapisu w katalogu zawierającego pliki wgrywane przez użytkowników.

Wdrażanie / instalowanie aplikacji

Określenie raz listy plików do zamieszczenia na serwerze FTP ogranicza poziom „wybrakowania”. Ponadto automatyzację warto stosować, gdy aplikacja wymaga rozmieszczenia większej ilości katalogów / dokonania odpowiednich wpisów w innych plikach (na wzór Zend Framework Command Line Tool i plików .zfproject).

Kopie zapasowe

Pliki zawsze o pożądanej nazwie, położone w żądanym miejscu na serwerze (lokalnym, zdalnym) – można sprzęgać np. z CRON.

Skrypty narzędziowe

Istnieje pewna klasa skryptów nazywanych narzędziowymi – takich jak np, ładowanie produktów do bazy danych z pliku XML, czy też ustalanie hierarchii uprawnień użytkowników w systemie – które stanowią o poprawnym działaniu aplikacji. Dla nich właśnie można wprowadzić albo pojedynczy cel wykonujący szereg wywołań skryptów pomocniczych, lub też dla każdego z nich zarezerwować osobny cel (bardzo przydatne na każdym etapie życia projektu).

Złożone i newralgiczne procesy

Przejścia z wersji A do wersji B często są dosyć unikatowe, i za każdym razem wymagają odrębnego podejścia, oraz scenariusza przeprowadzania operacji. Zaleca się preparowanie celów typu upgrade, które najpierw sprawdzają warunki konieczne do prawidłowego przebiegu aktualizacji (wersje oprogramowania, bazy danych itp.) a następnie ją przeprowadzają z uwzględnieniem wykonania kopii zapasowej (np. przez wykonanie celu backup) oraz innych czynników.

Praca z dodatkowymi modułami

W bardziej zaawansowanych projektach często używamy rozszerzeń, czy też rozwiązań dostarczanych przez inne podmioty – należą do nich np. ORM’y, czy rozszerzenia takie jak Zend_Lucene, które posiadają odrębną konfigurację, czy też zestaw operacji koniecznych do przeprowadzenia w celu umożliwienia poprawnej pracy – takich jak np. przebudowanie klas modeli na podstawie schematu bazy danych, stworzenie podręcznego indeksu wyników wyszukiwania celem odciążenia bazy danych.

Operacje pomocnicze

Do których możemy zaliczyć czyszczenie katalogów tymczasowych, utworzonych plików cache itp.

Słowo końcowe

Dla mnie bomba 😉