Skip to content

Instantly share code, notes, and snippets.

@emsifa
Last active August 23, 2016 08:22
Show Gist options
  • Save emsifa/38a820ca5524027b1d18924157f0d233 to your computer and use it in GitHub Desktop.
Save emsifa/38a820ca5524027b1d18924157f0d233 to your computer and use it in GitHub Desktop.
Helper untuk RESTful Routing pada BANG framework

RESTful Router BANG Framework

Status: experimental

Ini adalah helper untuk membuat aplikasi BANG menjadi REST.

Instalasi

Copy-paste kode dispatch_routes.php kedalam file app/helper.php (jangan lupa hapus <?php pada kode dispatch_routes ini). Atau bisa juga buat file terpisah lalu di require pada file index.php aplikasi BANG kamu.

Cara manapun boleh.

Cara Menggunakan

Pada file index.php aplikasi BANG kamu, ubah bagian ini:

$module = get_request_path();

Menjadi

$request = $_SERVER['REQUEST_METHOD'].' /'.get_request_path();
$module = dispatch_routes($request, [
    'GET /welcome' => 'welcome', // hanya menerima request dengan method GET yg diarahkan ke module `welcome`
    'GET|POST /user/:username' => 'user' // menerima request dengan method GET atau POST yg diarahkan ke module `user`
], [
    'cache' => 'route-caches.txt'
]);

Kode diatas adalah contoh mendaftarkan 2 buah route dimana route pertama akan mengakses modul 'welcome' jika aplikasi menerima request berupa GET ke path /welcome. Dan route ke-2 mendaftarkan route yg akan mengakses ke modul 'user' jika aplikasi menerima request berupa GET atau POST ke path /user/:username dimana :username adalah parameter yang dapat bernilai kombinasi angka, huruf, karakter '-', atau '_'.

Untuk mencobanya, silahkan buat modul 'user' dengan membuat file app/module/user.php. Lalu isikan sebagai berikut:

<?php
global $_PARAM;
?>
<h1>Halo <?= $_PARAM['username'] ?></h1>

Lalu akses browser ke url http://localhost/project-bang-kamu/index.php/user/johndoe. Jika berhasil, browser akan menampilkan pesan 'Halo johndoe'.

Selanjutnya coba hapus GET pada GET|POST di route user tersebut, dan coba refresh browser. Sekarang browsermu seharusnya akan menampilkan error 404 karena route tersebut tidak menerima GET request.

Performa

Metode yang digunakan pada router ini sama seperti yang saya buat pada rakit framework yang hasil benchmarknya saya post disini. Teknik yang digunakan mirip-mirip seperti FastRoute, yakni mengelompokkan sebagian route kedalam sebuah regex, jadi tidak menyocokkan regex pada route 1/1.

Berikut hasil benchmark untuk router ini pada laptop Lenovo dengan prosesor AMD A10 saya:

# CASE 1: Benchmarking without cache
==========================================================
[bench] first route => 3.9417381286621ms
[bench] last route => 8.5415799617767ms
[bench] unknown route => 8.0575070381165ms

# CASE 2: Benchmarking within cache
==========================================================
[bench] first route => 2.8668539524078ms
[bench] last route => 3.3761830329895ms
[bench] unknown route => 2.9186360836029ms

pada benchmarking ini di test router melakukan 10.000 dispatching ke 50 routes untuk setiap testnya.

<?php
global $_PARAM;
$_PARAM = [];
function dispatch_routes($request, array $routes_data, array $options = [])
{
$options = array_merge([
'cache' => false,
'routes_per_regex' => 16,
'max_params' => 10
], $options);
$get_matches = function($regex, $data) use ($request, $options) {
$pattern = implode('', explode(' ', $request, 2));
if (!preg_match($regex, $pattern, $matches)) {
return false;
}
$index = (count($matches) - 1 - $options['max_params']);
return [
'route' => $data['routes'][$index],
'data' => $data['datas'][$index],
];
};
$parse_params = function($route) use ($request, $options) {
global $_PARAM;
$path_info = ltrim(explode(" ", $request)[1],'/');
$path_route = preg_replace("/\(|\)|\?/", "", ltrim(explode(" ", $route)[1],'/'));
$route_paths = explode("/", $path_route);
$uri_paths = explode("/", $path_info);
foreach ($route_paths as $i => $path) {
$value = isset($uri_paths[$i])? $uri_paths[$i] : null;
$_PARAM[$i] = $value;
if (strpos($path, ':') === 0) {
$key = str_replace(':', '', $path);
$_PARAM[$key] = $value;
}
}
};
if ($options['cache'] AND file_exists($options['cache'])) {
$regexes = unserialize(file_get_contents($options['cache']));
} else {
$regexes = [];
$route_keys = array_keys($routes_data);
$route_datas = array_values($routes_data);
$chunk_routes = array_chunk($route_keys, $options['routes_per_regex']);
$chunk_datas = array_chunk($route_datas, $options['routes_per_regex']);
foreach ($chunk_routes as $x => $routes) {
$datas = $chunk_datas[$x];
$regex = "";
foreach ($routes as $i => $route) {
list($methods, $path) = explode(" ", $route, 2);
$methods = "(".preg_replace("/GET/", "HEAD|GET", $methods).")";
$path = preg_replace("/:[a-zA-Z0-9_]+/", "([a-zA-Z0-9-_]+)", $path);
$route_regex = $methods.$path;
$count_brackets = substr_count($route_regex, "(");
$count_added_brackets = $options['max_params'] + ($i - $count_brackets);
$route_regex .= str_repeat("()", $count_added_brackets);
$regex .= "|" . $route_regex;
}
$regex = "~^(?|" . ltrim($regex, "|") . ")$~x";
$regex_data = ['routes' => $routes, 'datas' => $datas];
if (!$options['cache']) {
$matches = $get_matches($regex, $regex_data);
if (!$matches) continue;
$parse_params($matches['route']);
return $matches['data'];
} else {
$regexes[$regex] = $regex_data;
}
}
}
if ($options['cache']) {
if (!file_exists($options['cache'])) {
file_put_contents($options['cache'], serialize($regexes));
}
foreach ($regexes as $regex => $data) {
$matches = $get_matches($regex, $data);
if (!$matches) continue;
$parse_params($matches['route']);
return $matches['data'];
}
}
return null;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment