2013-03-06

Testy jednostkowe - Instalacja PHPUnit

Zanim ochoczo zabiorę się do instalacji PHPUnita najpierw warto przypomnieć: po co to komu? :) Testy jednostkowe (ang. unit test) służą do częstego weryfikowania poprawności działania pojedynczych elementów programu takich jak np. obiekty. Przykładowo gdy piszemy klasę modelu korzystając z jakiegoś frameworka PHP, warto napisać dla niej Unit testy. Dzięki temu po każdej zmianie kodu aplikacji, możemy zyskać pewność, że kod działa poprawnie bez żmudnego manualnego testowania. Tak więc poddajemy fragment programu testowi, który wykonuje go i porównuje wynik (np. zwrócone wartości, stan obiektu, wyrzucone wyjątki) z wynikami oczekiwanymi. 
Co nam to daje, poza wysiłkiem implementacji test casów? Otóż zyskujemy możliwość błyskawicznego wychwycenia i poprawienia błędu - zanim jeszcze zostanie wprowadzony na środowisko produkcyjne.

OK to tyle jeśli chodzi o teorię - pora na praktykę. A w praktyce... cóż PHPUnit żądzi i basta :) Jest to swego rodzaju złoty standard w temacie testowania kodu PHP. Ale jak go zainstalować?

Zwyczajnie :) PEARem:

sudo pear config-set auto_discover 1
sudo pear install pear.phpunit.de/PHPUnit

W idealnym przypadku wystarczy następnie zainstalować PHPUnit
sudo apt-get install phpunit

i już można rozpocząć testy...
... niestety wielu użytkowników ubuntu napotka tu pierwsze schody, mi też się to przytrafiło. Po napisaniu przykładowego testu zabrałem się ochoczo do uruchomienia testu... lecz próba uruchomienia z terminala phpunit powodowała tylko komunikat:

PHP Fatal error: Call to undefined method PHP_CodeCoverage_Filter::getInstance() in /usr/bin/phpunit on line 39


Okazuje się, że niektóre wersje Ubuntu zawierają błąd na szczęście łatwo go samodzielnie naprawić uruchamiając po kolei komendy:

sudo pear config-set bin_dir /usr/bin
sudo pear config-set doc_dir /usr/share/php/doc
sudo pear config-set php_dir /usr/share/php
sudo pear config-set cfg_dir /usr/share/php/cfg 
sudo pear config-set data_dir /usr/share/php/data
sudo pear config-set test_dir /usr/share/php/test
sudo pear uninstall phpunit/PHPUnit
sudo pear channel-discover pear.phpunit.de
sudo pear install --alldeps phpunit/PHPUnit

2013-01-08

Odroid U2 - juz tydzień "zabawy"

Po tygodniu obcowania z odroidem U2 muszę stwierdzić - fajna zabawka ale raczej dla dużych dzieci... i to najlepiej z backgroundem informatycznym i ogromną dozą cierpliwości :) Jest to produkt developerski i jako taki nie pozbawiony błędów, nadal rozwijany. Nowe wersje softu wydawane są przez producenta co kilka dni. Na szczęście support na forum społeczności jest bardzo sprawny i nikt nie jest pozostawiany bez pomocy. Ja w pierwszym tygodniu użytkowania skupiłem się na konfiguracji androida - gdyż dla mnie Odroid ma pełnić funkcję HTPC oraz konsoli do gier. Z linuxem poczekam jeszcze parę dni, gdy większość bugów zostanie rozwiązanych. Pewną bolączką wydawanych nowych wersji softu Hardkernela jest konieczność flushowania całej karty pamięci i co za tym idzie utrata wszystkich ustawień i aplikacji. Gdy się to robi raz czy kilka razy dziennie - przestaje być już zabawne... dlatego cierpliwie poczekam.

Cierpliwością muszą się również wykazać domownicy użytkownika Odroida... Mam w domu jeden TV z wejsciem HDMI (stary monitor DVI się nie nadaje), dlatego podziwiam moją rodzinę, która cierpliwie znosiła moje klikanie bezprzewodową klawiaturą, ciągle restarty, gorączkowe ściąganie ROMu, przekładanie kart eMMC z odroida do czytnika, flash, itd. itd....
Przez ten tydzien przetestowałem pierwszą nieudaną wersję ICS, nstępnie JB w wersji od hardkernela i Cyanogenmod dla Androida. Ostatecznie zostałem przy najnowszym ICS gdzie jest zdecydowanie mniej bugów...

W obecnej chwili mam dwa zasadnicze problemy do rozwiązania. Odroid nie widzi mojego dysku USB 2.5 cala 1 TB Verbatim, problem z emulacją dotyku za pomocą myszki.

Trzeci problem z nie działającym padem bezprzewodowym XBOX360 rozwiązałem wpisując w terminal emulator:

su
mount -o remount,rw /system

Ponieważ niestety komenda cp nie działa... użyłem cat :)
cat /system/usr/keylayut/Vendor_045e_Product_028e.kl > Vendor_045e_Product_0719.k
chmod 644 /system/usr/keylayout/Vendor_045e_Product_0719.kl
reboot
Rozwiązanie to opiera się na tym, że w androidzie jest profil dla pada przewodowego - brakuje jedynie dla bezprzewodowego. Proste skopiowanie profilu pod inną nazwą rozwiązało problem.

2013-01-02

Odroid U2

W ubiegły poniedziałek nareszcie dotarł z korei mój developerski egzemplarz odroid U2. Wspaniały początek nowego roku :)
Ponieważ zamierzam używać go również jako HTPC z zasilanym z USB dyskiem 1TB, zamówiłem już wcześniej w polsce silniejszy 3A zasilacz (koreańczycy oferują standardowo 2A - a to prowadzi do niedoboru mocy w przypadku dużego obiążenia). Z duszą na ramieniu dokonałem pierwszego odpalenia... a tu nic, nie reaguje. Na szczęście po dociśnięciu wszystkich styków zasilacza wkońcu odpalił... uff :)

System (Android, ale można zaintalować również arm'ową wersję ubuntu) odpalił się dosłownie w kilka sekund. 4rdzeniowy procek samsunga w połączeniu z ultra szybką kartą eMMC robią swoje :)
Do szczęścia brakowało mi jeszcze aplikacji googla (głównie play store), których koreańczycy nie mogli dołączyć do dystrybucji ze względu na licencję. Na szczęście instalacja jest trywialna:

1. w pierwszym kroku należy się upewnić że SDK androida jest zainstalowane w systemie.
2. teraz pora ściągnąć sterownik odroida dla windows z forum społeczności hardkernela i można już podłączyć PC do portu micro USB maszynki
4. teraz z konsoli, gdzie rozpakoaliśmy archiwum należy wykonać polecenia:
adb remount
adb push system /system
adb reboot

Po zrestartowaniu odroid ma już dostęp do sklepu googla i możemy zacząć zabawę.... ,która niestety szybko się kończy... image androida aktualnie zawiera jakiś błąd i po kilkudziesięciu minutach maszyna się zawiesi, po kolejnym restarcie już nie wstaje. Po zflashowaniu karty "świerzym" obrazem,  system sie uruchamia ale ponownie się szybko zawiesi. Producent zna problem i aktualnie pracuje nad rozwiązaniem. Tymczasem więc "pobawię" się linuxem - który ma niestety jedną wadę - brak sprzętowej akceleracji dla procesora graficznego Mali (na Androidzie śmiga jak burza)... więc gdy np. chcemy obejrzeć film, całość obliczeń bierze na klatę 4 rdzeniowy proc... no ale przynajmniej jest stabliny :)


2012-12-12

REST in peace - projektowanie API webowego cz.1 base URL

Dobrze napisane API aplikacji webowej powinno być przyjazne dla developerów, którzy z niego będą korzystali. Osiągnięcie tego celu ułatwia stosowanie wzorca architektury REST (Representational State Transfer).

Jednym z najważniejszych elementów API jest jego adres URL. RESTowe podejście mówi: "Rzeczowniki są dobre, czasowniki są złe". Bazowy URL powinien być prosty i intuicyjny, dzięki temu użycie projektowanego API będzie łatwe. Powinny być tylko 2 bazowe URLe na zasób.

Koń jaki jest każdy widzi - a więc pora na przykład ;) 


/horses /horses/1234

Pierwszy url reprezentuje kolekcję, drugi: element kolekcji. Zamiast używać czasowników w adresie URL zastosujemy czasowniki HTTPowe : POST, GET, PUT, DELETE. Z ich pomocą API wykona podstawowe operacje CRUD (Create-Read-Update-Delete) właśnie w tej kolejności. Tak więc zamiast adresu /getAllHorses użyjemy: GET /horses, zamiast /updateHorse/1234 -> PUT /horses/1234 itd.

Poniższa tabela przedstawia podstawowe akcje na zasobie konika:
Zasób
POST
(dodaj)
GET
(czytaj)
PUT
(aktualizuj)
DELETE
(usuń)
/horses
Nowy koń
Lista koni
Bulk update koni
Usuń wszystkie konie
/horses/1234
Błąd
Pokaż siwka
Jeśli istnieje to zrób update siwka,
jeśli nie – Błąd
Usuń siwka
W efekcie programista nie będzie potrzebował dokumentacji aby zrozumieć jak działa API. 

2012-10-06

Composer - czyli jak zapanować nad zależnościami.

Ile razy zdarzyło Ci się "pałować" z dependencjami bibliotek projektu? Często aplikacja wymaga bibliotek w konkretnej wersji, do tego te biblioteki zależą od innych bibliotek, również w określonej wersji. Instalacja biblioteki globalnie w systemie, wiąże się z częstym problemem, gry upgrade biblioteki do wyższej wersji, wymaganej w w jednym projekcie wpływa na pozostałe projekty/biblioteki. 

Po bardzo ciekawej prezentacji "Composer - zarządzanie zależnościami w PHP" wygłoszonej przez Michała Pipa w miniony piątek na phpCon - postanowiłem utrawalić i usystematyzować wiedzę w temacie na blogu. 

Composer to fenomenalne narzędzie pozwalające zadeklarować biblioteki, od których zależy projekt. Composer sam ściągnie odpowiednie wersje bibliotek i zainstaluje. W momencie gdy dołączymy autoloadera composera do projektu 'vendor/autoload.php'; wszystkie biblioteki zostaną dołączone do projektu.

Instalacja jest banalna:
$ curl -s http://getcomposer.org/installer | php
$ sudo mv composer.phar /usr/local/bin/composer

Aktualizacja jest jeszcze prostsza - composer sam się zaktualizuje.
$ composer.phar self-update



Composer zarządza zależnościami, a nie pakietami. W pliku konfiguracyjnym możemy określić konkretną wersję biblioteki np. 1.0.1 lub operować wildcaredm * , np. gdy chcemy zawsze najnowszą wersję stabliną, która nie zmieniła się funkcjonalnie, wystarczy napisać: 1.0.* Warto zaznaczyć, że zgodnie ze standardem semver ostatnia cyfra oznacza jedynie bugfixing i nie wpływa funkcjonalność.

W praktyce wystarczy stworzyć plik composer.json :
{
    "require": {
        "monolog/monolog": "1.0.*"
    }
}

I uruchomić komendę:
$ composer.phar install

W efekcie zostaną utworzone:
  • Katalog vendor/
  • Plik composer.lock
  • Konfiguracja loadera (vendor/autoload.php)
Kilka słów o composer.lockTen plik bezwzględnie powinien być wersjonowany (razem z composer.json)! Wersjonowanie obu tych plików zapewnia, że każdy kto będzie instalował biblioteki dla projektu, zainstaluje tę samą ich wersję. Dzieje się tak dlatego, że polecenie install w pierwszej kolejności sprawdza wersję biblioteki w composer.lock; composer.json jest sprawdzany wyłącznie gdy composer.lock nie istnieje, a po sprawdzeniu jest on tworzony.

Polecenie update aktualizuje biblioteki do najnowszej wersji pasującej do definicje z composer.json i zapisuje te wersje w composer.lock;



Głównym repozytorium pakietów dla Composera jest Packegist



Choć Composer został pierwotnie zaprojektowany dla frameworka Symfony, to doskonale sprawdza się również w innych projektach. Np. dla CakePHP 2.x powstał bardzo łatwy w użyciu Plugin 

2012-09-05

użycie JQuery datepicker w symfony

Kilka dni temu na stackOverflow ktoś pałował się z użyciem w symfony sfWidgetFormDateJQueryUI niestety bez rezultatu... rozwiązaniem (jak się okazało skutecznym), które zaproponowałem było zastosowanie standardowego widgeta textowego z symfony (z id="date_of_birth") i dodanie w templatce widoku do slotu javascript kodu jQuery:

<?php append_to_slot('javascript'); ?>

  $(function() {
    $("#date_of_birth").datepicker();
  });

<?php end_append_to_slot(); ?>



2012-08-21

Follow the white RabbitMQ - czyli rozproszone przetwarzanie zadań

Często gdy przetwarzanie pewnych zadań w PHP po akcji użytkownika zajmuje cenny czas i zmusza do oczekiwania na ponowne wyrenderowanie widoku, warto wydelegować je do zewnętrznego workera. Typowym przykładem jest np. wysyłka wiadomości e-mail po zarejestrowaniu użytkownika, czy generowanie plików pdf. Można w tym celu posłużyć się taskami wywoływanymi z CRONa, ale zwłaszcza w systemach o większej skali, to rozwiązanie nie jest wystarczające.

Godnym polecenia systemem, który rozwiąże problem jest RabbitMQ.
Integracja z PHP okazuje się nie być tak banalna jak by się to mogło wydawać (a to głównie z uwagi na delikatnie mówiąc kulejącą dokumentację biblioteki AMQP)

Zacznijmy od instalacji króliczka, która na moim developerskim ubuntu była bardzo prosta:
sudo apt-get install rabbitmq-server

Następnie za pomocą PECLa instalujemy rozszerzenie AMQP do PHP:
sudo pecl install amqp

w php.ini uzupełniamy:
extension=ampq.so

UWAGA użytkownicy DEBIANA! Powyższe kroki były wystarczjące gdy instalowałem amqp pod Ubuntu, jednak podczas próby powtórzenia ich pod debianem squeeze wystąpiły problemy z instalację z pecl'a (komunikat "configure: error: Please reinstall the librabbit-mq distribution")
Rozwiązaniem jest wykonanie:
git clone git://github.com/alanxz/rabbitmq-c.git
cd rabbitmq-c
git submodule init
git submodule update
autoreconf -i
./configure
make
sudo make install

Naturalnie użytkownicy debiana squeeze mają do dyspozycji mega starą wersję servera rabbitmq, z którą nie będzie chciał gadać AMQP. Dlatego konieczne będzie wykonanie dodatkowych kroków:
Jeśli już zainstalowałeś starego rabbita - usuń go, aby uniknąć kolejnych przykrości :)

sudo apt-get remove rabbitmq-server --purge

następnie dodaj do /etc/apr/sources.list linijkę:
deb http://backports.debian.org/debian-backports squeeze-backports main

następnie update i instalacja backportu:
sudo apt-get update
sudo apt-get -t squeeze-backports install rabbitmq-server

Teraz należy skonfigurować RabbitMQ. Służy do tego konsolowy narząd rabbitmqctl, który dostajemy w paczce z królikiem. Po szczegóły odsyłam do dokumentacji rabbita, która w odróżnieniu od tej z PHP.net jest bardzo dobra i szczegółowa.

Dodajemy usera i host wirtualny, oraz ustawiamy uprawnienia:
sudo rabbitmqctl add_user butterfly butterfly
sudo rabbitmqctl add_vhost butterfly
sudo rabbitmqctl set_permissions -p butterfly butterfly ".*" ".*" ".*"

Warto jeszcze (przynajmniej na początku) włączyć logowanie dla naszego hosta i na jednej z konsol odpalić podgląd logu:
sudo rabbitmqctl trace_on -p butterfly
tail -f /var/log/rabbitmq/rabbit@Butterfly.log


Jesteśmy już gotowi do napisania klasy PHP, która będzie się komunikowała protokołem AMQP z serverem RabbitMQ:

/**
 * Class for AMQP Connections
 * 
 * @author gmotyl 
 */
class AMQPConnector {

  //Broker login credentials
  protected $login = "butterfly"
  protected $password = "butterfly";
  protected $vhost = "butterfly";
  
  //amqp connection variables
  private $amqpConnection;
  private $queue;
  private $exchange;
  private $routingKey;

  /**
   * Initializes amqpConnection and sets up queue and exchange
   * 
   * @param string $exchangeName
   * @param string $routingKey
   * @param string $queueName 
   */
  public function __construct($exchangeName, $routingKey, $queueName)
  {
    $this->amqpConnection = $this->connect();
    
    $channel = new AMQPChannel($this->amqpConnection);
    $this->exchange = new AMQPExchange($channel);
    
    $this->exchange->setName($exchangeName);
    $this->exchange->setType(AMQP_EX_TYPE_DIRECT);

    $this->queue = new AMQPQueue($channel);

    $this->queue->setName($queueName);
    $this->queue->declare();
    $this->queue->bind($exchangeName, $routingKey);    
    
    $this->routingKey = $routingKey;
  }
  
  public function __destruct() 
  {
    if(!$this->amqpConnection->disconnect()) {
      throw new Exception("Could not disconnect !");
    }
  }
  
  /**
   * Connects to broker
   * 
   * @return \AMQPConnection 
   */
  protected function connect() 
  {
    $amqpConnection = new AMQPConnection();

    $amqpConnection->setLogin($this->login);
    $amqpConnection->setPassword($this->password);
    $amqpConnection->setVhost($this->vhost);    
    $amqpConnection->connect();

    if(!$amqpConnection->isConnected()) {
      die("Cannot connect to the broker, exiting !");
    }

    return $amqpConnection;
  }
  
  /**
   * Returns Queue object
   * 
   * @return \AMQPQueue 
   */
  public function receiveQueue() 
  {
    return $this->queue;
  }
  
  /**
   * Publish a message to the exchange
   * Returns TRUE on success or FALSE on failure. 
   * 
   * @param string $text
   * @return bool 
   */
  public function sendMessage($text)
  {
    return $this->exchange->publish($text, $this->routingKey);;
  }
}