Created
September 25, 2017 21:32
-
-
Save itsgoingd/7f28f1690b037cb7734f25ce23528915 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Dingo\Api\Routing\Adapter; | |
use ArrayIterator; | |
use ReflectionClass; | |
use FastRoute\Dispatcher; | |
use FastRoute\RouteParser; | |
use Illuminate\Support\Str; | |
use Illuminate\Http\Request; | |
use FastRoute\DataGenerator; | |
use FastRoute\RouteCollector; | |
use Laravel\Lumen\Application; | |
use Dingo\Api\Contract\Routing\Adapter; | |
use Dingo\Api\Exception\UnknownVersionException; | |
class Lumen implements Adapter | |
{ | |
/** | |
* Lumen application instance. | |
* | |
* @var \Laravel\Lumen\Application | |
*/ | |
protected $app; | |
/** | |
* FastRoute parser instance. | |
* | |
* @var \FastRoute\RouteParser | |
*/ | |
protected $parser; | |
/** | |
* FastRoute data generator instance. | |
* | |
* @var \FastRoute\DataGenerator | |
*/ | |
protected $generator; | |
/** | |
* FastRoute dispatcher class name. | |
* | |
* @var string | |
*/ | |
protected $dispatcher; | |
/** | |
* Array of registered routes. | |
* | |
* @var array | |
*/ | |
protected $routes = []; | |
/** | |
* Indicates if the middleware has been removed from the application instance. | |
* | |
* @var bool | |
*/ | |
protected $middlewareRemoved = false; | |
/** | |
* Create a new lumen adapter instance. | |
* | |
* @param \Laravel\Lumen\Application $app | |
* @param \FastRoute\RouteParser $parser | |
* @param \FastRoute\DataGenerator $generator | |
* @param string $dispatcher | |
* | |
* @return void | |
*/ | |
public function __construct(Application $app, RouteParser $parser, DataGenerator $generator, $dispatcher) | |
{ | |
$this->app = $app; | |
$this->parser = $parser; | |
$this->generator = $generator; | |
$this->dispatcher = $dispatcher; | |
} | |
/** | |
* Dispatch a request. | |
* | |
* @param \Illuminate\Http\Request $request | |
* @param string $version | |
* | |
* @return mixed | |
*/ | |
public function dispatch(Request $request, $version) | |
{ | |
if (! isset($this->routes[$version])) { | |
throw new UnknownVersionException; | |
} | |
$this->removeMiddlewareFromApp(); | |
$routes = $this->mergeOldRoutes($version); | |
$this->app->setDispatcher( | |
new $this->dispatcher($routes->getData()) | |
); | |
$this->normalizeRequestUri($request); | |
return $this->app->dispatch($request); | |
} | |
/** | |
* Merge the old application routes with the API routes. | |
* | |
* @param string $version | |
* | |
* @return array | |
*/ | |
protected function mergeOldRoutes($version) | |
{ | |
if (! isset($this->oldRoutes)) { | |
$this->oldRoutes = $this->app->getRoutes(); | |
} | |
if (! isset($this->mergedRoutes[$version])) { | |
$this->mergedRoutes[$version] = $this->routes[$version]; | |
foreach ($this->oldRoutes as $route) { | |
$this->mergedRoutes[$version]->addRoute($route['method'], $route['uri'], $route['action']); | |
} | |
} | |
return $this->mergedRoutes[$version]; | |
} | |
/** | |
* Normalize the request URI so that Lumen can properly dispatch it. | |
* | |
* @param \Illuminate\Http\Request $request | |
* | |
* @return void | |
*/ | |
protected function normalizeRequestUri(Request $request) | |
{ | |
$query = $request->server->get('QUERY_STRING'); | |
$uri = '/'.trim(str_replace('?'.$query, '', $request->server->get('REQUEST_URI')), '/').($query ? '?'.$query : ''); | |
$request->server->set('REQUEST_URI', $uri); | |
} | |
/** | |
* Get the URI, methods, and action from the route. | |
* | |
* @param mixed $route | |
* @param \Illuminate\Http\Request $request | |
* | |
* @return array | |
*/ | |
public function getRouteProperties($route, Request $request) | |
{ | |
$uri = ltrim(isset($route['uri']) ? $route['uri'] : $request->getRequestUri(), '/'); | |
$methods = isset($route['methods']) ? $route['methods'] : (array) $request->getMethod(); | |
$action = (isset($route[1]) && is_array($route[1])) ? $route[1] : $route; | |
if (in_array('GET', $methods) && ! in_array('HEAD', $methods)) { | |
$methods[] = 'HEAD'; | |
} | |
return [$uri, $methods, $action]; | |
} | |
/** | |
* Add a route to the appropriate route collection. | |
* | |
* @param array $methods | |
* @param array $versions | |
* @param string $uri | |
* @param mixed $action | |
* | |
* @return void | |
*/ | |
public function addRoute(array $methods, array $versions, $uri, $action) | |
{ | |
$this->createRouteCollections($versions); | |
foreach ($versions as $version) { | |
foreach ($this->breakUriSegments($uri) as $uri) { | |
$this->routes[$version]->addRoute($methods, $uri, $action); | |
} | |
} | |
} | |
/** | |
* Break a URI that has optional segments into individual URIs. | |
* | |
* @param string $uri | |
* | |
* @return array | |
*/ | |
protected function breakUriSegments($uri) | |
{ | |
if (! Str::contains($uri, '?}')) { | |
return (array) $uri; | |
} | |
$segments = preg_split( | |
'/\/(\{.*?\})/', | |
preg_replace('/\{(.*?)\?\}/', '{$1}', $uri), | |
-1, | |
PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY | |
); | |
$uris = []; | |
while ($segments) { | |
$uris[] = implode('/', $segments); | |
array_pop($segments); | |
} | |
return $uris; | |
} | |
/** | |
* Create the route collections for the versions. | |
* | |
* @param array $versions | |
* | |
* @return void | |
*/ | |
protected function createRouteCollections(array $versions) | |
{ | |
foreach ($versions as $version) { | |
if (! isset($this->routes[$version])) { | |
$this->routes[$version] = new RouteCollector($this->parser, clone $this->generator); | |
} | |
} | |
} | |
/** | |
* Remove the global application middleware as it's run from this packages | |
* Request middleware. Lumen runs middleware later in its life cycle | |
* which results in some middleware being executed twice. | |
* | |
* @return void | |
*/ | |
protected function removeMiddlewareFromApp() | |
{ | |
if ($this->middlewareRemoved) { | |
return; | |
} | |
$this->middlewareRemoved = true; | |
$reflection = new ReflectionClass($this->app); | |
$property = $reflection->getProperty('middleware'); | |
$property->setAccessible(true); | |
$property->setValue($this->app, []); | |
$property->setAccessible(false); | |
} | |
/** | |
* Get all routes or only for a specific version. | |
* | |
* @param string $version | |
* | |
* @return mixed | |
*/ | |
public function getRoutes($version = null) | |
{ | |
if (! is_null($version)) { | |
return $this->routes[$version]; | |
} | |
return $this->routes; | |
} | |
/** | |
* Get routes in an iterable form. | |
* | |
* @param string $version | |
* | |
* @return \ArrayIterator | |
*/ | |
public function getIterableRoutes($version = null) | |
{ | |
$iterable = []; | |
foreach ($this->getRoutes($version) as $version => $collector) { | |
$routeData = $collector->getData(); | |
// The first element in the array are the static routes that do not have any parameters. | |
foreach ($this->normalizeStaticRoutes($routeData[0]) as $method => $routes) { | |
if ($method === 'HEAD') { | |
continue; | |
} | |
foreach ($routes as $route) { | |
$route['methods'] = $this->setRouteMethods($route, $method); | |
$iterable[$version][] = $route; | |
} | |
} | |
// The second element is the more complicated regex routes that have parameters. | |
foreach ($routeData[1] as $method => $routes) { | |
if ($method === 'HEAD') { | |
continue; | |
} | |
foreach ($routes as $data) { | |
foreach ($data['routeMap'] as list($route, $parameters)) { | |
$route['methods'] = $this->setRouteMethods($route, $method); | |
$iterable[$version][] = $route; | |
} | |
} | |
} | |
} | |
return new ArrayIterator($iterable); | |
} | |
/** | |
* Normalize the FastRoute static routes so they're the same across multiple versions. | |
* | |
* @param array $routes | |
* | |
* @return array | |
*/ | |
protected function normalizeStaticRoutes(array $routes) | |
{ | |
foreach (array_keys($routes) as $key) { | |
// If any of the keys are an HTTP method then we are running on a newer version of | |
// Lumen and FastRoute which means we can leave the routes as they are. | |
if ($this->stringIsHttpMethod($key)) { | |
return $routes; | |
} | |
} | |
$normalized = []; | |
// To normalize the routes we'll take the inner array which contains the routes method as the | |
// key and make that the parent element on the array. We'll then add all routes for a | |
// particular HTTP method as children of it by keying them to their URI. | |
foreach ($routes as $uri => $value) { | |
foreach ($value as $method => $route) { | |
$normalized[$method][$uri] = $route; | |
} | |
} | |
return $normalized; | |
} | |
/** | |
* Determine if a string is an HTTP method. | |
* | |
* @param string $string | |
* | |
* @return bool | |
*/ | |
protected function stringIsHttpMethod($string) | |
{ | |
$methods = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']; | |
return in_array($string, $methods, true); | |
} | |
/** | |
* Set the routes on the adapter. | |
* | |
* @param array $routes | |
* | |
* @return void | |
*/ | |
public function setRoutes(array $routes) | |
{ | |
// Route caching is not implemented for Lumen. | |
} | |
/** | |
* Prepare a route for serialization. | |
* | |
* @param mixed $route | |
* | |
* @return mixed | |
*/ | |
public function prepareRouteForSerialization($route) | |
{ | |
// Route caching is not implemented for Lumen. | |
} | |
/** | |
* Gather the route middlewares. | |
* | |
* @param array $route | |
* | |
* @return array | |
*/ | |
public function gatherRouteMiddlewares($route) | |
{ | |
// Route middleware in Lumen is not terminated. | |
return []; | |
} | |
/** | |
* Append given method to the current route methods. | |
* | |
* @param array $route | |
* @param string $method | |
* | |
* @return array | |
*/ | |
private function setRouteMethods($route, $method) | |
{ | |
return isset($route['methods']) | |
? array_push($route['methods'], $method) | |
: [$method]; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment