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);;
  }
}

2012-08-20

Backone.js dla bezkręgowców cz.4 - Bindowanie zdażeń

Pora na rozszerzenie klasy widoku o kod, który doda stworzone zadania do listy w HTMLu. W tym celu piszemy metodę insertTask, która za pomocą prostego jQuerowego kodu doda nowy element <li> do listy zadań. Zostało już tylko podbindowanie tej metody pod zdarzenie add kolekcji. Od tej pory zawsze gdy kolekcjia zwiększy się o kolejny model - jego reprezentacja zostanie zwizualizowana w HTMLu przez naszą klasę widoku:


$(function() {
  TaskEditView = Backbone.View.extend ({
    initialize: function() {
      console.log('Initializing task list view');

      this.collection.bind('add', this.insertTask);
    },

(...)

    insertTask: function(model) {
      var item = $('<li>')
        .html(model.get('name'));

      $('#taskList').append(item);
    }
  });

To było bardzo podstawowe wprowadzenie do framworka backbone.js, jeśli chcesz lepiej poznać tą technologię - zachęcam do lektury dokumentacji na backbonejs.org

Z doświadczenia wiem, że framework ten bardzo upraszcza bardziej skomplikowane projekty. Szczególnie jest to widoczne, podczas implementacji części funkcjonalności, która (przynajmniej częściowo) wpisuje się w definicję single-page application, gdzie kodu JSowego może być dość dużo.

Wszystkie kody z tego tutka są dostępne publicznie w repo bitBucket:

git clone https://bitbucket.org/gmotyl/backbone_tutorial.git
git clone git@bitbucket.org:gmotyl/backbone_tutorial.git

2012-08-15

Backone.js dla bezkręgowców cz.3 - Kolekcja

Kolekcja w Backbone to zwyczajnie lista modeli. W naszym przykładzie potrzebujemy modelu Task reprezentującego pojedyńcze zadanie z listy zadań. Kolekcja TaskCollection będzie zawierała listę zadań:

//Model
var Task = Backbone.Model.extend({
  initialize: function(){
    console.log("New Task initialized");
  }
});

//Kolekcja
var TaskCollection = Backbone.Collection.extend({
    model: Task
});

Naturalnie musimy teraz zainicjować kolekcję:


//Instancja Kolekcji
var tasks = new TaskCollection();

W kolejnym kroku zmodyfikujemy zadeklarowaną w poprzednim tutku instancję widoku, wstrzykując do niej nasz obiekt kolekcji zadań:


//Instancja widoku 
taskedit = new TaskEditView({
  el: 'body',
  collection: tasks
});

Teraz gdy mamy do dyspozycji w widoku obiekt kolekcji, możemy już nią swobodnie manipulować. Na początek zadbamy o uzupełnianie kolekcji o nowe zadania:


//klasa widoku listy zadań
$(function() {
  TaskEditView = Backbone.View.extend ({
    initialize: function() {
      console.log('Initializing task list view');
    },

    events: {
      "click #addTask" : "showPrompt"
    },

    showPrompt: function() {
      var taskName = prompt("Co chcesz dziś zrobić?");

      this.createNewTask(taskName);
    },

    createNewTask: function(taskName) {
      var taskItem = new Task({
        name: taskName
      });

      this.collection.add(taskItem);

      console.log(this.collection.toJSON());
    }
  });


Na końcu metody showPrompt dodaliśmy wywołanie createNewTask, która uzupełni kolekcję. Służy do tego metoda add kolekcji, jako parametr podajemy model. W tym celu stworzyliśmy instancję klasy Task, wstrzykując w nią podane przez użytkownika zadanie. Na końcu metody w celach szkoleniowych zapisujemy w konsoli Jsonową reprezentację kolekcji.


Teraz gdy podamy dwa kolejne zadania jako "Napisać kolejny odcinek tutka" i "Skosić trawnik", na konsoli wyświetli się :


Initializing task list view
New Task initialized
[Object { name="Napisać kolejny odcinek tutka"}]
New Task initialized
[Object { name="Napisać kolejny odcinek tutka"}, Object { name="Skosić trawnik"}]

cdn...

Wszystkie kody z tego tutka są dostępne publicznie w repo bitBucket:


git clone https://bitbucket.org/gmotyl/backbone_tutorial.git
git clone git@bitbucket.org:gmotyl/backbone_tutorial.git

2012-08-14

Backone.js dla bezkręgowców cz.2 - Widok

Dziś na tapetę weźmiemy przykład prostej klasy widoku backbone.js
Dla uproszczenia wszystkie kody zapiszemy w jednym pliku, naturalnie przy poważnych projektach będziemy chcieli mieć każdą klasę w osobnym pliku - co znacznie ułatwi "ogarnięcie" całości.

Stwórzmy więc gdzieś na serwerze index.html:
<!DOCTYPE html>
<html>
<head>
  <title>Lista zadań</title>
  <meta charset="utf-8" />
  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
  <script src="http://ajax.cdnjs.com/ajax/libs/underscore.js/1.3.1/underscore-min.js"></script>
  <script src="http://ajax.cdnjs.com/ajax/libs/backbone.js/0.9.2/backbone-min.js"></script>
</head>
<body>
  <button id="addTask">Dodaj zadanie</button>
  <ul id="taskList">
  </ul>
  
  <script type="text/javascript">
    //klasa widoku listy zadań będzie tutaj
  </script>
</body>
</html>


Zacznijmy od napisania szkieletu widoku:


//klasa widoku listy zadań
$(function() {
  TaskEditView = Backbone.View.extend ({
    initialize: function() {
      console.log('Initializing task list view');
    },

    events: {
      "click #addTask" : "showPrompt"
    },

    showPrompt: function() {
      var taskName = prompt("Co chcesz dziś zrobić?");
    }
  });

  taskedit = new TaskEditView({
    el: 'body'
  });
});

Jeśli czytasz uważnie, zauważysz w drugiej linii powyższego listingu konstrukcję $(function() {});   - jest to odpowiednik oklepanego $(document).ready(function () {}); Efekt jest dokładnie ten sam,  $() sprawi, że jQuery wewnętrznie wywoła za nas czerwony kod, użyliśmy jednak znacznie mniej znaków i tym samym przyczyniliśmy się do ochrony środowiska naturalnego :)

Nasza klasa widoku (nie mylić z nk.pl! :) ) składa się z funkcji initialize która zconsolloguje komunikat podczas instancjonowania obiektu (dla celów szkoleniowych oczywiście). Dalej zapinamy eventa na kliknięcie elementu o id #addTask (jQuerowe selectory działają w backbone). W efekcie po kliknięciu guzika powinna się wyświetlić nasza metoda showPrompt, która na razie jeszcze nic nie robi poza pokazaniem okna inputu, ale przecież nie od razu Rzym zbudowano :)

Naturalnie musimy stworzyć instancję klasy (linia 17), przekazujemy jako parametr el, 'body' - dzięki temu metoda events widoku będzie słuchała na całym body.

W backbone istnieje kilka specjalnych opcji które możemy przekazać do klasy widoku - są one dostępne bezpośrednio jako zmienne (np. w tym wypadku możemy wewnątrz dowolnej metody widoku odwołać się do elementu poprzez this.el lub skrótem do jQerowego obiektu this.$el.
Pozostałe specjalne opcje dostępne bezpośrednio w widoku to: model, collection, id, className, tagName i attributes - o większości z nich wspomnę napewno w kolejnych częściach tutka.

Backbone nie ogranicza nas (na szczęście) jedynie do wymienionych opcji, instancjonując widok możemy przekazać dowolne zmienne, tablice, obiekty itd. Będą one wówczas dosŧepne w widoku przez this.options.nazwanaszejopcji.

W kolejnym odcinku rozbudujemy klasę widoku i połączymy ją z modelem.

2012-08-13

Last minute commit

Planowałem dziś napisać kolejny odcinek tutka z backbone.js... Wracam wlasnie z pracy i pisze ten wpis na fonie...
Zostałem dziś dłużej, właśnie po to by dokończyć kod w wymienionym framrworku. Po 10h pisania czuję, że mam już powyżej uszu dzej-esa, a nie da się napisać o backbone nie dotykając JS. Pocieszam się tylko tym, że bez backbone moje dzisiejsze zadanie byłoby o wiele bardziej pracochłonne... i pewnie zastała by mnie ciemna noc w pracy.

Miałem dziś wyjść punktualnie
git push

i do domu... A tu nagle coś wybuchło...
Przy okazji naprawy błędu zrobiłem wspólnie z moim szefem refactoring backbonowego kodu i udało nam się skurczyć główną klasę o ponad 100 linii! A więc jednak warto było poświęcić te dodatkowe dwie godziny :)

posted from Bloggeroid

2012-08-10

Backone.js dla bezkręgowców cz.1

Ten post będzie początkiem krótkiego mini tutoriala wprowadzającego do JSowego frameworka Backbone.js

Wiem, że podobnych samouczków mnoży się nad każdym frameworkiem jak nie przymierzając much nad... ehmm... :) jednak ponieważ tak się składa, że ostatnio dość dużo w backbonie pisałem, to postanowiłem mimo wszystko dodać coś od siebie w temacie... i dla odmiany po polsku.

Tutaj mała dygresja : większość dobrych tutoriali jest w sieci po angielsku, polecam ich lekturę tym, którzy chcą naprawdę dobrze poznać nowe technologie (nota bene: trudno sobie wyobrazić programistę nie władającego językiem Shakespeare'a). Lecz zgodzi się chyba ze mną każdy polak, że miło czasem poczytać coś o technologii po polsku ;)

Ok wracając do meritum: Naukę Backobe.js proponuję zacząć (po przeczytaniu dokumentacji) od stworzenia pierwszego modelu:

Najpierw upewnijmy się że w hedzie strony dołączyliśmy biblioteki, backbone zależy tylko od underscore a więc:


Teraz można już zadeklarować pierwszy model:
  
MyModel = Backbone.Model.extend({
  initialize: function(){
    console.log("Hello world!");
  }
});
    
var modelInstance = new MyModel;

W ostatniej linii powyższego kodu instancjonujemy model MyModel, spowoduje to automatyczne wywołanie funkcji initialize i na konsoli pojawi się "Hello world!"

cdn...

2012-08-09

Kręgosłup JS

Nie samym PHP programista żyje. Prawie w każdym projekcie prędzej czy później zachodzi potrzeba użycia JSa. Nie ma problemu jeśli jest to jakiś pojedyńczy $.datepicker czy inny gotowy plugin. Schody zaczynają się gdy kodu do napisania/utrzymania mamy więcej.

Do dziś dnia bywa, że budzę się w nocy zlany zimnym potem, gdy nawiedza mnie koszmar związany z pewnym traumatycznym doświadczeniem. Miało to miejsce, gdy podczas pracy nad projektem, który miał już swoje lata, zaszła potrzeba "drobnej" modyfikacji kodu JS na jednej z podstron. Nie byłoby problemu, gdyby nie fakt, że plik ten miał ponad 2K linii kodu... który najpierw trzeba było przeczytać.

Na szczęście twórcy backone.js wyciągnęli pomocną dłoń do programistów zagubionych w mgle kodu JSowego. Framework ten pozwala na uporządkowanie kodu na wzór framowrków MVC znanych z PHP. Teraz utrzymanie nawet bardzo rozbudowanych aplikacji JS stało się o wiele prostsze. Polecam.