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',
	));
Dodano piątek, Marzec 19th, 2010 w kategoriach Kohana framework, Programowanie.

Tagi: ,

9 komentarze

  1. Drugi sposób na rozwiązanie tego problemu to rozszerzenie funkcjonalności klasy Request. W połączeniu z kilkoma drobnymi korektami w Bootstrapie, osiągamy sytuację w której mamy następującą kolejność działania routingu: baza danych > zdefiniowane route’y > bezpośrednie odwołania do kontrolerów.

    Postaram się opisać w niedługim czasie jak osiągnąć takie rozwiązanie na mój sposób :) .

  2. Początkowo też myślałem o zmodyfikowaniu Requesta jednak zdecydowałem się na modyfikację Route ponieważ takie rozwiązanie ma kilka zalet. Po pierwsze mamy elastyczność co do wyboru kolejności metod przeprowadzających routing. Po drugie nie trzeba się bawić w ustawianie statusu na 404 w przypadku niedopasowania adresu. Wystarczy w metodzie matches zwrócić false. Po trzecie routing odbywa się tam gdzie powinien.

  3. Hej,
    Panowie – fajnie że zaczynacie bloga od kohanej tematyki ale zlitujcie się i dajcie większą czcionkę…

  4. Dzięki za uwagę. To jest jakiś darmowy szablon WordPressa. Jak u Ciebie ta czcionka wygląda, bo u mnie i kolegów jest całkiem sporych rozmiarów?
    http://www.osmialowski.pl/temp/wynurzenie_screenshot.png

  5. http://myih.eu/zdjecie/1269767811zrzut_ekranu.png
    O dziwo – problem tylko w operze. Firefox pokazuje ok, zresztą widzę na podglądzie teraz że font-size: 16px. Nie wiem o co come on:/

    A już tak na temat artykułu to dobra robota. Dzięki :)

  6. l1em1on1 pisze:

    co do opery to „O dziwo” może nie jest zbytnio trafne ;) choć to tylko pewnie moje uprzedzenie.

  7. Początkowo też myślałem o zmodyfikowaniu Requesta jednak zdecydowałem się na modyfikację Route ponieważ takie rozwiązanie ma kilka zalet. Po pierwsze mamy elastyczność co do wyboru kolejności metod przeprowadzających routing. Po drugie nie trzeba się bawić w ustawianie statusu na 404 w przypadku niedopasowania adresu. Wystarczy w metodzie matches zwrócić false. Po trzecie routing odbywa się tam gdzie powinien.

  8. nediam pisze:

    Ciekawe podejście :)

  9. ja tam przeczytalem arta na kindlu w trybie „article mode” i jestem bardzo zadowolony ;)

Napisz komentarz