Skip to content

Instantly share code, notes, and snippets.

@jm42
Created October 10, 2014 00:43
Show Gist options
  • Save jm42/f2a690fd2c55a4e6206b to your computer and use it in GitHub Desktop.
Save jm42/f2a690fd2c55a4e6206b to your computer and use it in GitHub Desktop.
Traversal Router
<?php
// This was the first test to create what then became Traba.
//
// https://github.com/guide42/traba
//
$request = array(
'path_info' => '/user/123/edit',
'query' => array(
'name' => 'John Doe',
'age' => 42,
),
);
// {{{ Fast an ugly traversal implementation
function split_path_info($path)
{
$parts = array();
foreach (explode('/', $path) as $part) {
if (empty($part) || $part === '.') {
continue;
}
if ($part === '..') {
if ($parts) {
unset($parts[count($parts) - 1]);
continue;
}
throw new RuntimeException('');
}
$parts[] = $part;
}
return $parts;
}
function get_path_info($context)
{
if (isset($context['request']['path_info'])) {
return $context['request']['path_info'];
}
return '/';
}
function traverser($context, $root)
{
$path = get_path_info($context);
if ($path === '/') {
return array(
'context' => $root,
'name' => null,
'subpath' => array(),
'traversed' => array(),
);
}
$parts = split_path_info($path);
foreach($parts as $i => $segment) {
if (!(is_array($root) || $root instanceof \ArrayAccess) || !isset($root[$segment])) {
return array(
'context' => $root,
'name' => $segment,
'subpath' => array_slice($parts, $i + 1),
'traversed' => array_slice($parts, 0, $i),
);
}
$root = $root[$segment];
}
return array(
'context' => $root,
'name' => null,
'subpath' => array(),
'traversed' => $parts,
);
}
// }}}
// {{{ Example of utilization of the traversal algorithm
class User {
public $name;
public $age;
public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
}
class UserDispatcher implements \ArrayAccess
{
private $users = null;
public function init() {
if ($this->users === null) {
$this->users = array(
123 => new User('Not John', 24),
);
}
}
public function offsetExists($offset) {
$this->init();
return isset($this->users[$offset]);
}
public function offsetGet($offset) {
$this->init();
return $this->users[$offset];
}
public function offsetSet($offset, $value) {}
public function offsetUnset($offset) {}
}
$context = array('request' => $request);
$root = array(
'user' => new UserDispatcher(),
);
$matched = traverser($context, $root);
// }}}
// {{{ Implementation of the matcher
function matcher(array $registry, array $matched)
{
$context = $matched['context'];
$name = $matched['name'] ? $matched['name'] : 'index';
foreach ($registry as $view) {
if ($view[0] === $name && $context instanceof $view[1]) {
return $view[2];
}
}
return null;
}
// }}}
// {{{ Use the matcher
function user_edit($request, User $user)
{
$user->name = $request['query']['name'];
$user->age = $request['query']['age'];
return 'OK';
}
function user_index($request, User $user)
{
return 'Name=' . $user->name . '; Age=' . $user->age;
}
function not_found($request, $context)
{
return '404';
}
$registry = array(
// | name | context type | callback |
array( 'index', 'User' , 'user_index' ),
array( 'edit' , 'User' , 'user_edit' ),
);
$view = matcher($registry, $matched);
if ($view === null) {
$view = 'not_found';
}
// }}}
// {{{
$response = call_user_func($view, $request, $matched['context']);
echo $response . PHP_EOL;
// }}}
// Hope nobody will kill me for so ugly code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment