Archiwum kategorii: Phing

Phing, czyli zautomatyzuj swoje zadania #4

Kiedy projekt dochodzi do etapu, w którym trzeba/warto pokazać go „na zewnątrz” trzeba go jakoś wdrożyć na serwerze. Automatyzacja całego procesu budowania i wdrożenia jest ważna a wgranie plików na serwer jest jednym z etapów. W jego realizacji z wykorzystaniem FTP może pomóc Phing i zadanie FtpDeployTask. Żeby móc z niego skorzystać trzeba mieć w systemie pakiet PEAR: Net_FTP.

<ftpdeploy 
  host="${ftp.host}" 
  port="${ftp.port}" 
  username="${ftp.username}" 
  password="${ftp.password}"
  dir="${ftp.dir}" 
  passive="false"
  <fileset dir="dist">
    <include name="**"/>
  </fileset>
</ftpdeploy>

Jak widać powyżej użycie tego zadania jest banalnie proste (jeżeli jednak nie, to polecam moje wcześniejsze wpisy nt. Phinga).

Za dobry pomysł uważam budowanie projektu dla środowisk „zewnętrznych” w osobnym katalogu (ignorowanym przez IDE). Dzięki temu można uwzględnić strukturę katalogów na serwerze (np. umieszczenie plików wywoływanych przez cron w innym katalogu niż projekt). Dodatkową zaletą jest fakt, że mając odwzorowaną strukturę katalogów z serwera w podkatalogu można również wgrać pliki z wykorzystaniem klienta FTP. Przy mało stabilnym połączeniu internetowym wgranie plików projektu może być utrudnione ponieważ błąd wgrania jednego pliku przerywa wykonywanie całego zadania.

Przydatne linki:

Phing, czyli zautomatyzuj swoje zadania #3

Trochę czasu mineło od ostatniego i w końcu udało mi się znaleźć chwilę na opisanie kolejnych elementów Phinga – filtrów i maperów.

Filtry

Filtry pozwalają na wykonywanie różnych operacji na zawartości plików – na przykład usuwanie komentarzy z kodu. Wywołanie filtrów grupuje się w tagu <filterchain>, przykład poniżej:

	<copy todir="kopia" overwrite="true">
		<fileset dir=".">
			<patternset refid="ogolne" />
		</fileset>
		<filterchain>
			<stripphpcomments />
		</filterchain>
	</copy>

Przykładowy kod definiuje operację kopiowania plików (zdefiniowanych w elemencie <patternset> o ID: „ogolne” do katalogu „kopia”. Oprócz zwykłego kopiowania pliki są przetwarzane przez łańcuch filtrów. W tym wypadku zawierający jeden filtr, który usuwa z plików komentarze PHP.

W momencie pisania tego posta Phing dostarczał 19 wbudowanych filtrów, listę można znaleźć w dokumentacji. Natomiast z popularniejszych filtrów myślę, że należy wymienić:

Z praktycznego punktu widzenia filtry są bardzo przydatne, ale mogą powodować pewne problemy w połączeniu z systemem kontroli wersji – o tym co można poradzić napiszę w dalszej części.

Mapery

Efektem działania filtrów jest zmiana zawartości plików, natomiast mapery działają analogicznie, ale z nazwami plików. W momencie przygotowywania wpisu Phing dostarczał 6 maperów. W moim odczuciu najbardziej przydatne są:

  • GlobMapper – podmiana nazw plików według prostych wzorców;
  • RegexpMapper – to samo, ale z wykorzystaniem wyrażeń regularnych we wzorcach.

Użycie maperów odbywa się w analogiczny sposób jak filtrów – w ramach jakiejś operacji plikowej (przykład zaczerpnięty z oficjalnej dokumentacji):

<copy todir="/tmp">
  <mapper type="glob" from="*.php" to="*.php.bak"/>
  <fileset refid="someid" />
</copy>

Praktyczny przykład użycia

Za dobry przykład użycia Phinga (filtry, mapery) i systemu kontroli wersji uważam generowanie plików konfiguracyjnych projektu.

Idea opiera się na plikach o rozszerzeniu .dist (kwestia umowna). Do systemu kontroli wersji trafiają pliki „szablonowe” (.dist), tak żeby ich struktura mogła być śledzona. Z drugiej strony, aby nie zawierały konkretnych informacji (np. dostęp do bazy danych). W tych plikach zamiast wartości pojawiają się Phingowe odwołania do zmiennych. Utworzenie działającego pliku konfiguracyjnego polega na:

  1. skopiowaniu pliku
  2. zmianie rozszerzenia z .php.dist na .php
  3. podstawieniu wartości w miejsce wywołań zmiennych

Plik config/Config.php.dist:

<?php

class Config
{
	const DB_HOST = '${config.db.host}';
	const DB_NAME = '${config.db.name}';
	const DB_USER = '${config.db.user}';
	const DB_PASSWORD = '${config.db.password}';
}

Plik srodowisko.properties:

config.db.host = localhost
config.db.name = site
config.db.user = siteuser
config.db.password = tAjNeHaSlO

Plik build.xml:

<?xml version="1.0" encoding="UTF-8"?>

<project name="HelloWorld" default="hello">
	<property file="srodowisko.properties" />

	<target name="configure">
		<copy todir="config">
			<fileset dir="config">
				<include name="*.php.dist" />
			</fileset>
			<mapper type="glob" from="*.php.dist" to="*.php" />
			<filterchain>
				<expandproperties />
			</filterchain>
		</copy>
	</target>

</project>

Po uruchomieniu komendy phing configure skrypt podstawi wartości z pliku właściwości, zmieni rozszerzenie i utworzy nowy plik, który można dołączyć do kodu projektu. Takie podejście daje dużą elastyczność, ponieważ cała konfiguracja środowiska może być zapisana w jednym pliku. Dzięki temu projekt można przekonfigurować czy to pod kątem środowiska produkcyjnego, czy developerskiego podstawiając jeden plik i wydając jedną komendę (a można to jeszcze uprościć). Zaletą jest również to, że w systemie kontroli wersji znajdują się pliki, bez wartości zależnych od środowiska. W tym celu, dla powyższego przypadku należy dodać do listy plików ignorowanych przez system kontroli wersji plik Config.php.

Przydatne linki:

Phing, czyli zautomatyzuj swoje zadania #2

Przyszedł czas na drugie spojrzenie na Phing – czym on jest napisałem w skrócie w poprzednim wpisie.

Parametry uruchomienia

To narzędzie konsolowe, więc jest uruchamiane za pomocą wywołania phing [opcje] [cel] w konsoli (najlepiej w katalogu zawierającym plik build.xml). W wywołaniu można umieścić parametry/opcje:

  • [nazwa celu] – nazwa celu, który ma zostać wywołany; jeżeli się nie pojawi zostanie uruchomiony cel domyślny projektu (<project ... default="domyslny_cel"/>);
  • -h / -help – wyświetlenie ogólnej pomocy aplikacji;
  • -l / -list – wyświetlenie listy celów zdefiniowanych w build.xml;
  • -f [plik] / -buildfile [plik] – nazwa pliku skryptu, jeżeli inna niż build.xml;
  • -D[właściwość]=[wartość] – przekazanie do skryptu właściwości o podanej wartości;
  • -propertyfile [plik] – wczytanie podanego pliku z deklaracjami właściwości;

Cele

W poprzedniej sekcji wspomniałem o parametrze pozwalającym wyświetlić listę dostępnych celów. Jednak nie każdy cel powinien być uruchamiany „samodzielnie” – czasem jest potrzebny tylko do spełnienia jakichś podstawowych wymagań dla innego celu, który wykonuje faktyczne operacje. Przykładem może być cel, który wczytuje/ustala odpowiednie właściwości konfiguracyjne (np. ścieżki do katalogów, dane FTP etc.), wywołanie takiego celu samodzielnie do niczego nie doprowadzi. Dlatego takie „prywatne” cele można ukryć na liście za pomocą atrybutu <target ... hidden="true"/>.

Kolejną i chyba nabardziej przydatną możliwością celów jest ustalanie dla nich zależności – na przykład, żeby wykonać cel „deploy” (który wgrywa pliki na serwer), należy najpierw te pliki/katalogi przygotować (np. usunąć pliki tymczasowe). W skrypcie Phing nie trzeba deklarować dodatkowo wywołań celów, od których zależy wykonanie bieżącego lub samemu wywoływać ręcznie cele w odpowiedniej kolejności. W tym celu wykorzystuje się atrybut <target ... depends="cel1,cel2,cel3"/>

Są jeszcze dwa pomocne atrybuty celów – if, unless. Pierwszy uruchamia cel tylko w momencie, kiedy zmienna o podanej nazwie jest zadeklarowana. Drugi ma działanie odwrotnie. Poniżej przykład:

<?xml version="1.0" encoding="UTF-8"?>

<project name="HelloWorld" default="hello">

    <property name="uruchomione" value="" />

    <target name="hello" depends="cel1,cel2">
        <echo>Hello</echo>
    </target>

    <target name="cel1" if="uruchomione">
        <echo>cel 1</echo>
    </target>

    <target name="cel2" unless="uruchomione">
        <echo>cel 2</echo>
    </target>

</project>

Poniżej wynik uruchomienia (pojawiają się nagłówki wszystkich wywołanych celów, ale kod cel2 nie został wykonany ze względu na warunek zawarty w atrybucie unless.

cim@cim-k52:~/public_html/Phing$ phing 
Buildfile: /home/cim/public_html/Phing/build.xml

HelloWorld > cel1:

     [echo] cel 1

HelloWorld > cel2:


HelloWorld > hello:

     [echo] Hello

BUILD FINISHED

Total time: 0.0581 seconds

Właściwości

Właściwości są swego rodzaju zmiennymi dla skryptu Phing i zostały już użyte w poprzednich przykładach. Warto pamiętać, że odwołania do właściwości mają budowę ${nazwaWlasciwosci}, np. <echo>Wartość zmiennej: ${nazwa}</echo>. Przydatne jest używanie kropek dla budowania hierarchii – ${aplikacja.baza.host}% – można w ten sposób grupować właściwości co mocno zwiększa czytelność.

Do tej pory w przykładach pojawiały się właściwości z wartościami zadeklarowanymi w pliku build.xml, jednak to nie jedyne możliwe źródła danych:

  • <property name="nazwa" value="wartosc" /> – pokazywany wcześniej sposób deklarowania zmiennej, w deklaracjach można odwoływać się do innych zmiennych (np. do jakiejś ścieżki dodać nazwe kolejnego katalogu);
  • <property file="plik.properties" /> – wczytuje do skryptu właściwości zdefiniowane w podanym pliku; można użyć również dodatkowego atrybutu <property file="plik.properties" prefix="zPliku" /> co spowoduje dodanie prefiksu do wszystkich nazw właściwości znajdujących się w pliku;
  • <echo>${env.PATH}</echo> – odwołania z prefiksem env. pozwalają na dostęp do zmiennych środowiskowych (zależne od systemu);

W przypadku zdublowania nazw właściwości Phing będzie zwracał pierwszą wartość właściwości, żeby zmienić to zachowanie trzeba dodać do drugiej deklaracji atrybut <property ... override="yes" />.

Podczas tworzenia ścieżek lub operacji plikowych pomocny może okazać się dostęp do ścieżki do katalogu projektu (zadeklarowanej w tagu <project ... basedir="/var/www/Projekt"> lub domyśnie ustawianej przez Phing). Ścieżka jest dostępna pod nazwą ${project.basedir}.

FileSet

Ponieważ Phing ma ułatwiać wdrożenia projektu posiada wsparcie do operowania na zbiorach plików – FileSet. Jeżeli na jakichś plikach mają zostać wykonane operacje można je wskazać bardzo dokładnie:

<target name="copy">
    <copy todir="kopia">
        <fileset dir=".">
            <exclude name="*.xml" />
            <include name="*.properties" />
            <include name="*.php" />
        </fileset>
    </copy>
</target>

Powyższa definicja przedstawia cel kopiujący pliki do katalogu „kopia” za pomocą polecenia copy. Natomiast miejsce z którego pliki są kopiowane („.” – czyli bieżący katalog) oraz deklaracje, które pliki (w tym wypadku według rozszerzeń) mają być kopiowane lub pomijane definiuje typ FileSet.

Definiowanie dołączeń/wykluczeń jest elastyczne:

  • można wykorzystywać atrybuty: <fileset ... includes="..."/> i <fileset ... excludes="..."/> z wykorzystaniem przecinków lub spacji podawać wzorce, jednak powyżej kilku wzorców ta metoda sprawia, że skrypt jest mniej czytelny;
  • można wykorzystać atrybuty: <fileset ... includesfile="..."/> i <fileset ... excludesfile="..."/> podając ścieżki do plików, w których każda linijka zawiera jeden wzorzec; jednak wymaga to utrzymywania dodatkowych plików oprócz niezbędnego build.xml;
  • można użyć tagów <include/> i <exclude/> w tagu FileSet tak jak w przykładzie powyżej; chociaż przy wielu operacjach powtarzanie tego samego zestawu dołączeń/wykluczeń i późniejsze ich utrzymywanie różnież może być kłopotliwe;
  • można użyć typu PatternSet.

Deklarowanie wzroców dopasowań nazw plików opiera się na gwiazkach („*” lub „**”). Jedna gwiazka pozwala na dopasowanie nazwy pliku/katalogu jednak w ramach tego dopasowania mogą się znajdować tylko pliki/katalogi bezpośrednio w danym katalogu. Natomiast dwie gwiazdki pozwalają na dopasowania również z uwzględnieniem zawartości podkatalogów. Przykłady z opisem są w oficjalnej dokumentacji.

PatternSet

W poprzenim akapicie wymieniłem kilka możliwości definiowania dołączeń/wykluczeń dla typu FileSet wraz z ich niedogodnościami. Ostatnią opcją jest typ PatternSet, który najprościej można opisać jako zbiór wzorców dla FileSet. Poniżej zmodyfikowany kod celu kopiującego pliki:

<patternset id="ogolne">
    <exclude name="*.xml" />
    <include name="*.properties" />
    <include name="*.php" />
</patternset>

<target name="copy">
    <copy todir="kopia">
        <fileset dir=".">
            <patternset refid="ogolne" />
        </fileset>
    </copy>
</target>

Wynik wykonania celu przedstawionego powyżej i z poprzedniego przykładu są identyczne. Różnia polega na tym, że wykorzystanie PatternSet ułatwia zapanowanie nad definicjami warunków – można je zdefiniować globalnie i tylko odwoływać się w razie potrzeby, a zmiany ograniczają się do jednego miejsca. Warto też zauważyć, że PatternSet w przeciwieństwie do FileSet jest niezależny od katalogu. Można zdefiniować warunki takie jak dołączanie plików PHP, HTML, TPL, pomijanie plików logów, plików tymczasowych etc. i używać ich w kontekście różnych katalogów. Sposoby definiowania warunków w PatternSet są takie same jak w FileSet z wyjątkiem możliwości odwoływania się do typu PatternSet.

Przydatne linki:

Phing, czyli zautomatyzuj swoje zadania #1

Pracując przy projekcie programistycznym, oprócz pisania kodu trzeba też m.in. zarządzać projektem – może to być uruchomienie testów, skonfigurowanie projektu czy wdrożenie projektu etc. Z reguły każde z takich zadań wymaga wykonania jakichś operacji, często wielu i w określonej kolejności – w tym miejscu „zapala się lampka”. Takie operacje można, a nawet trzeba, zautomatyzować! Dzięki temu uniknie się błędów wynikających z rutyny, znudzenia wykonywaniem ciągle tych samych operacji, bo czas można poświęcić na ciekawsze sprawy.

Dla programistów Javy znajomo brzmi nazwa Ant. Dla programistów PHP istnieje podobne narzędzie o nazwie Phing. W obu przypadkach, w skrócie, chodzi o możliwość „oprogramowania” różnych zadań związanych z projektem i późniejsze tylko uruchamianie odpowiednich procedur, które aplikacja zrobi za użytkownika.

Tym wpisem chciałbym rozpocząć cały cykl wpisów na temat narzędzia Phing, a ponieważ to pierwszy wpis zacznijmy od podstaw.

Instalacja

Instalacja narzędzia jest możliwa na kilka sposobów, wszystkie opisane są na tej stronie: http://www.phing.info/trac/wiki/Users/Installation. Poprawność instalacji, można zweryfikować poleceniem w konsoli:

root@cim-k52:~# phing -version
Phing 2.6.1

Użycie

Użycie Phinga do zarządzania projektem opiera się na utworzeniu i wypełnieniu pliku build.xml. Jak wskazuje rozszerzenie jest to plik XML. Dzięki temu nie trzeba się uczyć nowej składni (wystarczy znajomość podstaw XML). Dodatkowo działanie Phinga jest niezależne od systemu operacyjnego (włącznie ze ścieżkami do plików). Oprócz build.xml bardzo przydatne są pliki właściwości (*.properties), które są źródłem danych dla skryptu1.

Na początek kilka „słów kluczowych” odnośnie projektu z perspektywy Phing:

  • project (projekt) – <project> to główny element w pliku build.xml, tzw. root node; definuje projekt, jego nazwę, opis, domyślny cel; dokumentacja
  • target (cel) – <target> definiowane cele, które będzie można później wywoływać, np. konfiguracja projektu, wdrożenie na środowisko, uruchomienie testów; cele składają się z wywołań zadań; dokumentacja
  • properties (właściwość) – <property> pozwala definiować właściwości, które w grunie rzeczy oznaczają zmienne skryptu; dokumentacja
  • task (zadanie) – to konkretne zadania, których wykonanie składa się na osiągnięcie celu, np. skopiowanie/przeniesienie pliku, wgranie na serwer; w dokumentacji można znaleść listę dostępnych zadań

Na koniec pierwszego wpisu testowe uruchomienie, poniżej treść pliku build.xml:

<?xml version="1.0" encoding="UTF-8"?>

<project name="HelloWorld" default="hello">

    <property name="who"  value="World" />

    <target name="hello">
	<echo>Hello ${who}</echo>
    </target>

</project>

Plik build.xml najlepiej umieścić w głównym katalogu projektu. Podstawowym sposobem użycia jest wywołanie w katalogu zawierającym plik skryptu z poziomu konsoli:

cim@cim-k52:~/public_html/Phing$ phing hello
Buildfile: /home/cim/public_html/Phing/build.xml

HelloWorld > hello:

     [echo] Hello World

BUILD FINISHED

Total time: 0.0559 seconds

W powyższym przypadku wywołanie phing i phing hello daje ten sam efekt, ponieważ cel hello jest domyślnym celem projektu.

Komunikaty na konsoli pozwalają śledzić operacje (cele i zadania) wykonywane przez Phing (co jest szczególnie przydatne przy wywoływaniu celów, które zależą od osiągnięcia innych celów). Na koniec pojawia się komunikat o „BUILD FINISHED” informujący o poprawnym zakończeniu pracy, w innym wypadku pojawi się „BUILD FAILED” ze szczegółowym komunikatem błędu.


1 zawartość pliku build.xml będę nazywał skryptem, jeżeli macie/znacie lepsze nazwy piszcie w komentarzach