fbpx

Czym jest Phing? Mini automatyzacje dla maxi efektu

phing - biurko

Czym jest Phing? Mini automatyzacje dla maxi efektu

Ile razy dostawałeś szału, bo po raz kolejny musiałeś robić w kółko te same czynności. Dla przykładu odtworzenie bazy danych z fixtur. Nie jest to tylko jedna komenda na wczytanie fixtur, a kilka kroków do wykonania. Trzeba usunąć scheme, stworzyć ją na nowo, może wykonać jakieś migracje i dopiero wczytać fixtury. Po drodze przydałoby się jeszcze wyczyścić cache i tak dziesiątki razy, gdy np. pracujesz nad importem danych i testujesz, czy dane wchodzą poprawnie. Można dostać fioła. Jednak jest na to sposób – Phing!

Kolejna sytuacja. Gdy przychodzi nowy członek zespołu i musi postawić sobie środowisko albo jeszcze lepiej, musisz zmienić komputer lub robiłeś format. To chyba mój największy koszmar. Wtedy nic nie chce działać tak jak powinno. Z Symfony i tak sprawa jest prostsza, bo większość opiera się na prostych komendach, które wykonują się w miarę szybko więc nawet jeśli coś się wywali, to powtórzenie całego scenariusza nie jest jeszcze tak czasochłonne, ale w innych systemach to orka na ugorze. Mija kolejne pół godziny i dowiadujesz się, że nie wykonałeś jakiejś komendy zanim odpaliłeś następną, nie ma transakcyjności, więc wszystko musisz zaczynać od nowa.

Mała automatyzacja, a cieszy

Myślę, że niejeden programista stracił nerwy w takich sytuacjach i stąd pojawiło się coś takiego jak Continuous Delivery czy Continuous Integration, o których będę pisać bardziej szczegółowo w niedalekiej przyszłości. Jednak CD i CI to zwykle ogromne procesy, które ciągną za sobą całkiem duże infrastruktury systemów wspomagających wytwarzanie oprogramowania. To nie jest skala, która nas dzisiaj interesuje, na razie zajmijmy się własnym, lokalnym podwórkiem.

Od razu zapala się nam czerwona lampka ZAUTOMATYZUJMY TO! Dokładnie, masz racje, zautomatyzujmy to. Naprzeciw wychodzi nam Phing. Małe narzędzie, które ma wielka moc i cały czas dziwie się, że jest tak mało popularne. Jest skutecznie blokowany przez Ansible, podobne narzędzie, które może ma więcej możliwości, ale moim zdaniem jest dużo bardziej skomplikowane i mniej przyjemne w użyciu.

Czym jest Phing?

Phing, jak piszą twórcy, to narzędzie do budowania aplikacji opartych na PHP z wykorzystaniem plików xml. Dalej nic mi to nie mówi więc może po ludzku. W praktyce po zainstalowaniu Phinga w projekcie, co możemy zrobić za pomocą composera, interesuje nas najbardziej jeden plik – build.xml, który pojawia się w głównym katalogu projektu, jeśli się nie pojawił to skopiuj przykładowy plik zamieszczony poniżej. Oprócz tego, w katalogu /bin pojawi się skrypt phing.php bądź w vendorze, który pozwala na uruchamianie scenariuszy.

Instalacja

composer require phing/phing

Niektórzy rekomendują, aby instalować phinga z dopiskiem –dev czyli tylko na developerskich wersjach aplikacji, jednak ja nie widzę przeciwskazań aby był dostępny również na produkcyjnych kopiach, a dlaczego to opisze poniżej.

Jak budować scenariusze?

Plik build.xml wygląda następująco:

<?xml version="1.0" encoding="UTF-8"?>
<project name="myproject" basedir="." default="build">

</project>

Taka zawartość pliku niestety nam nic nie da, ponieważ nie zawiera ona żadnych scenariuszy. Określiliśmy jedynie nazwę projektu. Jeśli chcemy przygotować np. scenariusz odtworzenia środowiska lokalnego będzie on wyglądał np. tak:

<?xml version="1.0" encoding="UTF-8"?>
<project name="eCeremoniarz" basedir="." default="build">
    <property name="dir.console" value="php bin/console"/>

    <!-- Build -->
    <target name="build:local" depends="cache:clear, database:drop, database:create, database:migrate, database:fixture:dev , cache:clear" description="Local build plan"/>

    <!-- Database -->
    <target name="database:create" description="Create database"
        <echo msg="Create database..."/>
        <exec command="${dir.console} doctrine:database:create --no-interaction" logoutput="true" checkreturn="true" />
    </target>

    <target name="database:migrate" description="Migrate migrations">
        <echo msg="Executing database migrations..."/>
        <exec command="${dir.console} doctrine:migrations:migrate --no-interaction --allow-no-migration" logoutput="true" checkreturn="true" />
    </target>

    <target name="database:fixture:dev" description="Executes unit tests.">
        <echo msg="Fill database with fixtures ..."/>
        <exec command="${dir.console} doctrine:fixtures:load --group=dev --no-interaction"
              checkreturn="true"
              logoutput="true">
        </exec>
    </target>

    <target name="database:drop" description="Drop database">
        <echo msg="Drop database..."/>
        <exec command="${dir.console} doctrine:database:drop --no-interaction --force" logoutput="true" checkreturn="true" />
    </target>

    <!-- cache -->
    <target name="cache:clear" description="Clear cache">
        <echo msg="Start clearing cache"/>
        <exec command="${dir.console} cache:clear --no-interaction" logoutput="true" checkreturn="true" />
    </target>
</project>

Bardzo ważny jest element target, czyli cel, przeze mnie nazywany scenariuszem. Mogą to być pojedyncze komendy jak cache:clear czy database:migrate albo bardziej złożone jak build: local.

Wewnątrz elementu target możesz skonfigurować takie rzeczy jak echo, czyli tekst, który będzie wyrzucany w konsoli podczas wykonywania komendy, sprawdza się fajnie, kiedy mamy złożone scenariusze, ponieważ wiemy przy którym kroku aktualnie jesteśmy.  

Element exec to już docelowa komenda, która ma się wykonać np.

command=”${dir.console} cache:clear –no-interaction” logoutput=”true” checkreturn=”true”

W commandzie możemy użyć ${dir.console}  lub po prostu php bin/console.  Ja ustawiłam sobie property dir.console po to aby nie pisać za każdym razem bin/console nauczona doświadczeniem, że w starszym Symfony było app/console. Mając zdefiniowane property na samej górze jako dir.console przy upgradzie miałam tylko do wykonania zmianę w jednej linii. Nie jest to jednak wymagane.

Reszta przypisów oznacza dokładnie to co jest napisane, czyli nie będziemy wchodzić w interakcje więc musi poradzić sobie na wartościach domyślnych. W tym przypadku chcę, aby wyrzucił błąd na ekran, jeśli jakiś wystąpi i ma sprawdzić czy po zakończeniu wartość zwracana jest ok.

Trochę inaczej wyglądają scenariusze złożone takie jak build:local. Zawierają zapis depends, gdzie wypisujemy inne zdefiniowane targety, które wykonają się w zapisanej kolejności.

Jak używać phinga?

Gdy już mamy zbudowane scenariusze przydałoby się je jakoś wywołać. Nic prostszego. Wystarczy w konsoli wywołać komendę:

bin/phing <nazwa scenariusza>

np. bin/phing build:local

Kiedy używać Phinga?

W swojej codziennej pracy często wykorzystuje Phinga do upraszczania i przyspieszania pewnych kroków wytwarzania oprogramowania. Są to najczęściej:

Odbudowywanie środowiska developerskiego

Ile razy migracje w poszczególnych zadaniach zmieniają strukturę bazy tak, że realizując następne musisz stawiać bazę od nowa bądź cofać migracje? Pracując długi czas na tej samej bazie wyklinałeś już tyle śmieciowych danych, że musisz ją odświeżyć, bo kupy się to nie trzyma? Setki! W takiej sytuacji lubię przygotować sobie krótki scenariusz w phingu, który nie robi nic innego jak odbudowuje bazę, zapuszcza migracje i wrzuca fixtury żeby chociaż pojawił się użytkownik, na którego mogę się zalogować i voilà, środowisko bez większego wysiłku jest gotowe do pracy. W zależności od aplikacji wykonuje się to czasami dłuższą chwilę, ale za to w międzyczasie zdążę zrobić sobie świeżą kawę. 

Skomplikowane procesy importu/eksportu

Często hasło import i eksport, a co za tym idzie temat integracji spędza mi sen z powiek. To chyba najgorsze tematy jakie mogą się pojawić w aplikacji, ponieważ są bardzo nieprzewidywalne. Taki import produktów z pliku wydaje się dość prosty: mam plik -> odczytuje zgodnie ze strukturą -> przetwarzam -> wrzucam dane do nowego systemu. Zawsze, ale to zawsze pojawiają się problemy typu: a co ze zdjęciami do produktów? A Kategorie? Dodatkowe atrybutu? Może jakieś powiązane produkty? Na koniec coś najgorszego, czyli poprawność danych? No właśnie i zaczynają się schody.

Kiedy realizujemy taki import czy eksport to już jedna i ta sama bajka, najczęściej jest to proces składający się z pewnych kroków. Można go oczywiście zrealizować na milion różnych sposobów i spiąć np. jedną komendą, ale można również zrobić takich komend wiele do poszczególnych kroków. Jeśli wybierzemy tą drugą opcję Phing sprawdzi się tutaj doskonale, aby spiąć to w całościowy proces i prezentować nawet w trakcie jego postęp.

Stawianie środowiska od zera, zarówno developerskiego, jak i produkcyjnego

Tak jak pisałam wcześniej niektórzy rekomendują instalacje phinga jedynie na środowiskach developerskich. Ja nie zawsze, ale zdarza mi się używać go również na środowiskach produkcyjnych. Dlaczego? Bo po co sobie komplikować. Oczywiście, jeśli mam w projekcie przygotowaną infrastrukturę do CD i CI jak np. Bitbucket Pipelines, Bamboo czy Jenkinsa to wtedy nie jest mi potrzebny Phing. Jednak, gdy nie mam takiej możliwości albo projekt jest mały i czas spędzony na przygotowanie tego wszystkiego nie jest opłacalny to po co kombinować. Miałam okazję pracować z małą aplikację typu Saas, gdzie każdy klient dostaje swoją kopię na wyizolowanym środowisku. Skala jest na tyle niewielka, że nie opłaca się wdrożenie całego CD, a na tyle duża, że ręcznie doprowadzało mnie to do szewskiej pasji. W takiej sytuacji Phing sprawdził mi się bezbłędnie.

Testy i statyczna analiza kodu

Bardzo fajnym wykorzystaniem phinga jest np. dbanie o jakość kodu i jej stabilność. Jeśli w aplikacji mamy napisane testy PHPUnita i/lub Behata to możemy je zgrupować w scenariusz tests:run i odpalać na swoich zmianach zanim je wrzucimy na repo. Zaoszczędzi nam to sporo czasu na czekanie aż automaty w naszej infrastrukturze CD i CI je odpalą. Jeśli nie mamy CD lub CI to już w ogóle warto przygotować sobie coś takiego co będzie nam w pewnym sensie imitować automatykę i zadba o stabilność aplikacji.

Przykładowy scenariusz dla phpunita:

<target name="test:unit" description="Executes unit tests.">
    <echo msg="Running unit tests..."/>
    <exec command="bin/phpunit -c phpunit.xml.dist"
          checkreturn="true"
          logoutput="true"
          passthru="true"
    >
    </exec>
</target>

Analogicznie wygląda sytuacja z jakością kodu. Dodałam przykład z uruchomienia Code Sniffera ale równie dobrze może to być PHPStan, Mass Detector czy inne narzędzia. Jeśli nie wiesz o czym mówię, to się nie przejmuj, niedługo pojawi się post, gdzie wyjaśnię szczegółowo czym są i po co je stosować.

Przykładowy scenariusz dla codesniffera

<target name="check:cs" description="PHPCS checker">
    <phpcodesniffer haltonerror="true" standard="${project.basedir}/vendor/escapestudios/symfony2-coding-standard/Symfony2"
                    allowedFileExtensions="php"
                    ignorePatterns="autoload.php,Test.php"
                    showSniffs="false"
                    showSources="false"
                    showWarnings="true"
                    verbosity="0">
        <fileset dir="${dir.src}">
            <include name="**/*.php"/>
        </fileset>
        <fileset dir="${dir.component}">
            <include name="**/*.php"/>
        </fileset>
        <formatter type="full" usefile="false"/>
        <formatter type="checkstyle" outfile="${dir.reports}/checkstyle-cs.xml"/>
    </phpcodesniffer>
</target>

To co najbardziej lubię w Phingu to właśnie prostota. Nie ma tu nic przekombinowanego, jasny i czytelny xml, w którym składamy wszystko jak z klocków i cel osiągnięty. Proszę o więcej takich narzędzi.

Bardzo ciekawi mnie czy używałeś kiedykolwiek phinga w swoich projektach?  A może to dla Ciebie nowość?

Zgarnij darmowy ebook i cotygodniową dawkę wiedzy

.
Magdalena Limanówka-Kuciel
magdalena@panizkomputerem.pl

Jestem programistką, która lubi mieć ręce pełne roboty. Do życia potrzebuje komputera z internetem i kubka gorącej kawy. Więcej na stronie o mnie.