-
Sería un responder para
Ochenta\emit
,function __invoke(ServerRequest $req, callable $open) { try { $action = $this->getRouter()->match($req); } catch (RouteNotFoundException $ex) { return not_found($req, $open); } catch (MethodNotAllowedException $ex) { return method_not_allowed($req, $open); } $payload = $action(); $responder = $this->getResponderOf($payload); return $responder($req, $open); };
-
Las rutas pueden estar encapsuladas en objectos,
class Hello { function __invoke(): array { return [ AddBlog::class => [method('GET'), path('/hello[/<slug:user>]')], ]; } function addBlog(string $user=null): string { if ($user) { return "/hello/$user"; } return '/hello'; } }
-
En vez de algo específico para el método y el path, se puede hacer un estilo de middleware pero con funciones que devuelvan FALSE si no matchea la parte del request o un array con valores matcheados.
function method($method) { $normalized = strtoupper($method); return function(ServerRequest $req) use($normalized) { return $req->getMethod() === $normalized ? [] : FALSE; }; }
-
Mala implementacion de
match
,// FIXME don't use exceptions to flow control function match(ServerRequest $req, array $routes) { $resolver = function($matches, $route) use($req) { if (is_array($match = $route($req))) { return $match + $matches; } elseif ($match === FALSE) { return new \RuntimeException('Route not found'); } return $matches; }; foreach ($routes as $handler => $route) { try { if (is_int($handler) && is_array($route)) { return match($req, $route); } elseif (is_int($handler) && is_callable($route)) { return match($req, $route()); } elseif (is_string($handler) && is_array($route)) { return [$handler, array_reduce($route, $resolver, [])]; } elseif (is_string($handler) && is_callable($route)) { return [$handler, $resolver([], $route)]; } else { throw new \InvalidArgumentException('Invalid route'); } } catch (\RuntimeException $ex) { continue; } } throw new \RuntimeException('Route not found'); }
-
Uso de
match
,match(new ServerRequest, [Homepage::class => path('/')]); match(new ServerRequest, [Homepage::class => [method('GET'), path('/')]]); match(new ServerRequest, [function() { return [Homepage::class => [path('/')]]; }]); match(new ServerRequest, [new Hello]);
-
Otra idea de
$routes
,// https://gist.github.com/jm42/b4a2a6a9daa23d042edf6d3eb25ea27b $routes = [ '/' => Homepage::class, '/admin' => function(callable $r): array { return [ $r('GET', path('/users')) => ListUsers::class, $r('GET', path('/user/{int:id}')) => ShowUser::class, '/api' => function(callable $r): array { return [ $r('PATCH', path('/user/{int:id}')) => ApiPatchUser::class, ]; }, ]; }, ];
-
Primera evolución,
function match(ServerRequest $req, array $routes) { $path = $req->getUri()['path']; $left = array_reverse($routes); $matchers = []; while (end($left)) { $key = key($left); $route = array_pop($left); if (isset($matchers[$key]) && ($match = $matchers[$key]($req)) !== FALSE) { return ['error' => MATCH_ERR_OK, 'match' => $route, 'vars' => iterator_to_array($match)]; } if ($key === $path) { return ['error' => MATCH_ERR_OK, 'match' => $route]; } if (stripos($path, $key) === 0 && is_callable($route)) { $left = array_merge($left, array_reverse($route(function(...$all) use($path, &$matchers) { $matchers[$uniqid = uniqid()] = function(ServerRequest $req) use($path, $all) { if (stripos($req->getUri()['path'], $path) !== 0) { return FALSE; } foreach ($all as $matcher) { if (($match = $matcher($req)) !== FALSE) { yield from $match; } } return FALSE; }; return $uniqid; }))); $path = substr($path, strlen($key)); } } return ['error' => MATCH_ERR_NOT_FOUND]; }
-
Basico,
function match(array $routes): callable { $routes = array_reverse($routes); return function(ServerRequest $req, array $args=[]) use($routes) { $args['path'] = $args['path'] ?? $req->getUri()['path']; while (end($routes)) { $key = key($routes); $route = array_pop($routes); if ($key === $args['path']) { return ['error' => MATCH_ERR_OK, 'match' => $route]; } } return ['error' => MATCH_ERR_NOT_FOUND]; }; }
-
Extendido,
function match(array $routes, array $matchers=[]): callable { $match = function(callable $route, string $prefix='') use(&$matchers): array { $pre = []; $new = $route(function(string $method, string $path) use(&$pre, &$matchers, $prefix) { $id = $id = uniqid(); $pre[$id] = NULL; $matchers[strtoupper($method)][$id] = function(ServerRequest $req) use($prefix, $path) { return $prefix . $path === $req->getUri()['path']; }; return $id; }); foreach ($new as $nkey => $nroute) { if (array_key_exists($nkey, $pre)) { $pre[$nkey] = $nroute; } else { $pre[$prefix . $nkey] = $nroute; } } return $pre; }; return function(ServerRequest $req) use($routes, &$matchers, $match) { $method = $req->getMethod(); $path = $req->getUri()['path']; while (reset($routes)) { $key = key($routes); $route = array_shift($routes); if (isset($matchers[$method][$key]) && !empty($ret = $matchers[$method][$key]($req))) { return ['error' => MATCH_ERR_OK, 'match' => $route, 'return' => $ret]; } if ($key === $path) { return ['error' => MATCH_ERR_OK, 'match' => $route]; } if (is_callable($route)) { $routes = array_merge($routes, $match($route, $key)); } } return ['error' => MATCH_ERR_NOT_FOUND]; }; }
-
No se puede hacer asi,
function match(callable $handler) { return function(Request $req) use($handler) { $path = strstr($req->getTarget(), '?', true) ?: $req->getTarget(); $method = $req->getMethod(); $routes = $handler(function(...$matchers) { // TODO }); while (reset($routes)) { $key = key($routes); $route = array_shift($routes); if ($key === $path) { return ['error' => MATCH_ERR_OK, 'match' => $route]; } if (is_callable($route) && stripos($path, $key) === 0) { $ret = match($route)(new Request( $req->getMethod(), substr($path, strlen($key)), $req->getHeaders(), $req->getBody())); if ($ret['error'] !== MATCH_ERR_NOT_FOUND) { return $ret; } } } return ['error' => MATCH_ERR_NOT_FOUND]; }; }
-
Implementacion larga,
define('MATCH_ERR_OK', 0); define('MATCH_ERR_NOT_FOUND', 1); define('MATCH_ERR_METHOD_NOT_ALLOWED', 2); function match(array $routes): callable { return function(ServerRequest $req) use($routes): array { $matchers = ['GET' => [], 'POST' => []]; $method = $req->getMethod(); $path = $req->getUri()['path']; while (reset($routes)) { $key = key($routes); $route = array_shift($routes); if (isset($matchers[$method][$key])) { $args = $matchers[$method][$key]($req); if ($args !== FALSE) { return ['error' => MATCH_ERR_OK, 'match' => $route, 'args' => $args]; } } if ($path === $key) { return ['error' => MATCH_ERR_OK, 'match' => $route]; } if (is_callable($route) && stripos($path, $key) === 0) { $pre = []; $new = $route(function(string $method, string $pattern, ...$extra) use(&$matchers, &$pre, $key) { $method = strtoupper($method); $pattern = $key . $pattern; if (!isset($matchers[$method])) { throw new \InvalidArgumentException('Invalid method'); } $id = uniqid(); $pre[$id] = NULL; $matchers[$method][$id] = function(ServerRequest $req) use($pattern, $extra) { $path = $req->getUri()['path']; if ($path === $pattern) { $args = []; } elseif (preg_match($pattern, $path, $matches)) { $args = $matches; } else { return FALSE; } foreach ($extra as $matcher) { if (($match = $matcher($req)) === FALSE) { return FALSE; } $args = $match + $args; } return $args; }; return $id; }); foreach ($new as $nkey => $nroute) { if (array_key_exists($nkey, $pre)) { $pre[$nkey] = $nroute; } else { $pre[$key.$nkey] = $nroute; } } $routes = $pre + $routes; } } return ['error' => MATCH_ERR_NOT_FOUND]; }; }
-
Sus tests,
use function Ochenta\match; describe('match', function() { it('returns array with error', function() { $matcher = match([]); $match = $matcher(new ServerRequest); expect($match)->toBeA('array')->toContainKey('error'); }); it('returns matched route by checking exact path from routes key', function() { $matcher = match(['/' => 'Homepage']); $match = $matcher(new ServerRequest(['REQUEST_URI' => '/'])); expect($match)->toContainKey('match'); expect($match['match'])->toBe('Homepage'); }); it('returns matched route by executing the function and check the exact path in the returned array key', function() { $matcher = match(['/api' => function() { return ['/users' => 'UserController::listAction']; }]); $match = $matcher(new ServerRequest(['REQUEST_URI' => '/api/users'])); expect($match)->toContainKey('match'); expect($match['match'])->toBe('UserController::listAction'); }); it('returns matched route by using the callable injected to the function as keys', function() { $matcher = match(['/api' => function(callable $r) { return [$r('GET', '/users') => 'UserController::listAction']; }]); $match = $matcher(new ServerRequest(['REQUEST_URI' => '/api/users'])); expect($match)->toContainKey('match'); expect($match['match'])->toBe('UserController::listAction'); }); });
Last active
August 10, 2016 18:59
-
-
Save jm42/a38a4f68d28d20016b5b1f88bf64cfda to your computer and use it in GitHub Desktop.
Playing around with a match function
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment