233 lines
6.4 KiB
PHP
233 lines
6.4 KiB
PHP
<?php namespace Phroute\Phroute;
|
|
|
|
use Phroute\Phroute\Exception\HttpMethodNotAllowedException;
|
|
use Phroute\Phroute\Exception\HttpRouteNotFoundException;
|
|
|
|
class Dispatcher {
|
|
|
|
private $staticRouteMap;
|
|
private $variableRouteData;
|
|
private $filters;
|
|
private $handlerResolver;
|
|
public $matchedRoute;
|
|
|
|
/**
|
|
* Create a new route dispatcher.
|
|
*
|
|
* @param RouteDataInterface $data
|
|
* @param HandlerResolverInterface $resolver
|
|
*/
|
|
public function __construct(RouteDataInterface $data, HandlerResolverInterface $resolver = null)
|
|
{
|
|
$this->staticRouteMap = $data->getStaticRoutes();
|
|
|
|
$this->variableRouteData = $data->getVariableRoutes();
|
|
|
|
$this->filters = $data->getFilters();
|
|
|
|
if ($resolver === null)
|
|
{
|
|
$this->handlerResolver = new HandlerResolver();
|
|
}
|
|
else
|
|
{
|
|
$this->handlerResolver = $resolver;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispatch a route for the given HTTP Method / URI.
|
|
*
|
|
* @param $httpMethod
|
|
* @param $uri
|
|
* @return mixed|null
|
|
*/
|
|
public function dispatch($httpMethod, $uri)
|
|
{
|
|
list($handler, $filters, $vars) = $this->dispatchRoute($httpMethod, trim($uri, '/'));
|
|
|
|
list($beforeFilter, $afterFilter) = $this->parseFilters($filters);
|
|
|
|
if(($response = $this->dispatchFilters($beforeFilter)) !== null)
|
|
{
|
|
return $response;
|
|
}
|
|
|
|
$resolvedHandler = $this->handlerResolver->resolve($handler);
|
|
|
|
$response = call_user_func_array($resolvedHandler, $vars);
|
|
|
|
return $this->dispatchFilters($afterFilter, $response);
|
|
}
|
|
|
|
/**
|
|
* Dispatch a route filter.
|
|
*
|
|
* @param $filters
|
|
* @param null $response
|
|
* @return mixed|null
|
|
*/
|
|
private function dispatchFilters($filters, $response = null)
|
|
{
|
|
while($filter = array_shift($filters))
|
|
{
|
|
$handler = $this->handlerResolver->resolve($filter['closure']);
|
|
|
|
$params = array_merge([$response], $filter['params']);
|
|
|
|
if(($filteredResponse = call_user_func_array($handler, $params)) !== null)
|
|
{
|
|
return $filteredResponse;
|
|
}
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Normalise the array filters attached to the route and merge with any global filters.
|
|
*
|
|
* @param $filters
|
|
* @return array
|
|
*/
|
|
private function parseFilters($filters)
|
|
{
|
|
$beforeFilter = array();
|
|
$afterFilter = array();
|
|
|
|
if(isset($filters[Route::BEFORE]))
|
|
{
|
|
foreach ($filters[Route::BEFORE] as $filter){
|
|
if(array_key_exists($filter['name'], $this->filters)){
|
|
$beforeFilter[] = array_merge($filter, ['closure' => $this->filters[$filter['name']]]);
|
|
}
|
|
}
|
|
//$beforeFilter = array_intersect_key($this->filters, array_flip((array) $filters[Route::BEFORE]));
|
|
}
|
|
|
|
if(isset($filters[Route::AFTER]))
|
|
{
|
|
foreach ($filters[Route::AFTER] as $filter){
|
|
if(array_key_exists($filter['name'], $this->filters)){
|
|
$afterFilter[] = array_merge($filter, ['closure' => $this->filters[$filter['name']]]);
|
|
}
|
|
}
|
|
//$afterFilter = array_intersect_key($this->filters, array_flip((array) $filters[Route::AFTER]));
|
|
}
|
|
|
|
return array($beforeFilter, $afterFilter);
|
|
}
|
|
|
|
/**
|
|
* Perform the route dispatching. Check static routes first followed by variable routes.
|
|
*
|
|
* @param $httpMethod
|
|
* @param $uri
|
|
* @throws Exception\HttpRouteNotFoundException
|
|
*/
|
|
private function dispatchRoute($httpMethod, $uri)
|
|
{
|
|
if (isset($this->staticRouteMap[$uri]))
|
|
{
|
|
return $this->dispatchStaticRoute($httpMethod, $uri);
|
|
}
|
|
|
|
return $this->dispatchVariableRoute($httpMethod, $uri);
|
|
}
|
|
|
|
/**
|
|
* Handle the dispatching of static routes.
|
|
*
|
|
* @param $httpMethod
|
|
* @param $uri
|
|
* @return mixed
|
|
* @throws Exception\HttpMethodNotAllowedException
|
|
*/
|
|
private function dispatchStaticRoute($httpMethod, $uri)
|
|
{
|
|
$routes = $this->staticRouteMap[$uri];
|
|
|
|
if (!isset($routes[$httpMethod]))
|
|
{
|
|
$httpMethod = $this->checkFallbacks($routes, $httpMethod);
|
|
}
|
|
|
|
return $routes[$httpMethod];
|
|
}
|
|
|
|
/**
|
|
* Check fallback routes: HEAD for GET requests followed by the ANY attachment.
|
|
*
|
|
* @param $routes
|
|
* @param $httpMethod
|
|
* @throws Exception\HttpMethodNotAllowedException
|
|
*/
|
|
private function checkFallbacks($routes, $httpMethod)
|
|
{
|
|
$additional = array(Route::ANY);
|
|
|
|
if($httpMethod === Route::HEAD)
|
|
{
|
|
$additional[] = Route::GET;
|
|
}
|
|
|
|
foreach($additional as $method)
|
|
{
|
|
if(isset($routes[$method]))
|
|
{
|
|
return $method;
|
|
}
|
|
}
|
|
|
|
$this->matchedRoute = $routes;
|
|
|
|
throw new HttpMethodNotAllowedException('Allow: ' . implode(', ', array_keys($routes)));
|
|
}
|
|
|
|
/**
|
|
* Handle the dispatching of variable routes.
|
|
*
|
|
* @param $httpMethod
|
|
* @param $uri
|
|
* @throws Exception\HttpMethodNotAllowedException
|
|
* @throws Exception\HttpRouteNotFoundException
|
|
*/
|
|
private function dispatchVariableRoute($httpMethod, $uri)
|
|
{
|
|
foreach ($this->variableRouteData as $data)
|
|
{
|
|
if (!preg_match($data['regex'], $uri, $matches))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$count = count($matches);
|
|
|
|
while(!isset($data['routeMap'][$count++]));
|
|
|
|
$routes = $data['routeMap'][$count - 1];
|
|
|
|
if (!isset($routes[$httpMethod]))
|
|
{
|
|
$httpMethod = $this->checkFallbacks($routes, $httpMethod);
|
|
}
|
|
|
|
foreach (array_values($routes[$httpMethod][2]) as $i => $varName)
|
|
{
|
|
if(!isset($matches[$i + 1]) || $matches[$i + 1] === '')
|
|
{
|
|
unset($routes[$httpMethod][2][$varName]);
|
|
}
|
|
else
|
|
{
|
|
$routes[$httpMethod][2][$varName] = $matches[$i + 1];
|
|
}
|
|
}
|
|
|
|
return $routes[$httpMethod];
|
|
}
|
|
|
|
throw new HttpRouteNotFoundException('Route ' . $uri . ' does not exist');
|
|
}
|
|
}
|