Skip to content

Commit

Permalink
Merge pull request #2639 from l0gicgate/4.x-DecoupleFastRouteDispatching
Browse files Browse the repository at this point in the history
4.x - Decouple FastRoute Dispatcher From RouteResolver
  • Loading branch information
l0gicgate authored Apr 20, 2019
2 parents d5497de + 1ce3288 commit f1b45ef
Show file tree
Hide file tree
Showing 11 changed files with 976 additions and 860 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Changelog


## 4.0.0 - TBD
## 4.0.0 - 2019-07-03

### Added
- [#2639](https://github.com/slimphp/Slim/pull/2639) Add `DispatcherInterface` and decouple FastRoute dispatcher entirely from core. This enables us to swap out our router implementation for any other router.
- [#2638](https://github.com/slimphp/Slim/pull/2638) Add `RouteCollector::fullUrlFor()` to give the ability to generate fully qualified URLs
- [#2634](https://github.com/slimphp/Slim/pull/2634) Added ability to set invocation strategy on a per-route basis.
- [#2555](https://github.com/slimphp/Slim/pull/2555) Added PSR-15 Middleware Support
- [#2529](https://github.com/slimphp/Slim/pull/2529) Slim no longer ships with a PSR-7 implementation. You need to provide a PSR-7 ServerRequest and a PSR-17 ResponseFactory to run Slim.
Expand All @@ -21,6 +23,7 @@

### Deprecated

- [#2638](https://github.com/slimphp/Slim/pull/2638) Deprecate `RouteCollector::pathFor()` which gets replaced by `RouteCollector::urlFor()` preserving the orignal functionality
- [#2555](https://github.com/slimphp/Slim/pull/2555) Double-Pass Middleware Support has been deprecated

### Removed
Expand Down
26 changes: 26 additions & 0 deletions Slim/Interfaces/DispatcherInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);

namespace Slim\Interfaces;

use Slim\Routing\RoutingResults;

interface DispatcherInterface
{
/**
* Get allowed methods for a given uri
*
* @param string $uri
* @return array
*/
public function getAllowedMethods(string $uri): array;

/**
* Get routing results for a given request method and uri
*
* @param string $method
* @param string $uri
* @return RoutingResults
*/
public function dispatch(string $method, string $uri): RoutingResults;
}
2 changes: 1 addition & 1 deletion Slim/Interfaces/RouteCollectorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public function removeNamedRoute(string $name): RouteCollectorInterface;
*
* @return RouteInterface
*
* @throws RuntimeException
* @throws RuntimeException If route of identifier does not exist
*/
public function lookupRoute(string $identifier): RouteInterface;

Expand Down
8 changes: 4 additions & 4 deletions Slim/Middleware/RoutingMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

namespace Slim\Middleware;

use FastRoute\Dispatcher;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
Expand All @@ -18,6 +17,7 @@
use Slim\Exception\HttpMethodNotAllowedException;
use Slim\Exception\HttpNotFoundException;
use Slim\Interfaces\RouteResolverInterface;
use Slim\Routing\RoutingResults;

/**
* Class RoutingMiddleware
Expand Down Expand Up @@ -74,7 +74,7 @@ public function performRouting(ServerRequestInterface $request): ServerRequestIn
$routeStatus = $routingResults->getRouteStatus();

switch ($routeStatus) {
case Dispatcher::FOUND:
case RoutingResults::FOUND:
$routeArguments = $routingResults->getRouteArguments();
$routeIdentifier = $routingResults->getRouteIdentifier() ?? '';
$route = $this->routeResolver
Expand All @@ -84,10 +84,10 @@ public function performRouting(ServerRequestInterface $request): ServerRequestIn
->withAttribute('route', $route)
->withAttribute('routingResults', $routingResults);

case Dispatcher::NOT_FOUND:
case RoutingResults::NOT_FOUND:
throw new HttpNotFoundException($request);

case Dispatcher::METHOD_NOT_ALLOWED:
case RoutingResults::METHOD_NOT_ALLOWED:
$exception = new HttpMethodNotAllowedException($request);
$exception->setAllowedMethods($routingResults->getAllowedMethods());
throw $exception;
Expand Down
157 changes: 62 additions & 95 deletions Slim/Routing/Dispatcher.php
Original file line number Diff line number Diff line change
@@ -1,132 +1,99 @@
<?php
/**
* Slim Framework (https://slimframework.com)
*
* @license https://github.com/slimphp/Slim/blob/4.x/LICENSE.md (MIT License)
*/

declare(strict_types=1);

namespace Slim\Routing;

use FastRoute\Dispatcher\GroupCountBased;
use FastRoute\RouteCollector;
use FastRoute\RouteParser;
use FastRoute\RouteParser\Std;
use Slim\Interfaces\DispatcherInterface;
use Slim\Interfaces\RouteCollectorInterface;

class Dispatcher extends GroupCountBased
/**
* Class Dispatcher
*
* @package Slim\Routing
*/
class Dispatcher implements DispatcherInterface
{
/**
* @var string
* @var RouteCollectorInterface
*/
private $httpMethod;
private $routeCollector;

/**
* @var string
* @var RouteParser
*/
private $uri;
private $routeParser;

/**
* @var array
* @var FastRouteDispatcher|null
*/
private $allowedMethods = [];
private $dispatcher;

/**
* @param string $httpMethod
* @param string $uri
* @return RoutingResults
* Dispatcher constructor.
*
* @param RouteCollectorInterface $routeCollector
* @param RouteParser|null $routeParser
*/
public function dispatch($httpMethod, $uri): RoutingResults
public function __construct(RouteCollectorInterface $routeCollector, RouteParser $routeParser = null)
{
$this->httpMethod = $httpMethod;
$this->uri = $uri;

if (isset($this->staticRouteMap[$httpMethod][$uri])) {
return $this->routingResults(self::FOUND, $this->staticRouteMap[$httpMethod][$uri]);
}
$this->routeCollector = $routeCollector;
$this->routeParser = $routeParser ?? new Std();
}

$varRouteData = $this->variableRouteData;
if (isset($varRouteData[$httpMethod])) {
$result = $this->dispatchVariableRoute($varRouteData[$httpMethod], $uri);
$routingResults = $this->routingResultsFromVariableRouteResults($result);
if ($routingResults->getRouteStatus() === self::FOUND) {
return $routingResults;
}
/**
* @return FastRouteDispatcher
*/
protected function createDispatcher(): FastRouteDispatcher
{
if ($this->dispatcher) {
return $this->dispatcher;
}

// For HEAD requests, attempt fallback to GET
if ($httpMethod === 'HEAD') {
if (isset($this->staticRouteMap['GET'][$uri])) {
return $this->routingResults(self::FOUND, $this->staticRouteMap['GET'][$uri]);
}
if (isset($varRouteData['GET'])) {
$result = $this->dispatchVariableRoute($varRouteData['GET'], $uri);
return $this->routingResultsFromVariableRouteResults($result);
$routeDefinitionCallback = function (RouteCollector $r) {
foreach ($this->routeCollector->getRoutes() as $route) {
$r->addRoute($route->getMethods(), $route->getPattern(), $route->getIdentifier());
}
};

$cacheFile = $this->routeCollector->getCacheFile();
if ($cacheFile) {
/** @var FastRouteDispatcher $dispatcher */
$dispatcher = \FastRoute\cachedDispatcher($routeDefinitionCallback, [
'dispatcher' => FastRouteDispatcher::class,
'routeParser' => $this->routeParser,
'cacheFile' => $cacheFile,
]);
} else {
/** @var FastRouteDispatcher $dispatcher */
$dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback, [
'dispatcher' => FastRouteDispatcher::class,
'routeParser' => $this->routeParser,
]);
}

// If nothing else matches, try fallback routes
if (isset($this->staticRouteMap['*'][$uri])) {
return $this->routingResults(self::FOUND, $this->staticRouteMap['*'][$uri]);
}
if (isset($varRouteData['*'])) {
$result = $this->dispatchVariableRoute($varRouteData['*'], $uri);
return $this->routingResultsFromVariableRouteResults($result);
}

if (count($this->getAllowedMethods($httpMethod, $uri))) {
return $this->routingResults(self::METHOD_NOT_ALLOWED);
}

return $this->routingResults(self::NOT_FOUND);
$this->dispatcher = $dispatcher;
return $this->dispatcher;
}

/**
* @param int $status
* @param string|null $handler
* @param array $arguments
* @return RoutingResults
* {@inheritdoc}
*/
protected function routingResults(int $status, string $handler = null, array $arguments = []): RoutingResults
public function getAllowedMethods(string $uri): array
{
return new RoutingResults($this, $this->httpMethod, $this->uri, $status, $handler, $arguments);
}

/**
* @param array $result
* @return RoutingResults
*/
protected function routingResultsFromVariableRouteResults(array $result): RoutingResults
{
if ($result[0] === self::FOUND) {
return $this->routingResults(self::FOUND, $result[1], $result[2]);
}
return $this->routingResults(self::NOT_FOUND);
$dispatcher = $this->createDispatcher();
return $dispatcher->getAllowedMethods($uri);
}

/**
* @param string $httpMethod
* @param string $uri
* @return array
* {@inheritdoc}
*/
public function getAllowedMethods(string $httpMethod, string $uri): array
public function dispatch(string $method, string $uri): RoutingResults
{
if (isset($this->allowedMethods[$uri])) {
return $this->allowedMethods[$uri];
}

$this->allowedMethods[$uri] = [];
foreach ($this->staticRouteMap as $method => $uriMap) {
if ($method !== $httpMethod && isset($uriMap[$uri])) {
$this->allowedMethods[$uri][] = $method;
}
}

$varRouteData = $this->variableRouteData;
foreach ($varRouteData as $method => $routeData) {
$result = $this->dispatchVariableRoute($routeData, $uri);
if ($result[0] === self::FOUND) {
$this->allowedMethods[$uri][] = $method;
}
}

return $this->allowedMethods[$uri];
$dispatcher = $this->createDispatcher();
$results = $dispatcher->dispatch($method, $uri);
return new RoutingResults($this, $method, $uri, $results[0], $results[1], $results[2]);
}
}
107 changes: 107 additions & 0 deletions Slim/Routing/FastRouteDispatcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php
/**
* Slim Framework (https://slimframework.com)
*
* @license https://github.com/slimphp/Slim/blob/4.x/LICENSE.md (MIT License)
*/

declare(strict_types=1);

namespace Slim\Routing;

use FastRoute\Dispatcher\GroupCountBased;

class FastRouteDispatcher extends GroupCountBased
{
/**
* @var array
*/
private $allowedMethods = [];

/**
* @param string $httpMethod
* @param string $uri
* @return array
*/
public function dispatch($httpMethod, $uri): array
{
if (isset($this->staticRouteMap[$httpMethod][$uri])) {
return [self::FOUND, $this->staticRouteMap[$httpMethod][$uri], []];
}

$varRouteData = $this->variableRouteData;
if (isset($varRouteData[$httpMethod])) {
$result = $this->dispatchVariableRoute($varRouteData[$httpMethod], $uri);
$routingResults = $this->routingResultsFromVariableRouteResults($result);
if ($routingResults[0] === self::FOUND) {
return $routingResults;
}
}

// For HEAD requests, attempt fallback to GET
if ($httpMethod === 'HEAD') {
if (isset($this->staticRouteMap['GET'][$uri])) {
return [self::FOUND, $this->staticRouteMap['GET'][$uri], []];
}
if (isset($varRouteData['GET'])) {
$result = $this->dispatchVariableRoute($varRouteData['GET'], $uri);
return $this->routingResultsFromVariableRouteResults($result);
}
}

// If nothing else matches, try fallback routes
if (isset($this->staticRouteMap['*'][$uri])) {
return [self::FOUND, $this->staticRouteMap['*'][$uri], []];
}
if (isset($varRouteData['*'])) {
$result = $this->dispatchVariableRoute($varRouteData['*'], $uri);
return $this->routingResultsFromVariableRouteResults($result);
}

if (count($this->getAllowedMethods($uri))) {
return [self::METHOD_NOT_ALLOWED, null, []];
}

return [self::NOT_FOUND, null, []];
}

/**
* @param array $result
* @return array
*/
protected function routingResultsFromVariableRouteResults(array $result): array
{
if ($result[0] === self::FOUND) {
return [self::FOUND, $result[1], $result[2]];
}
return [self::NOT_FOUND, null, []];
}

/**
* @param string $uri
* @return array
*/
public function getAllowedMethods(string $uri): array
{
if (isset($this->allowedMethods[$uri])) {
return $this->allowedMethods[$uri];
}

$this->allowedMethods[$uri] = [];
foreach ($this->staticRouteMap as $method => $uriMap) {
if (isset($uriMap[$uri])) {
$this->allowedMethods[$uri][] = $method;
}
}

$varRouteData = $this->variableRouteData;
foreach ($varRouteData as $method => $routeData) {
$result = $this->dispatchVariableRoute($routeData, $uri);
if ($result[0] === self::FOUND) {
$this->allowedMethods[$uri][] = $method;
}
}

return $this->allowedMethods[$uri];
}
}
Loading

0 comments on commit f1b45ef

Please sign in to comment.