Bazaar uzupełnieniem SVN-a

Subversion – jeden model pracy

Najbardziej popularnym systemem wersjonowania jest Subversion. Został on zaprojektowany do pracy w sposób scentralizowany. Działa to tak, że gdzieś na serwerze znajduje się centralne repozytorium z którego programista pobiera kopię roboczą na swój lokalny komputer. Dokonuje modyfikacji plików po czym zatwierdza dokonane zmiany w repozytorium centralnym. Taki model pracy ma swoje zalety jednak zdarzają się sytuacje kiedy przydała by się lokalna wersja repozytorium. Poczciwy SVN nie daje jednak możliwości pracy w sposób rozproszony co jest jego wielką słabością w stosunku do np. Gita czy Bazaar-a, które dzięki swojej elastycznej architekturze oferują wiele przeróżnych sposobów organizacji pracy z repozytorium.

Modele pracy są jednym z wielu argumentów, dla których warto rozważyć przesiadkę na inny system kontroli wersji. Polecam zapoznać się z artykułami Why Switch to Bazaar? – Any Workflow albo Workflows – Bazaar Version Control oraz Why Git is Better than X – Any Workflow.

Ja pracuję już kilka lat z Subversion i większość firm z którymi współpracuję lub też współpracowałem też używa SVN. Dlatego decyzja o zmianie systemu wersjonowania najczęściej nie leży w mojej gestii.

Co mogę zatem zrobić? Zarówno Bazaar (bzr-svn) jak i Git (git-svn) mają narzędzia umożliwiające współpracę z Subversion.

Po co łączyć Subversion z Bazaarem lub Gitem

Model pracy rozproszonej oferuje kilka możliwości stanowiących doskonałe uzupełnienie modelu scentralizowanego.

  • Słabe połączenie internetowe albo jego brak nie jest problemem ponieważ wszelkie zmiany dokonywane są lokalnie. Połączenie z serwerem centralnym potrzebne jest jedynie raz na jakiś czas.
  • Niezależne repozytorium, odseparowane i nienarażone na zmiany czynione przez innych użytkowników daje możliwość eksperymentowania. Dokonane zmiany w przypadku powodzenia można później scalić z główną gałęzią lub też usunąć nie zaśmiecając historii zmian centralnego repozytorium.
  • Można ustalić procedurę zgodnie z którą zmiany dokonane w projekcie przez developera trafiają najpierw do osoby nadzorującej projekt, która je sprawdza, testuje i dopiero po akceptacji pcha da SVN-a. W ten sposób do centralnego repozytorium trafią jedynie zmiany stabilne.
  • Warto też wspomnieć o takim prozaicznym powodzie jak brak upierdliwych katalogów “.svn” w katalogu roboczym projektu – małe a cieszy :D .

Praca z Bazaarem jako uzupełnieniem Subversion

Importowanie repozytorium

Ja wybrałem Bazaar z uwagi na moje większe doświadcznie z tym systemem kontroli wersji.

Dla tych, którzy nie mieli jeszcze styczności z Bazaarem proponuję na start prosty tutorial Bazaar in five minutes. Jeśli chodzi o naukę to Bazaar ma tę przewagę nad Gite, że większość jego komend jest identyczna z tymi znanymi z SVN.

Jeśli nie mamy jeszcze zainstalowanego Bazaara (bzr) oraz wtyczki umożliwiającej współpracę z Subversion bzr-svn to w przypadku Ubuntu wystarczy jedynie pobrać wyżej wspomniane z repozytorium i zainstalować.

sudo apt-get install bzr bzr-svn

W przytoczonym wyżej tutorialu nowe repozytorium tworzy się poprzez komendę bzr init. W tym przypadku nie ma takiej potrzeby. Aby “zaimportować” repozytorium SVN do Bazaara należy utworzyć tzw. brancha. Zakładam że znajdujemy się w katalogu roboczym, w którym trzymamy projekty (workspace). Wywołujemy komendę bzr branch i podajemy jako parametry url repozytorium svn oraz nazwę katalogu jaki zostanie stworzony na naszym komputerze i w którym zostanie utworzone lokalne repozytorium Bazaara.

bzr branch svn+http://svn-repository.example.com/project/trunk ./project

Od tego momentu można już pracować z Bazaarem. Dostępna jest pełna historia zmian i jakby się uprzeć to można zapomnieć o SVN-ie ;) (choć do właściwego importu SVN do Bazaar służy komenda bzr svn-import) Dostępne mamy zarówno narzędzia konsolowe, jak też całą kolekcję GUI z których niejako firmowym jest multoplatformowy Bazaar Explorer. Fanom Eclipse polecam z kolei plugin do obsługi Bazaara, który niczym nie ustępuje swojemu odpowiednikowi dla Subversion.

Pracujemy w ten sposób, że wszelkie zmiany w plikach zatwierdzamy (commit) do lokalnego repozytorium. Kiedy zmiany w projekcie przyjmują już stabilną postać jesteśmy gotowi do zarejestrowania wprowadzonych poprawek w centralnym repozytorium SVN. Zanim to jednak zrobimy należy najpierw sprawdzić czy od czasu ostatniego pobrania zmian z SVN coś się nie zmieniło. W końcu najczęściej nie pracujemy sami. Wchodzimy do katalogu naszego projektu i wywołujemy:

Warto sprawdzić czy coś się zmieniło?

bzr pull svn+http://svn-repository.example.com/project/trunk

Jeśli pracujesz w zespole, a poprawka którą miałeś zrobić nie była mała to najprawdopodobniej otrzymasz komunikat, że w repozytorium dokonano zmian. Trzeba je będzie sprawdzić, scalić z lokalnymi zmianami i ponownie zatwierdzić. Bazaar podpowie Ci co masz zrobić.

bzr: ERROR: These branches have diverged. Use the missing command to see how.
Use the merge command to reconcile them.

Scalanie różnic jest proste

Jeśli inni developerzy nie edytowali tych samych plików co ty to zazwyczaj wystarczy:

Do podejrzenia różnic

bzr missing

Do scalenia zmian

bzr merge

Następnie przydałoby się sprawdzić jakie pliki różnią się względem lokalnego repozytorium po scaleniu ze zmianami dokonanymi w repozytorium centralnym

bzr status

Po ich zatwierdzeniu

bzr commit -m 'Treść komunikatu'

Zostaje już tylko wysłanie wszystkiego do svn.

bzr push svn+http://svn-repository.example.com/project/trunk

UWAGA! append_revisions_only error

W tym momencie najprawdopodobniej wyskoczy Ci mały Zonk i otrzymasz taki niemiły komunikat

Using saved push location: svn+http://svn-repository.example.com/project/trunk
bzr: ERROR: Operation denied because it would change the mainline history. Set the append_revisions_only setting to False on branch "svn+http://svn-repository.example.com/project/trunk" to allow the mainline to change.

Niestety komunikat Bazaar nie jest w tym wypadku wyczerpujący gdyż nic nie mówi na temat tego gdzie ustawić append_revisions_only na false.

W Ubuntu wyedytuj plik ~/.bazaar/subversion.conf i dodaj wspomnianą dyrektywę pod adresem repozytorium SVN

	[4ef181b9-d188-42c4-ae88-58g6dfg8760b]
	locations = svn+http://svn-repository.example.com/project/trunk
	append_revisions_only = False

To powinno załatwić sprawę.

Uwaga konflikt!

To był optymistyczny scenariusz. W mniej pomyślnej sytuacji dokonasz zmian w tym samym pliku w którym ktoś inny grzebał i to najprawdopodobniej w tych samych liniach. Automat tego nie rozwiąże. Załóżmy, że chodzi o plik “foo.py”. Pojawia się konflikt a problematyczny plik doznaje cudownego rozmnożenia.

  • foo.py.BASE – plik w wersji pozbawionej zmian powodujących konflikt (w svn foo.py.rOLDREV)
  • foo.py.OTHER – plik ze zmianami dokonanymi przez innego użytkownika (w svn foo.py.rNEWREV)
  • foo.py.THIS – plik z moimi lokalnymi zmianami (w svn foo.py.mine)
  • foo.py – zawiera to samo co foo.py.THIS

Kilka słów od Kdiff3 – bez niego moje życie było by o wiele bardziej skomplikowane

Każdy ma swoje ulubione narzędzie do scalania różnic. Ja preferuję Kdiff3 – dostępny na Linuksa i Windowsa. Mimo, że zarówno Eclipse jak i Bazaar Explorer mają swoje narzędzia do porównywania zmian ja staram się użyć Kdiff3 którego interfejs jest mi dobrze znany.

Edycja konfliktu przy użyciu Kdiff3 wymaga wywołania w konsoli komendy

kdiff3 foo.py.BASE foo.py.OTHER foo.py.THIS -o foo.py

Oczywiście nie wszyscy lubią konsolę. Aby np. Bazaar Explorer korzystał z Kdiff3 należy wyedytować plik ~/.bazaar/bazaar.conf i w sekcji DEFAULT dodać.

external_merge = kdiff3 %b %o %t -o %r

Jako ciekawostkę kierując się zawodową uprzejmością podaję też sposób wywołania tortoisemerge.

external_merge = tortoisemerge /base:%b /theirs:%o /mine:%t /merged:%r

Kdiff3 można też zintegrować z Eclipse jednak opcję taką znalazłem jedynie dla Subversion. Należy otworzyć Preferencje->Team->Diff/Merge. W sekcji “Conflict Resolution Program” wybrać opcję “External” i wskazać gdzie leży Kdiff3 (u mnie /usr/bin/kdiff3). W polu parameters należy podać

${base} ${yours} ${theirs}  -o ${merged}

Rozwiązaniem konfliktu należy się pochwalić

Po ręcznym rozstrzygnięciu konfliktów należy Bazaar poinformować o tym – analogicznie jak w svn.

bzr resolve foo.py

Komenda “resolve” usuwa przy okazji pliki foo.py.BASE, foo.py.OTHER oraz foo.py.THIS. Teraz wystarczy już tylko zatwierdzić zmiany (commit) oraz pchnąć do centralnego repozytorium (push).

Dobra rada na koniec

Bazaar czy Git to systemy kontroli wersji stworzone do pracy rozproszonej oraz dopracowane pod kątem łączenia osobnych repozytoriów. Mają pod tym względem znaczną przewagę nad Subversion. Model pracy będący mixem korzystającym z rozproszonego repozytorium np. Bazaar oraz centralnego SVN ma swoje zalety jednak stanowi dodatkowy narzut pracy związany z koniecznością dublowania pewnych czynności. Nie należy na siłę wdrażać rozwiązań, które nie są efektywne lub wręcz zbędne. Dlatego jeśli w firmie używającej Subversion stosuje się dopracowane procedury zatwierdzania zmian w repozytorium, nie ma problemów z połączeniem, a modyfikacje w projekcie nie mają charakteru rewolucji będącej jednym wielkim eksperymentem wymagającym tworzenie piaskownicy, być może funkcjonalność Subversion jest w zupełności wystarczająca.

Skomentuj jako pierwszy

Obsługa błędów uwierzytelnienia i autoryzacji na stronach www.

Angielskie słowa authentication i authorization są często mylone i używane zamiennie podczas gdy są to pojęcia znaczące zupełnie co innego. Authentication czyli polskie uwierzytelnienie (błędnie niekiedy tłumaczone jako autentykacja) oznacza identyfikację tożsamości, inaczej mówiąc odpowiada na pytanie z kim mamy do czynienia. Z kolei authorization po polsku autoryzacja to weryfikacja uprawnień.

Jeśli dostęp do jakichś zasobów na stronie jest ograniczony dla wybranej grupy użytkowników – np. dostęp do panelu administracyjnego – wtedy najpierw należy dokonać uwierzytelnienia tj. rozpoznać kim jest użytkownik żądający dostępu do zasobu, a dopiero później go autoryzować czyli sprawdzić, czy zalogowany użytkownik ma odpowiednie uprawnienia.

W wielu serwisach użytkowników niezalogowanych tzw. no name-ów określa się w bardziej elegancki sposób mianem gości. W sytuacji kiedy taki gość wpisuje w przeglądarce adres strony, która wymaga logowania w większości przypadków przekierowuje się go na stronę logowania. Pomysł jak najbardziej słuszny, tyle że przy okazji zapomina się o wysłaniu odpowiedniego nagłówka HTTP 401 Unauthorized. (Uwaga – opis kodu odpowiedzi 401 może niektórych wprowadzić w błąd nie mniej używa się go przy uwierzytelnianiu a nie autoryzacji. Należy go sobie tłumaczyć jako “nieautoryzowany dostęp – żądanie zasobu, który wymaga uwierzytelnienia”. Na dobrą sprawę należało by ten nagłówek zwracać również w chwili kiedy, użytkownik poda niewłaściwe dane i nie uda mu się zalogować na stronie.

Można by zadać pytanie po co w ogóle bawić się w te nagłówki. Otóż wspomniany wyżej gość to niezidentyfikowany użytkownik. Jest on do tego stopnia nierozpoznany, że nie wiadomo nawet czy jest on człowiekiem, czy np. pajączkiem indeksującym serwis na potrzeby wyszukiwarki stron. Taki program otrzymując odpowiedni nagłówek następnym razem wchodząc na nasz serwis ominie odsyłacz do chronionej strefy oszczędzając w ten sposób cenny czas i zasoby naszego serwera.

Rozpatrzmy teraz drugi przypadek kiedy użytkownik jest już zalogowany, ale próbuje się dostać do strefy, do której nie ma uprawnień. Także w tym przypadku użytkownik może być maszyną choćby w postaci pośrednika usługi sieciowej. Tu można przyjąć kilka strategii.

  • można wyświetlić komunikat o braku uprawnień zwracając równocześnie kod 403 Forbidden
  • można zwrócić kod błędu 404 i udać, że takiej strony nie ma,
  • można automatycznie wygasić sesję użytkownika i przekierować na stronę logowania,

Nagłówek 403 Forbidden informuje o tym, że żądany zasób istnieje ale bieżący użytkownik nie ma wystarczających uprawnień aby się do niego dostać. Automatowi taka odpowiedź w zupełności wystarczy, z kolei człowiekowi przydało by się jeszcze wyświetlić odpowiedni komunikat i formularz logowania lub przynajmniej odsyłacz do takowego.

Zwrócenie kodu 404 nie jest może do końca poprawne, gdyż wprowadza w błąd, jednak ze względów bezpieczeństwa można pokusić się o takie małe oszustwo celem ukrycia przed nieuprawnionym użytkownikiem samego faktu istnienia chronionego zasobu.

Ostatnia z wymienionych jest moim zdaniem najgorszym z rozwiązań gdyż nie daje użytkownikowi możliwości rezygnacji z dostępu do chronionego zasobu tylko każe go koniecznością powtórnego uwierzytelnienia. Poza tym sytuacja ta jest nieco kłopotliwa z punktu widzenia zwracanych nagłówków, gdyż w pierwszej kolejności powinniśmy zwrócić kod 403 Forbidden, a zaraz potem kod 401 Unauthorized, a w jednym żądaniu możemy zwrócić tylko jeden.

Skomentuj jako pierwszy

kohana 3 – rozdzielenie wywołań wewnętrznych i zewnętrznych

Dzisiaj ze wsparciem Zbyszka napisałem troszkę bardziej dopracowane rozwiązanie rozdzielania wywołań.

Wywołania jakie będziemy tu rozróżniać to wywołanie bezpośrednie, ajaxowe i wewnętrzne (internal), a kod poszczególnych akcji będziemy wywoływać w sposób następujący:

public function action_index() {
//akcja bezpośrednia
}
public function action_ajax_index() {
//akcja ajaxowa
}
public function action_internal_index() {
//akcja wewnętrzna wywołana przez Request::factory('controller/index');
}

Kontroler który będzie wykrywał co to za request nazwałem Controller_Application i wygląda on tak:

<?php defined('SYSPATH') or die('No direct script access.');

class Controller_Application extends Controller_Template {

	public function __construct(Request $req) {
		parent::__construct($req);

		if (Request::$is_ajax OR $this->request !== Request::instance()) {
			$this->auto_render = FALSE;
			$this->request->action = Request::$is_ajax ?
								"ajax_".$this->request->action :
								"internal_".$this->request->action;

		} else if (preg_match("/^internal|ajax/", $this->request->action)) {
			throw new Internal_Exception('called internal method');
		}
	}
}

Dodatkowym zabezpieczeniem jest wyrzucenie wyjątku kiedy ktoś stara się dostać do nie przeznaczonej do tego akcji bezpośrednio (czyli akcji które mają w nazwie internal lub ajax). Wyjątek ten pozwala nam rozpoznać takowy czyn i w późniejszym czasie go przechwycić i wykonać jakąś akcję. W tym wypadku wyrzucimy stronę z błędem 404, aby to zrobić w bootstrapie dodajemy coś takiego:

$request = Request::instance($_SERVER['PATH_INFO']);

try { // Attempt to execute the response
	$request->execute();
} catch (Exception $e) {
	// Create a error response
	if ($e instanceof Internal_Exception) {
		$request->status = 404;
	}
	$_status = $request->status;
	switch($request->status) {
		case 500: // other errors handling by errors controller
			break;
		default:
			$_status = 404;
	}
	$request->response = Request::factory("errors/{$_status}")->execute()->response;
}

Kontroler errors jest już prostym kontrolerem wyświetlającym stronę błędu.

Napisano 9 komentarzy, napisz kolejny

Konferencja 4Developers 2010

W moim mniemaniu Konferencja 4Developers 2010 była dobrze zorganizowana. Mając w pamięci stronę internetową przygotowaną na potrzeby wyżej wspomnianego wydarzenia spodziewałem się raczej przeciętnego poziomu. Tym milej mnie zaskoczył profesjonalizm organizatorów.

Tematy były zróżnicowane. Niektóre ciekawsze inne mniej, ale w większości prelegenci świetnie wywiązali się ze swojego zadania. Oczywiście ja oceniam jedynie wykłady, w których brałem osobisty udział. Z uwagi na moje zainteresowania poszedłem ścieżką PHP i nie licząc jednej prezentacji z zakresu zarządzania projektami IT zaliczyłem pełen repertuar proponowany developerom PHP.

W tym miejscu nie byłbym sobą gdybym nie wspomniał o tym, że czuję się troszkę zawiedziony tym, że z uwagi na równocześnie odbywające się wykłady z różnych dziedzin ominęło mnie kilka fajnych tematów dotyczących zarządzania projektami IT, ale też Silverlighta, czy też wykład pt. “Duży efekt małym kosztem, czyli SQL Server nie tylko jako baza danych”.

Wracając jednak do PHP. Ivo Jansch przedstawił ideę “cloud computing” jako taką, a także w odniesieniu do pracy programisty PHP. Zaprezentował specyfikę pracy z chmurą, a także przedstawił zarówno zalety jak i problemy wiążące się w przypadku połączenia PHP z technologią cloud computing. Osobiście chętnie zapoznałbym się bliżej z tematem, ale z uwagi na koszty jakie się wiążą z dostępem do infrastruktury chmury zapowiada się raczej, że długo nie będę miał okazji.

Kolejny wykład dotyczący jakości “produkowanego” kodu prowadzony przez Sebastiana Bergmanna rozpoczął się serią ogólników ilustrowanych przyjemnymi obrazkami. Już zacząłem się martwić, że tak będzie do samego końca, ale na szczęście Pan Sebastian – bądź co bądź twórca PHPUnit – przeszedł do konkretów. Oprócz wyżej wspomnianego frameworka do przeprowadzania testów jednostkowych, zaprezentował też takie narzędzia ja PHP_CodeSniffer – jeden z nielicznych pakietów PEAR, który po wypróbowaniu przypadł mi do gustu – PHP_Depend czy phpUnderControl. Po raz kolejny przekonałem się, że na testowanie zawsze powinien znaleźć się czas i pieniądze.

W tym miejscu pozwolę sobie troszkę odejść od harmonogramu i przeskoczyć na osi czasu do prelekcji na temat “Debugowania PHP przy użyciu Xdebug”. Derick Rethans współtwórca Xdebuga (jak również np. eZComponents) zaprezentował możliwości tegoż narzędzia. Używam Xdebuga na co dzień nie mniej dowiedziałem się o kilku jego możliwościach, których nie miałem dotąd okazji wypróbować. Wykład ogólnie oceniam jako przydatny choć chwilami zagubienie prelegenta w swoim własnym komputerze było zabawne. Pan Derick jest prawdziwym hardkorem i na desktopie ma zainstalowanego Debiana przez co uruchomienie NetBeans wymagało u niego otwarcia konsoli kilkukrotnego użycia komendy “ls” i uruchomienie edytora z linii komend ;)

Polscy prelegenci też trzymali poziom. Za najbardziej wartościowy z mojego punktu widzenia wykład uznałbym “High Performance PHP” prowadzony przez Mariusza Gila (taka mała dygresja – jak to się stało, że tytuły wykładów obcokrajowców przetłumaczono na język polski a tego nie można było?). Pan Mariusz – co nie wynika z jego strony w serwisie Golden Line – pracuje obecnie w nk (chyba nie muszę rozwijać tego skrótu) ma doświadczenie z dużymi i często odwiedzanymi serwisami. Dlatego jego praktyczne rady dotyczące optymalizacji i strojenia aplikacji tak aby była ona w stanie pracować przy naprawdę dużym obciążeniu są warte uwagi. Wysoka wartość merytoryczna i dobrze przygotowana prezentacja sprawiły, że po zakończeniu wykładu czułem niedosyt i chętnie zrezygnowałbym z przerwy, aby posłuchać dalej.

Przez chwilę zastanawiałem się czy pójść na wykład Jarosława Pałki pt. JavaScript po stronie przeglądarki. Kiedyś czytałem jakiś artykuł na ten temat i traktowałem to w kategoriach ciekawostki żeby nie rzec ekscentrycznego programistycznego doświadczenia. Ciekawość zwyciężyła i się zdecydowałem. Jak się okazało pomimo tego, że JavaScript ma już ponad 15 lat to jednak zastosowanie go po stronie serwera jest rozwiązaniem będącym jeszcze w powijakach. No może trochę przesadziłem. Ponoć są już duże serwisy korzystające z takiego zastosowania JS. Nie żałuję uczestnictwa w tej prelekcji jako, że była ona prowadzona z wielką swadą. Nie podzielam jednak entuzjazmu prelegenta w zakresie możliwości jakie ma jakoby oferować CommonJS w odróżnieniu do innych języków skryptowych. Wiele z przedstawianych zalet rozpoznałem jako dostępne choćby w Pythonie. Poza tym nie jestem pewien czy to by było takie fajne gdyby JS z przeglądarki gadał z JS z serwera. Ja bym tam niekiedy wolał na odwrót i z ciekawością spoglądam na inicjatywy typ php.js. Atrapa wiadomo ale nice ;)

Napisałem wcześniej, że Polacy też dali radę – hmm – nie wszyscy. Wykład “Aplikacje internetowe wydajne od początku” prowadziło dwóch całkiem sympatycznych “chłopaków”. Celowo nie przedstawiam ich nazwisk, ani pracodawcy bo zaliczyli mega wpadkę. Nie dość, że cały czas zmagali się z problemami technicznymi to jeszcze przedstawiony przez nich temat był jednym, wielkim i do tego nie udanym przeglądem technologii mających poprawić wydajność aplikacji internetowych. Podstawowym moim zarzutem jest brak spójności. Prelegenci skakali po wątkach jak po kamieniach nie budując z nich mostu, który przeprowadziłby gładko słuchaczy przez temat. Właściwie beż żadnego wprowadzenia przechodzili od Sphinxa, poprzez Memcached, aż do HAProxy. Po drugie niepotrzebnie zagłębiali się w szczegóły. Zamiast wspomnieć o tym, że buforowanie szablonów jest jedną z metod poprawiających wydajność aplikacji, wylistowali metody i ustawienia systemu szablonów Smarty odpowiedzialnych za cache-owanie. Z kolei NoSQL został potraktowany po macoszemu. Chłopaki wymienili dostępne implementacje nie wyjaśniając, że chodzi o rozproszone bazy danych. Gdybym nie czytał kiedyś na ten temat nie wiedziałby o czym właściwie mowa. Wspominając o PDO zagalopowali się gdyż warstwa abstrakcji do baz danych – nawet napisana w C++ jest bądź co bądź dodatkowym narzutem. Uratowali się wyjaśniając, że z uwagi na wygodę i bezpieczeństwo warto niekiedy ponieść dodatkowe koszty. Każda moneta ma dwie strony tak samo kompresja z jednej strony oszczędza łącze, a z drugiej strony “zatrudnia” zasoby komputera potrzebne do pakowania i rozpakowywania danych. To mogłaby być fajna prezentacja gdyby zbudować spójną i składną opowieść, zapobiec problemom technicznym i wyeliminować pochrząkiwania prelegentów. Zwykle nie przejmuję się wyglądem jednak kiedy coś w prezentacji nie gra także kilkudniowy zarost zaczyna przemawiać na niekorzyść prezentera. Najbardziej jednak przeszkadzało mi ograniczenie się jednego z prelegentów do po prostu odczytania slajdów.

Jedyny wykład w jakim miałem okazję uczestniczyć, a nie dotyczący stricte programowania to prezentacja przygotowana przez Piotra Borka pt. “Łączenie ognia i wody – proces projektowy w Gratka Technologie”. Kiedy skończył pozazdrościłem mu. Proces realizacji projektów w Gratce nie jest może idealny, ale o wiele bliższy doskonałości w porównaniu z tym z czym ja się spotykam na co dzień. Wiadomo, że wiele zależy od specyfiki projektów, które się bierze na warsztat, kiedy jednak usłyszałem, że przy projekcie trwającym mniej więcej 2 tygodnie czas na oszacowanie czasochłonności implementacji tegoż projektu wynosi kilka, kilkanaście godzin to (…). Śmiać mi się zachciało gdy jeden z pracowników Allegro – o czym nie zapomniał wspomnieć – stwierdził, że on potrafi oszacować czasochłonność od ręki i nie potrzebuje do tego analizy dotychczas powstałego kodu bo zna go na wylot. Pan Piotr pogratulował – wypowiadającemu się w ten sposób – zdolności, czego i ja mu gratuluję.

Uczestnictwo w konferencji 4Developers było czystą przyjemnością. Super prelegenci, ciekawe tematy, fajne dziewczyny, do tego nieograniczona ilość napojów i przekąsek – rewelacja. Szczególne podziękowania dla sponsora, który nie tylko zafundował nam bilety na konferencję, ale też zadbał o to abyśmy nie chodzili głodni i wykupił obiad – też był bardzo dobry :D .

Skomentuj jako pierwszy

Kohana 3 – routing z wykorzystaniem bazy danych – zarys problemu

W frameworku KohanaPHP w wersji 2.x były tzw. hooks czyli po naszemu miejsca w które można się wpiąć z własnym kodem. Przydawały się w różnych sytuacjach m.in do podpięcia niestandardowego routingu korzystającego z bazy danych.

W najprostszym wydaniu mogło to wyglądać mniej więcej tak:


function db_routing() {
    // routing po bazie danych
}
Event::add('system.routing', 'db_routing');

Taki rodem z JavaScriptu “events listener” (nasłuchiwacz zdarzeń) umożliwiał podpięcie pod różne wydarzenia zachodzące w aplikacji funkcję zwrotną. W przypadku routingu sprawdzało się to doskonale. KohanaPHP w wersji 3 została jednak zaprojektowana od nowa. Na próżno szukać hooków, nie znajdziemy też klasy Event tak więc chcąc wzbogacić standardowy routing o metodę korzystającą z bazy danych trzeba podejść do problemu nieco inaczej.

KO3 rozpoznaje parametry w adresie przy pomocy wyrażeń regularnych, które definiujemy w pliku application/bootstrap.php z użyciem klasy Route.

Route::set('default', '(<controller>(/<action>(/<id>)))')
	->defaults(array(
		'controller' => 'welcome',
		'action'     => 'index',
	));

Następnie powoływana jest do życia instancja klasy Request

$request = Request::instance();

W chwili wywołania metody $request->execute() rozpoczyna się właściwy proces routingu. Adres przepuszczany jest przez wszystkie wcześniej zdefiniowane reguły i jeśli się na którąś załapie następuje identyfikacja kontrolera, akcji i pozostałych parametrów. W przypadku kiedy jednak adres nie zostanie rozpoznany rzucany jest wyjątek. Dodatkowo w obiekcie requesta ustawiany jest status, który w omawianym przez nas przypadku przyjmuje wartość 404 co znacznie ułatwia identyfikację “sytuacji wyjątkowej” (specjalnie unikam słowa błąd bo nie każdy wyjątek musi oznaczać błąd).

try {
	$request->execute();
} catch (Exception $e) {
	if ($request->status == 404) {
		Request::factory('error/404')->execute();
	}
}

Wyjątek oczywiście można przechwycić i wyświetlić stronę błędu “Error 404″ jak to jest pokazane na powyższym przykładzie, ale nic nie stoi na przeszkodzie, aby zamiast tego uruchomić w tym momencie mechanizm identyfikujący adres na podstawie wpisów w bazie danych.

Takie rozwiązanie nie jest złe, ale nie pozwala przeprowadzić routingu po bazie danych przed standardowym routingiem. Co zatem zrobić aby odwrócić kolejność?

Pomysł na jaki wpadłem jest prosty. Ponieważ KO3 zachowała cudowną właściwość polegającą na możliwości nadpisania klas systemowych wersjami charakterystycznymi dla danej aplikacji dlaczego by z tego nie skorzystać?

W katalogu application/classes stworzyłem plik route.php o następującej treści.

<?php defined('SYSPATH') or die('No direct script access.');

class Route extends Kohana_Route {

	public function __construct($uri = NULL, array $regex = NULL)
	{
		parent::__construct($uri, $regex);
	}

	public static function set($name, $uri, array $regex = NULL)
	{
		$class = ($name == 'database') ? 'Route_Db' : 'Route';
		return Route::$_routes[$name] = new $class($uri, $regex);
	}
}

Właściwie można by się obyć bez konstruktora gdyż KO3 wykorzystuje do wywołania konstruktora parenta Reflection API, jednak ja staram się pielęgnować dobre praktyki ;) . Klasa ta została stworzona tylko i wyłącznie w celu nadpisania metody statycznej “set”. W odróżnieniu od orginału sprawdza ona czy podana nazwa danej trasy (czytaj reguły dopasowania adresu) przypadkiem nie nazywa się “database”. W takim przypadku zamiast obiektu klasy Route tworzony jest obiekt klasy pochodnej Route_Db (application/classes/route/db.php), która również dziedziczy po Kohana_Route tylko, że w tym przypadku nadpisaniu uległa metoda “matches”.

<?php defined('SYSPATH') or die('No direct script access.');
class Route_Db extends Kohana_Route {
	public function __construct($uri = NULL, array $regex = NULL)
	{
		parent::__construct($uri, $regex);
	}

	public function matches($uri)
	{
		// routing po bazie danych
	}
}

Wszystko dzieje się w metodzie matches. To tutaj rozpozawane są parametry. W obiekcie Route adres sprawdzany jest wyrażeniem regularnym. W instancji klasy Route_Db możemy zrobić to samo z tym, że np. wyrażenia będą pobrane z bazy danych. Oczywiście pod pojęciem bazy danych nie musi kryć się tylko serwer relacyjnej bazy danych takiej jak np. MySql. Równie dobrze może to być bufor oparty na plikach gdzie adres będzie wprost mapowany na określony zestaw parametrów, albo inne źródło danych.

Na zakończenie trzeba jeszcze dodać nowego route-a do bootstrapu bo inaczej to nie zadziała.

Route::set('database', ''); // to tutaj

Route::set('default', '(<controller>(/<action>(/<id>)))')
	->defaults(array(
		'lang' => $default_lang,
		'controller' => 'welcome',
		'action'     => 'index',
	));

Rozwiązanie to jest na tyle elastyczne, że gdybyśmy w tym momencie chcieli stworzyć tzw. backend czyli zwyczajnie mówiąc panel administracyjny, w którym routing po bazie danych nie ma racji bytu wystarczy na samym początku dodać jeszcze jedną trasę, która skieruje nas we właściwe miejsce zanim system zacznie przeszukiwać bazę danych.

Route::set('admin', 'admin((/<controller>)(/<action>(/<id>)))', array('id'=>'.+'))
	->defaults(array(
		'directory' => 'admin',
		'controller' => 'index',
		'action' => 'index',
	));

Route::set('database', '');

Route::set('default', '(<controller>(/<action>(/<id>)))')
	->defaults(array(
		'lang' => $default_lang,
		'controller' => 'welcome',
		'action'     => 'index',
	));
Napisano 8 komentarzy, napisz kolejny

Ku powierzchni

Uwięzieni w głębinach dusimy się wypatrując wybawienia.
Kierujemy myśli ku powierzchni gdzie promienie słońca tańczą na falach.
Chcemy wyrwać się z otchłani.
Chcemy zaczerpnąć świeżego powietrza.
Chcemy  nabrać wiatru w żagle i popłynąć niesieni natchnieniem.

Skomentuj jako pierwszy