Wprowadzenie
Symfony framework jest projektem Open-Source przez ponad trzy lata i stał się jednym z najbardziej popularnych frameworków PHP dzięki swoim cechom oraz dokumentacji. Ta wspaniała tradycja była od samego początku.
W grudniu 2005, tuż pod pierwszej oficjalnej publikacji symfony, opublikowaliśmy "Askeet tutorial", zestaw 24 tutoriali, publikowany dzień po dniu między 1 grudnia i Bożym Narodzeniem.
Ten tutorial udowodnił swoją nieocenioną wartość jako narzędzie promujące framework osobom rozpoczynającym z nim przygodę. Dużo deweloperów nauczylo się korzystać z symfony dzięki askeet i wiele firm nadal używa askeet jako główny materiał treningowy.
Ale tutorial askeet zaczyna okazywać się za stary wraz z wypuszczeniem symfony 1.2, więc zdecydowaliśmy się opublikować kolejny, Jobeet.
Ten tutorial był publikowany każdego dnia na blogu symfony w 2008 i czytasz jego książkowe wcielenie.
Wyzwanie
Każdy rozdział/dzień powinien zająć około 1 godziny i będzie okazją do nauczenia się symfony kodując prawdziwą stronę od początku do końca.
Jedna godzina razy dwadzieścia cztery równa się jednemu dniowi, a to jest dokładnie tyle ile trzeba deweloperowi, żeby nauczyć się podstaw symfony. Każdego dnia nowe elementy będą dodawane do aplikacji i wykorzystamy ten rodzaj kodowania, żeby przedstawić Ci nowe funkcjonalności symfony jak również dobre praktyki w tworzeniu projektów z pomocą symfony.
Przy askeet, 21-szy dzień był dniem "get-a-symfony-guru-for-a-day" (użyj-symfony-guru-przez-dzień). Nie mieliśmy ustalonego planu i społeczność musiała zaproponować funkcjonalność do dodania do askeet. Był to wielki sukces i społeczność zdecydowała, że potrzebny jest silnik wyszukujący w aplikacji. Oczywiście zrobiliśmy go. Tutorial z 21-go dnia okazał się być jednym z najbardziej popularnych ze wszystkich tutorialu w askeet.
W Jobeet swiętowaliśmy zimę 21-go dniem projektowania layoutu. Zwycięski layout był wysłany przez centre{source} i jest użyty w tym tutorialu jako domyślny szblon. Jest również używany na stronie Jobeet.
Ten Tutorial jest inny
Pamiętasz wczesne dni PHP4. Coż za Piękna epoka! PHP był jedym z pierwszych języków dedykowanym do zastosowań webowych oraz jednym z łatwiejszych do opanowania.
Wraz z wielkim tempem rozwoju technologii webowych deweloperzy muszą dotrzymywać kroku najświeższym narzędziom oraz najlepszym praktykom. Najlepszym sposobem nauki jest oczywiście czytanie blogów, tutoriali i książek. Przeczytaliśmy wiele z nich, napisanych dla języków PHP, Python, Java, Ruby, or Perl, i wiele z nich okazywało się nie spełniać oczekiwań, gdy autor zaczynał podawać jako przykłady fragmenty kodu.
Z pewnością jesteś przyzwyczajony do czytania uwag typu:
"W prawdziwej aplikacji nie zapomnij dodać walidację i odpowiednią obslugę błędów."
lub
"Zabezpieczenia zostawiono czytelnikowi jako forma ćwiczeń."
lub
"Oczywiście trzeba będzie napisać testy."
Co? Te rzeczy są bardzo ważne. Prawdopodobnie są najważniejszą częścią każdego fragmentu kodu. I jako czytelnik jesteś zostawiony sam sobie. Dopóki nie rozważy się tych spraw przykłady są o wiele mniej użyteczne. Nie można ich używać jako dobry punkt startu. To bardzo źle! Dlaczego? Ponieważ bezpieczeństwo, walidacja, obsługa błędów i testy, dbają o to, żeby kod był dobry.
W tym tutorialu nie zobaczysz tego typu uwag, ponieważ napiszemy testy, obsługę błędów, kod walidujący by być pewnym, że tworzymy bezpieczną aplikację. To dlatego, że symfony dotyczy kodowania, ale również najlepszych praktyk i jak tworzyć profesjonalne aplikacje dla przedsiębiorstw. Będziemy mogli pozwolić sobie na ten luksus, ponieważ symfony zapewnia nam wszystkie narzędzia potrzebne do kodowania tych aspektów łatwo bez pisania dużej ilości kodu.
Walidacja, obsługa błędów, bezpieczeństwo i testy są w symfony określane jako first-class citizens, więc nie będzie to potrzebowało wiele wyjaśnień. To jest jeden z wielu powodów dlaczego lepiej używać frameworka do prawdziwych projektów.
Cały kod, który przeczytasz w tutorialu jest możliwy do wykorzystania w prawdziwym projekcie. Zachęcamy do kopiowania fragmentów kodu lub nawet calych kawałków.
Projekt
Tworzona aplikacja mogła być kolejnym silnikiem blogowym. Ale chcemy użyć symfony do stworzenia użytecznego projektu. Celem jest pokazanie, że symfony może być użyta do stworzenia profesjonalnych aplikacji przy małym wysiłku.
Zawartość projektu pozostanie sekretem przez następny dzień, bo mamy dzisiaj jeszcze dużo do zrobienia. Jednak już znacie nazwę aplikacji: Jobeet.
Co na dzisiaj?
Ponieważ 24 godziny to dużo czasu by stworzyć aplikację z symfony, nie będziemy pisać kodu PHP dzisiaj. Ale nie pisząc nawet jednej linii kodu zaczniecie rozumieć jakie płyną korzyści z korzystania z frameworka takiego jak symfony tworząc nowy projekt.
Celem na dzisiaj jest ustawienie środowiska deweloperskiego i wyświetlenie strony aplikacji w przeglądarce internetowej. Łączy się z tym instalacja symfony, stworzenie aplikacji i konfiguracja serwera.
Warunki początkowe
Najpierw sprawdź czy masz działające środowisko webowe z serwerem webowym (np. Apache), bazę danych (MySQL, PostgreSQL, SQLite), PHP 5.2.4 lub nowszy.
Z uwagi, że będziemy używać konsoli bardzo często dobrze jest używać Unixo-podobnego
systemu operacyjnego, ale jeśli będziesz używać Windows też będzie dobrze, będziecie musieli
wpisać kilka poleceń w cmd
wierszu poleceń.
note
Unixowe polecenie shellowe mogą być bardzo przydatne pod Windows.
Jeśli zechcesz używać narzędzi takich jak tar
, gzip
lub grep
w Windows
możesz zainstalować Cygwin. Oficjalna dokumentacja jest lekko
rozsiana, więc dobry poradnik można znaleźć tutaj.
Ci, którzy lubią urozmaicenia mogą spróbować narzędzia Microsoftu
Windows Services for Unix.
Z uwagi na to, że ten tutorial głównie będzie się skupiał na frameworku symfony, zakładamy, że już posiadasz wiedzę na temat PHP 5 i OOP (Object Oriented Programming).
Instalacja Symfony
Najpierw, utwórz katalog w którym będą się znajdowały pliki z projektem Jobeet:
$ mkdir -p /home/sfprojects/jobeet $ cd /home/sfprojects/jobeet
W Windows:
c:\> mkdir c:\development\sfprojects\jobeet c:\> cd c:\development\sfprojects\jobeet
note
Użytkownikom Windows zalecamy używanie ścieżek bez spacji.
Unikaj używania Documents and Settings
, jak również folderu Moje Dokumenty
.
Utworz katalog dla plików frameworka symfony:
$ mkdir -p lib/vendor
Żeby zainstalować symfony, pobierz plik archiwum znaleziony na stronie symfony. Ponieważ ten tutorial został napisany dla symfony 1.2, pobierz najnowszą dostępną wersję z tej gałęzi (musi to być minimum wersja 1.2.1).
W sekcji "Source Download" znajdziesz archiwum w formacie .tgz
lub .zip
.
Ściągnij archiwum, umieść je w nowo utworzonym katalogu lib/vendor
i rozpakuj:
$ cd lib/vendor $ tar zxpf symfony-1.2-1.tgz $ mv symfony-1.2.1 symfony
Pod Windows można rozpakować archiwum za pomocą explorera. Po tym jak
zmienisz nazwę katalogu na symfony
, powinien być widoczny następujący katalog
c:\development\sfprojects\jobeet\lib\vendor\symfony
.
Z powodu, że konfiguracja PHP rózni się bardzo między różnymi dystrybucjami, musimy sprawdzić czy Twoja konfiguracja PHP spełnia minimalne wymagania symfony. Uruchom skrypt sprawdzający konfigurację dołączony do symfony z konsoli:
$ cd ../.. $ php lib/vendor/symfony/data/bin/check_configuration.php
Jeśli będzie jakiś problem komunikaty na ekranie będą zawierały wskazówki jak je naprawić. Skrypt powinien rownież zostać uruchomiony z poziomu przeglądarki, ponieważ konfiguracja PHP może być inna. Skopiuj plik gdzieś do katalogu dostępnego dla serwera i wywołaj z przeglądarki. Nie zapomnij potem go usunąć.
Jeśli skrypt nie sygnalizuje żadnych błędów sprawdź czy symfony została poprawnie zainstalowana
używając konsoli symfony by wyświetlić wersję (używana jest duża litera V
):
$ php lib/vendor/symfony/data/bin/symfony -V
W Windows:
c:\> cd ..\.. c:\> php lib\vendor\symfony\data\bin\symfony -V
Jeśli chcesz wiedzieć co to narzędzie konsolowe potrafi jeszcze, wpisz
symfony
, żeby wyświetlić listę dostępnych opcja i zadań (tasków):
$ php lib/vendor/symfony/data/bin/symfony
W Windows:
c:\> php lib\vendor\symfony\data\bin\symfony
Wiersz poleceń symfony jest najlepszym przyjacielem dewelopera. Dostarcza wielu pożytecznych narzędzi, które zwiększają Twoją produktywność w codziennych czynnościach tj. czyszczenie cache, generowanie kodu i wiele innych.
Ustawienie Projektu
W symfony, aplikacje dzielące między sobą ten sam model danych są pogrupowane w projekty. Dla projektu Jobeet będziemy mieć dwie aplikacje: frontend i backend.
Tworzenie Projektu
Z poziomu katalogu jobeet
uruchom polecenie symfony generate:project
,
utworzy ono właściwy projekt symfony:
$ php lib/vendor/symfony/data/bin/symfony generate:project jobeet
W Windows:
c:\> php lib\vendor\symfony\data\bin\symfony generate:project jobeet
Polecenie generate:project
generuje domyślną strukturę katalogów i plików
potrzebnych dla projektu w symfony:
Katalog | Opis |
---|---|
apps/ |
Przechowuje wszystkie aplikacje projektu |
cache/ |
Pliki zcacheowane przez framework |
config/ |
Pliki konfiguracyjne projektu |
lib/ |
Biblioteki i klasy projektu |
log/ |
Pliki logów frameworka |
plugins/ |
Zainstalowane pluginy |
test/ |
Pliki testów jednostkowych i funkcjonalnych |
web/ |
Główny katalog webowy (patrz poniżej) |
note
Po co symfony generuje tak wiele plików? Jedną z głównych korzyści używania pełnego frameworka jest standaryzacja tworzenia Twoich projektów. Dzięki domyślnej strukturze plików i katalogów symfony każdy deweloper mający trochę wiedzy o symfony może zająć się utrzymaniem każdego projektu symfony. Kwestią minut jest zaznajomienie się z kodem, naniesienie poprawek, i dodanie nowych fukncjonalności.
Polecenie generate:project
dodatkowo stworzyło skrót symfony
w głownym katalogu
projektu Jobeet by skrócić ilość wpisywanych znaków, gdy chcemy uruchomić jakieś
polecenie.
Więc od teraz zamiast używać pełnej ścieżki do symfony będziemy używać skrótu symfony
.
Tworzenie Aplikacji
Teraz utworzymy aplikację frontend uruchamiając polecenie generate:app
:
$ php symfony generate:app --escaping-strategy=on --csrf-secret=UniqueSecret frontend
tip
Ponieważ plik skrótu symfony jest oznaczony jako wykonywalny, użytkownicy Unixów mogą
używać od teraz ./symfony
zamiast php symfony
.
W Windows możesz skopiować plik symfony.bat
do swojego projektu i uzywać symfony
zamiast php symfony
:
c:\> copy lib\vendor\symfony\data\bin\symfony.bat .
Bazując na nazwie aplikacji podanej jako argument polecenie generate:app
tworzy
domyślną strukturę katalogów potrzebną dla aplikacji w katalogu apps/frontend
:
Katalog | Opis |
---|---|
config/ |
Pliki konfiguracyjne aplikacji |
lib/ |
Biblioteki i klasy aplikacji |
modules/ |
Kod aplikacji (MVC) |
templates/ |
Pliki szablonów globalnych |
tip
Wszystkie polecenia symfony
muszą być uruchamiane z poziomu głównego katalogu projektu
chyba, że zostanie powiedziane inaczej.
Wywołując polecenie generate:app
dodatkowo przekazano dwa parametry związane z bezpieczeństwem:
--escaping-strategy
: Włącza output escaping zapobiegając atakom XSS--csrf-secret
: Włącza tokens sesji w formularzach zapobiegając atakom CSRF
Przekazując te dwa opcjonalne parametry zabezpieczyliśmy dalszą pracę przed dwoma najbardziej rozposzechnionymi zagrożeniami w sieci. Tak jest, symfony zajmie się automatycznie zabezpieczeniami za nas.
Ścieżka symfony
Można sprawdzić wersję symfony używaną w projekcie wpisując:
$ php symfony -V
Parametr -V
dodatkowo wyświetla ścieżkę do katalogu instalacyjnego symfony,
który można znaleźć w config/ProjectConfiguration.class.php
:
// config/ProjectConfiguration.class.php require_once '/Users/fabien/work/symfony/dev/1.2/lib/autoload/sfCoreAutoload.class.php';
Dla zwiększenia przenośności jest lepiej zmienić ścieżkę bezwzględną na względną:
// config/ProjectConfiguration.class.php require_once dirname(__FILE__).'/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';
W ten sposób można przenieść katalog projektu Jobeet gdziekolwiek na naszej maszynie lub na inną i dalej będzie dzialać.
Środowiska
Jeśli zajrzysz do katalogu web/
znajdziesz tam dwa pliki PHP:
index.php
and frontend_dev.php
. Te pliki to tzw. front kontrolery:
wszystkie wywołania do aplikacji przechodzą przez nie. Lecz dlaczego
mamy dwa front kontrolery jeśli stworzono tylko jedną aplikację?
Oba pliki wskazują na tą samą aplikację, ale na różne środowiska. Gdy tworzysz aplikację, chyba że tworzysz bezpośrednio na serwerze produkcyjnym, potrzebujesz paru środowisk:
- Środowisko deweloperskie: Używane przez web deweloperów, gdy pracują nad aplikacją, do tworzenia nowych funkcjonalności, poprawianie błędów, ...
- Środowisko testowe: Używane do automatycznego testowania aplikacji.
- Środowisko inscenizacyjne: Używane przez klienta do testowania aplikacji i zgłaszania błędów i brakującyh funkcjonalności.
- Środowisko produkcyjne: Używane do interakcji z użytkownikiem końcowym.
Co sprawia, że środowisko jest unikalne? W środowisku deweloperskim aplikacja musi zapisywać wszystkie szczegóły requestów, żeby ułatwić debugowanie, ale system cacheowania musi być wyłączony, ponieważ wszystkie zmiany w kodzie muszą być widoczne od razu. Więc, środowisko deweloperskie musi być zoptymalizowane dla dewelopera. Najlepszym przykładem jest moment wystąpienia wyjątku. By pomóc zdebugować problem szybciej symfony wyświetla go wraz ze wszystkimi informacjami na temat teraźniejszego requestu w oknie przeglądarki:
Ale w środowisku produkcyjnym cache musi być aktywny i oczywiście aplikacja musi wyświetlać dostosowane komunikaty o błędach zamiast wypluwać czyste wyjątki. Więc, środowisko produkcyjne musi być zoptymalizowane na wydajność i doświadczenia użytkownika.
Środowisko w symfony jest unikalnym zestawem ustawień konfiguracyjnych. Framework
symfony ma w sobie od razu zdefiniowane trzy środowiska: dev
(deweloperskie), test
(testowe), oraz prod
(produkcyjne).
Jeśli otworzysz pliki front kontrolera zobaczysz, że jedyna różnica jest w ustawieniu środowiska:
// web/index.php <?php require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php'); $configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'prod', false); sfContext::createInstance($configuration)->dispatch();
note
Zdefiniowanie nowego środowiska symfony jest tak proste jak stworzenie nowego front kontrolera. Zobaczymy później jak zmieniać ustawienia dla środowiska.
Ustawienia Web Serwera: Ten Brzydszy Sposób
W poprzedniej sekcji stworzony został katalog do przechowywania projektu Jobeet. Jeśli stworzono go gdzieś w katalogu dostępnym dla serwera to można już się dostać do projektu z przeglądarki.
Oczywiście, nie ma konfiguracji, jeśli jest ona szybka do ustawienia, ale spróbuj
się dostać do pliku config/databases.yml
przez przeglądarkę to zrozumiecie
bardzo złe konsekwencje takiej leniwej postawy. Jeśli użytkownik wie, że Twoja
strona jest zrobiona za pomocą symfony, będzie miał dostęp do wielu ważnych plików.
Nigdy, przenigdy nie używaj tych ustawień na serwerze produkcyjnym i przeczytajcie następną sekcję, żeby nauczyś się jak skonfigurować serwer właściwie.
Ustawienia Web Serwera: Bezpieczny Sposób
Dobrą praktyką jest umieszczanie w katalogu głównym serwera tylko tych plików,
które muszą być dostępne przez serwer, tj. arkusze stylów, JavaScripty lub
obrazki. Domyślnie zalecamy przechowywanie tych plików w katalogu web
projektu w symfony.
Jeśli spojrzysz na ten katalog, zobaczysz parę podkatalogów na pliki (css/ i images/) oraz dwa pliki front kontrolerów. Pliki front kontrolera to jedyne pliki PHP, które muszą być dostępne w katalogu głównym serwera. Wszystkie pozostałe pliki PHP mogą być ukryte przed przeglądarką, co jest oczywiście dobre jeśli zależy nam na bezpieczeństwie.
Ustawienia Web Serwera
Nadszedł czas zmiany ustawień serwera Apache, żeby nowy projekt był dostępny dla świata.
Znajdź i otwórz plik konfiguracyjny httpd.conf
i dodaj poniższą konfigurację
na końcu:
# Upewnij się, że ta linia występuje tylko raz w Twojej konfiguracji NameVirtualHost 127.0.0.1:8080 # To jest konfiguracja dla Jobeet Listen 127.0.0.1:8080 <VirtualHost 127.0.0.1:8080> DocumentRoot "/home/sfprojects/jobeet/web" DirectoryIndex index.php <Directory "/home/sfprojects/jobeet/web"> AllowOverride All Allow from All </Directory> Alias /sf /home/sfprojects/jobeet/lib/vendor/symfony/data/web/sf <Directory "/home/sfprojects/jobeet/lib/vendor/symfony/data/web/sf"> AllowOverride All Allow from All </Directory> </VirtualHost>
note
Alias /sf
daje dostęp do obrazków i plików javascript potrzebnych
do poprawnego wyświetlenia domyślnych stron symfony i paska narzędzi do debugowania.
W Windows musisz zamienić linię z Alias
na coś tego typu:
Alias /sf "c:\development\sfprojects\jobeet\lib\vendor\symfony\data\web\sf"
A /home/sfprojects/jobeet/web
powinno być zamienione na:
c:\development\sfprojects\jobeet\web
Taka konfiguracja sprawia, że Apache nasłuchuje na porcie 8080
na Twojej maszynie,
więc strona Jobeet będzie dostępna pod takim adresem URL:
http://localhost:8080/
Możesz zmienić 8080
na jakąkolwiek liczbę, ale preferuj używanie liczb powyżej 1024
,
bo one nie wymagają praw administratora.
Test nowej konfiguracji
Zrestartuj serwer Apache i sprawdź czy masz dostęp do nowej aplikacji wpisując
w przeglądarce http://localhost:8080/index.php/
lub http://jobeet.localhost/index.php/
zależnie od wybranej wcześniej konfiguracji serwera Apache.
note
Jeśli masz zainstalowany na serwerze Apache moduł mod_rewrite
możesz usunąć
część /index.php/ ze wszystkich adresów URL. Jest to możliwe dzięki regułom przepisywania
skonfigurowanym w pliku web/>htaccess
.
Dodatkowo spróbuj się dostać do aplikacji w środowisku deweloperskim. Wpisz poniższy adres URL:
http://jobeet.localhost/frontend_dev.php/
Pasek debugowania powinien być widoczny w prawym górnym rogu z dodatkowymi
małymi ikonami, co pokazuje, że Twója konfiguracja aliasu sf/
jest prawidłowa.
Pasek debugowania jest dostępny na wszystkich stronach w środowisku deweloperskim i daje dostęp do dużej ilości informacji po kliknięciu na różnych zakładkach: wykonane zapytania SQL, informacje o pamięci i o czasie.
note
Ustawienia są trochę inne jeśli chcesz uruchomić symfony na serwerze IIS pod Windows. Konfigurację taką znajdziesz w powiązanym tutorialu.
Subversion
Dobrą praktyką jest używać systemu kontroli wersji w czasie tworzenia aplikacji. Używanie takiego systemu pozwala na:
- pewną pracę
- cofanie do poprzedniej wersji jeśli zmiana zepsula coś innego
- pozwala na pracę nad projektem więcej niż jednej osobie w wydajny sposób
- dostęp do wszystkich poprzednich wersji aplikacji
W tej sekcji opiszemy jak używać Subversion z symfony. Jeśli używasz innego narzędzia, to musi być łatwe do zaadoptowania naszego opisu dla Subversion.
Zakładamy, że masz już dostęp do serwera Subversion i możesz dostać się do niego za pomocą HTTP.
tip
jeśli nie masz serwera Subversion możesz utworzyć go za darmo na Google Code lub poszukaj frazy "free subversion repository" w Google, aby poszukać kolejnych opcji.
Najpierw utwórz repozytorium dla projektu jobeet
na serwerze:
$ svnadmin create /path/to/jobeet/repository
Na swojej maszynie stwórz podstawową strukturę katalogów:
$ svn mkdir -m "created default directory structure" http://svn.example.com/jobeet/trunk http://svn.example.com/jobeet/tags http://svn.example.com/jobeet/branches
Potem usuń zawartość katalogów cache/
i log/
, ponieważ nie chcemy ich umieszczać
w repozytorium.
$ cd /home/sfprojects/jobeet $ rm -rf cache/* log/*
Teraz upewnij się, żeby były ustawione odpowiednie uprawnienia do zapisu w katalogach cache and logs, aby serwer mógł zapisywać w nich bez problemu:
$ chmod 777 cache/ log/
Teraz zrób pierwszy import:
$ svn import -m "made the initial import" . http://svn.example.com/jobeet/trunk
Ponieważ nigdy nie będziemy chcieli commitować plików w katalogach cache/
i log/
należy zdefiniować svn:ignore:
$ svn propedit svn:ignore cache
Domyślny edytor tekstowy skonfigurowany dla SVN powinien się uruchomić. Subversion musi ignorować całą zawartość w tym katalogu:
*
Zapisz i wyłącz. Gotowe.
Powtórz to samo dla katalogu log/
:
$ svn propedit svn:ignore log
I wpisz:
*
Na koniec, zacommituj te zmiany do repozytorium:
$ svn commit -m "added cache/ and log/ content in the ignore list"
tip
Użytkownicy Windows mogą używać TortoiseSVN do zarządzania swoim repozytorium.
Do zobaczenia jutro
Cóż, koniec czasu na dziś! Nawet jeśli nie zaczęliśmy jescze mówić o symfony, skonfigurowaliśmy solidne środowisko deweloperskie, powiedzieliśmy o dobrych praktykach i jesteśmy gotowi by zacząć kodowanie.
Jutro odkryjemy co aplikacja będzie robić i porozmawiamy o wymaganiach, które będą do zaimplementowania w czasie tutoriala.
note
Jeśli chcesz pobrać kod na dzisiaj lub na inny dzień, jest on dostępny
codziennie w repozytorium SVN Jobeet (http://svn.jobeet.org/
).
Na przykład możesz pobrać dzisiejszy kod checkoutując tag release_day_01
:
$ svn co http://svn.jobeet.org/tags/release_day_01/ jobeet/
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.