Remove RouteInterface

This commit is contained in:
PJ Dietz 2020-08-14 07:24:00 -04:00
parent 4eec56b582
commit fec5a4d405
16 changed files with 119 additions and 159 deletions

View File

@ -14,7 +14,7 @@ class PrefixRoute extends Route
public function getType(): int
{
return RouteInterface::TYPE_PREFIX;
return Route::TYPE_PREFIX;
}
/**

View File

@ -14,7 +14,7 @@ class RegexRoute extends Route
public function getType(): int
{
return RouteInterface::TYPE_PATTERN;
return Route::TYPE_PATTERN;
}
/**

View File

@ -4,12 +4,21 @@ namespace WellRESTed\Routing\Route;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use RuntimeException;
use WellRESTed\MiddlewareInterface;
/**
* @internal
*/
abstract class Route implements RouteInterface
abstract class Route implements MiddlewareInterface
{
/** Matches when request path is an exact match to entire target */
public const TYPE_STATIC = 0;
/** Matches when request path is an exact match to start of target */
public const TYPE_PREFIX = 1;
/** Matches by request path by pattern and may extract matched varialbes */
public const TYPE_PATTERN = 2;
/** @var string */
protected $target;
/** @var MethodMap */
@ -21,6 +30,45 @@ abstract class Route implements RouteInterface
$this->methodMap = $methodMap;
}
/**
* Return the Route::TYPE_ constants that identifies the type.
*
* TYPE_STATIC indicates the route MUST match only when the path is an
* exact match to the route's entire target. This route type SHOULD NOT
* provide path variables.
*
* TYPE_PREFIX indicates the route MUST match when the route's target
* appears in its entirety at the beginning of the path.
*
* TYPE_PATTERN indicates that matchesRequestTarget MUST be used
* to determine a match against a given path. This route type SHOULD
* provide path variables.
*
* @return int One of the Route::TYPE_ constants.
*/
abstract public function getType(): int;
/**
* Return an array of variables extracted from the path most recently
* passed to matchesRequestTarget.
*
* If the path does not contain variables, or if matchesRequestTarget
* has not yet been called, this method MUST return an empty array.
*
* @return array
*/
abstract public function getPathVariables(): array;
/**
* Examines a request target to see if it is a match for the route.
*
* @param string $requestTarget
* @return bool
* @throws RuntimeException Error occurred testing the target such as an
* invalid regular expression
*/
abstract public function matchesRequestTarget(string $requestTarget): bool;
/**
* Path, partial path, or pattern to match request paths against.
*
@ -50,6 +98,12 @@ abstract class Route implements RouteInterface
$this->methodMap->register($method, $dispatchable);
}
/**
* @param ServerRequestInterface $request
* @param ResponseInterface $response
* @param callable $next
* @return ResponseInterface
*/
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
{
$map = $this->methodMap;

View File

@ -25,7 +25,7 @@ class RouteFactory implements RouteFactoryInterface
* - Regular expressions will create RegexRoutes
*
* @param string $target Route target or target pattern
* @return RouteInterface
* @return Route
*/
public function create($target)
{

View File

@ -16,7 +16,7 @@ interface RouteFactoryInterface
* - Regular expressions will create RegexRoutes
*
* @param string $target Route target or target pattern
* @return RouteInterface
* @return Route
*/
public function create($target);
}

View File

@ -1,80 +0,0 @@
<?php
namespace WellRESTed\Routing\Route;
use WellRESTed\MiddlewareInterface;
/**
* @internal
*/
interface RouteInterface extends MiddlewareInterface
{
/** Matches when request path is an exact match to entire target */
const TYPE_STATIC = 0;
/** Matches when request path is an exact match to start of target */
const TYPE_PREFIX = 1;
/** Matches by request path by pattern and may extract matched varialbes */
const TYPE_PATTERN = 2;
/**
* Path, partial path, or pattern to match request paths against.
*
* @return string
*/
public function getTarget(): string;
/**
* Return the RouteInterface::TYPE_ constants that identifies the type.
*
* TYPE_STATIC indicates the route MUST match only when the path is an
* exact match to the route's target. This route type SHOULD NOT
* provide path variables.
*
* TYPE_PREFIX indicates the route MUST match when the route's target
* appears in its entirety at the beginning of the path.
*
* TYPE_PATTERN indicates that matchesRequestTarget MUST be used
* to determine a match against a given path. This route type SHOULD
* provide path variables.
*
* @return int One of the RouteInterface::TYPE_ constants.
*/
public function getType(): int;
/**
* Return an array of variables extracted from the path most recently
* passed to matchesRequestTarget.
*
* If the path does not contain variables, or if matchesRequestTarget
* has not yet been called, this method MUST return an empty array.
*
* @return array
*/
public function getPathVariables(): array;
/**
* Examines a request target to see if it is a match for the route.
*
* @param string $requestTarget
* @return boolean
* @throw \RuntimeException Error occurred testing the target such as an
* invalid regular expression
*/
public function matchesRequestTarget(string $requestTarget): bool;
/**
* Register a dispatchable (handler or middleware) with a method.
*
* $method may be:
* - A single verb ("GET"),
* - A comma-separated list of verbs ("GET,PUT,DELETE")
* - "*" to indicate any method.
*
* $dispatchable may be anything a Dispatcher can dispatch.
* @see DispatcherInterface::dispatch
*
* @param string $method
* @param mixed $dispatchable
*/
public function register(string $method, $dispatchable): void;
}

View File

@ -9,7 +9,7 @@ class StaticRoute extends Route
{
public function getType(): int
{
return RouteInterface::TYPE_STATIC;
return Route::TYPE_STATIC;
}
/**

View File

@ -22,7 +22,7 @@ class TemplateRoute extends Route
public function getType(): int
{
return RouteInterface::TYPE_PATTERN;
return Route::TYPE_PATTERN;
}
public function getPathVariables(): array

View File

@ -6,9 +6,9 @@ use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use WellRESTed\Dispatching\Dispatcher;
use WellRESTed\Dispatching\DispatcherInterface;
use WellRESTed\Routing\Route\Route;
use WellRESTed\Routing\Route\RouteFactory;
use WellRESTed\Routing\Route\RouteFactoryInterface;
use WellRESTed\Routing\Route\RouteInterface;
class Router
{
@ -18,13 +18,13 @@ class Router
private $dispatcher;
/** @var RouteFactoryInterface */
private $factory;
/** @var RouteInterface[] Array of Route objects */
/** @var Route[] Array of Route objects */
private $routes;
/** @var RouteInterface[] Hash array mapping exact paths to routes */
/** @var Route[] Hash array mapping exact paths to routes */
private $staticRoutes;
/** @var RouteInterface[] Hash array mapping path prefixes to routes */
/** @var Route[] Hash array mapping path prefixes to routes */
private $prefixRoutes;
/** @var RouteInterface[] Hash array mapping path prefixes to routes */
/** @var Route[] Hash array mapping path prefixes to routes */
private $patternRoutes;
/** @var mixed[] List array of middleware */
private $stack;
@ -207,7 +207,7 @@ class Router
return new RouteFactory($dispatcher);
}
private function getRouteForTarget(string $target): RouteInterface
private function getRouteForTarget(string $target): Route
{
if (isset($this->routes[$target])) {
$route = $this->routes[$target];
@ -218,26 +218,26 @@ class Router
return $route;
}
private function registerRouteForTarget(RouteInterface $route, string $target): void
private function registerRouteForTarget(Route $route, string $target): void
{
// Store the route to the hash indexed by original target.
$this->routes[$target] = $route;
// Store the route to the array of routes for its type.
switch ($route->getType()) {
case RouteInterface::TYPE_STATIC:
case Route::TYPE_STATIC:
$this->staticRoutes[$route->getTarget()] = $route;
break;
case RouteInterface::TYPE_PREFIX:
case Route::TYPE_PREFIX:
$this->prefixRoutes[rtrim($route->getTarget(), '*')] = $route;
break;
case RouteInterface::TYPE_PATTERN:
case Route::TYPE_PATTERN:
$this->patternRoutes[] = $route;
break;
}
}
private function getStaticRoute(string $requestTarget): ?RouteInterface
private function getStaticRoute(string $requestTarget): ?Route
{
if (isset($this->staticRoutes[$requestTarget])) {
return $this->staticRoutes[$requestTarget];
@ -245,7 +245,7 @@ class Router
return null;
}
private function getPrefixRoute(string $requestTarget): ?RouteInterface
private function getPrefixRoute(string $requestTarget): ?Route
{
// Find all prefixes that match the start of this path.
$prefixes = array_keys($this->prefixRoutes);

View File

@ -20,7 +20,7 @@ class PrefixRouteTest extends TestCase
{
$methodMap = $this->prophesize(MethodMap::class);
$route = new PrefixRoute('/*', $methodMap->reveal());
$this->assertSame(RouteInterface::TYPE_PREFIX, $route->getType());
$this->assertSame(Route::TYPE_PREFIX, $route->getType());
}
public function testReturnsEmptyArrayForPathVariables(): void

View File

@ -1,12 +1,9 @@
<?php
namespace WellRESTed\Test\Unit\Routing\Route;
namespace WellRESTed\Routing\Route;
use Prophecy\PhpUnit\ProphecyTrait;
use RuntimeException;
use WellRESTed\Routing\Route\MethodMap;
use WellRESTed\Routing\Route\RegexRoute;
use WellRESTed\Routing\Route\RouteInterface;
use WellRESTed\Test\TestCase;
class RegexRouteTest extends TestCase
@ -23,7 +20,7 @@ class RegexRouteTest extends TestCase
public function testReturnsPatternType()
{
$route = new RegexRoute('/', $this->methodMap->reveal());
$this->assertSame(RouteInterface::TYPE_PATTERN, $route->getType());
$this->assertSame(Route::TYPE_PATTERN, $route->getType());
}
/** @dataProvider matchingRouteProvider */

View File

@ -1,11 +1,9 @@
<?php
namespace WellRESTed\Test\Unit\Routing\Route;
namespace WellRESTed\Routing\Route;
use Prophecy\PhpUnit\ProphecyTrait;
use WellRESTed\Dispatching\DispatcherInterface;
use WellRESTed\Routing\Route\RouteFactory;
use WellRESTed\Routing\Route\RouteInterface;
use WellRESTed\Test\TestCase;
class RouteFactoryTest extends TestCase
@ -23,27 +21,27 @@ class RouteFactoryTest extends TestCase
{
$factory = new RouteFactory($this->dispatcher->reveal());
$route = $factory->create('/cats/');
$this->assertSame(RouteInterface::TYPE_STATIC, $route->getType());
$this->assertSame(Route::TYPE_STATIC, $route->getType());
}
public function testCreatesPrefixRoute()
{
$factory = new RouteFactory($this->dispatcher->reveal());
$route = $factory->create('/cats/*');
$this->assertSame(RouteInterface::TYPE_PREFIX, $route->getType());
$this->assertSame(Route::TYPE_PREFIX, $route->getType());
}
public function testCreatesRegexRoute()
{
$factory = new RouteFactory($this->dispatcher->reveal());
$route = $factory->create('~/cat/[0-9]+~');
$this->assertSame(RouteInterface::TYPE_PATTERN, $route->getType());
$this->assertSame(Route::TYPE_PATTERN, $route->getType());
}
public function testCreatesTemplateRoute()
{
$factory = new RouteFactory($this->dispatcher->reveal());
$route = $factory->create('/cat/{id}');
$this->assertSame(RouteInterface::TYPE_PATTERN, $route->getType());
$this->assertSame(Route::TYPE_PATTERN, $route->getType());
}
}

View File

@ -1,14 +1,12 @@
<?php
namespace WellRESTed\Test\Unit\Routing\Route;
namespace WellRESTed\Routing\Route;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Http\Server\RequestHandlerInterface;
use WellRESTed\Message\Response;
use WellRESTed\Message\ServerRequest;
use WellRESTed\Routing\Route\MethodMap;
use WellRESTed\Routing\Route\StaticRoute;
use WellRESTed\Test\TestCase;
class RouteTest extends TestCase
@ -33,12 +31,12 @@ class RouteTest extends TestCase
);
}
public function testReturnsTarget()
public function testReturnsTarget(): void
{
$this->assertSame(self::TARGET, $this->route->getTarget());
}
public function testRegistersDispatchableWithMethodMap()
public function testRegistersDispatchableWithMethodMap(): void
{
$handler = $this->prophesize(RequestHandlerInterface::class)->reveal();
@ -47,7 +45,7 @@ class RouteTest extends TestCase
$this->methodMap->register('GET', $handler)->shouldHaveBeenCalled();
}
public function testDispatchesMethodMap()
public function testDispatchesMethodMap(): void
{
$request = new ServerRequest();
$response = new Response();

View File

@ -1,11 +1,8 @@
<?php
namespace WellRESTed\Test\Unit\Routing\Route;
namespace WellRESTed\Routing\Route;
use Prophecy\PhpUnit\ProphecyTrait;
use WellRESTed\Routing\Route\MethodMap;
use WellRESTed\Routing\Route\RouteInterface;
use WellRESTed\Routing\Route\StaticRoute;
use WellRESTed\Test\TestCase;
class StaticRouteTest extends TestCase
@ -16,7 +13,7 @@ class StaticRouteTest extends TestCase
{
$methodMap = $this->prophesize(MethodMap::class);
$route = new StaticRoute('/', $methodMap->reveal());
$this->assertSame(RouteInterface::TYPE_STATIC, $route->getType());
$this->assertSame(Route::TYPE_STATIC, $route->getType());
}
public function testMatchesExactRequestTarget()

View File

@ -1,11 +1,8 @@
<?php
namespace WellRESTed\Test\Unit\Routing\Route;
namespace WellRESTed\Routing\Route;
use Prophecy\PhpUnit\ProphecyTrait;
use WellRESTed\Routing\Route\MethodMap;
use WellRESTed\Routing\Route\RouteInterface;
use WellRESTed\Routing\Route\TemplateRoute;
use WellRESTed\Test\TestCase;
class TemplateRouteTest extends TestCase
@ -48,7 +45,7 @@ class TemplateRouteTest extends TestCase
public function testReturnsPatternType()
{
$route = new TemplateRoute('/', $this->methodMap->reveal());
$this->assertSame(RouteInterface::TYPE_PATTERN, $route->getType());
$this->assertSame(Route::TYPE_PATTERN, $route->getType());
}
// -------------------------------------------------------------------------

View File

@ -1,6 +1,6 @@
<?php
namespace WellRESTed\Test\Unit\Routing;
namespace WellRESTed\Routing;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
@ -8,10 +8,9 @@ use WellRESTed\Dispatching\Dispatcher;
use WellRESTed\Dispatching\DispatcherInterface;
use WellRESTed\Message\Response;
use WellRESTed\Message\ServerRequest;
use WellRESTed\Routing\Route\Route;
use WellRESTed\Routing\Route\RouteFactory;
use WellRESTed\Routing\Route\RouteFactoryInterface;
use WellRESTed\Routing\Route\RouteInterface;
use WellRESTed\Routing\Router;
use WellRESTed\Test\Doubles\NextMock;
use WellRESTed\Test\TestCase;
@ -30,10 +29,10 @@ class RouterTest extends TestCase
{
parent::setUp();
$this->route = $this->prophesize(RouteInterface::class);
$this->route = $this->prophesize(Route::class);
$this->route->__invoke(Argument::cetera())->willReturn(new Response());
$this->route->register(Argument::cetera());
$this->route->getType()->willReturn(RouteInterface::TYPE_STATIC);
$this->route->getType()->willReturn(Route::TYPE_STATIC);
$this->route->getTarget()->willReturn('/');
$this->route->getPathVariables()->willReturn([]);
@ -93,7 +92,7 @@ class RouterTest extends TestCase
$this->request = $this->request->withRequestTarget($target);
$this->route->getTarget()->willReturn($target);
$this->route->getType()->willReturn(RouteInterface::TYPE_STATIC);
$this->route->getType()->willReturn(Route::TYPE_STATIC);
$this->router->register('GET', $target, 'middleware');
$this->router->__invoke($this->request, $this->response, $this->next);
@ -108,7 +107,7 @@ class RouterTest extends TestCase
$this->request = $this->request->withRequestTarget('/animals/cats/molly');
$this->route->getTarget()->willReturn($target);
$this->route->getType()->willReturn(RouteInterface::TYPE_PREFIX);
$this->route->getType()->willReturn(Route::TYPE_PREFIX);
$this->router->register('GET', $target, 'middleware');
$this->router->__invoke($this->request, $this->response, $this->next);
@ -123,7 +122,7 @@ class RouterTest extends TestCase
$this->request = $this->request->withRequestTarget($target);
$this->route->getTarget()->willReturn($target);
$this->route->getType()->willReturn(RouteInterface::TYPE_PATTERN);
$this->route->getType()->willReturn(Route::TYPE_PATTERN);
$this->route->matchesRequestTarget(Argument::cetera())->willReturn(true);
$this->router->register('GET', $target, 'middleware');
@ -135,16 +134,16 @@ class RouterTest extends TestCase
public function testDispatchesStaticRouteBeforePrefixRoute()
{
$staticRoute = $this->prophesize(RouteInterface::class);
$staticRoute = $this->prophesize(Route::class);
$staticRoute->register(Argument::cetera());
$staticRoute->getTarget()->willReturn('/cats/');
$staticRoute->getType()->willReturn(RouteInterface::TYPE_STATIC);
$staticRoute->getType()->willReturn(Route::TYPE_STATIC);
$staticRoute->__invoke(Argument::cetera())->willReturn(new Response());
$prefixRoute = $this->prophesize(RouteInterface::class);
$prefixRoute = $this->prophesize(Route::class);
$prefixRoute->register(Argument::cetera());
$prefixRoute->getTarget()->willReturn('/cats/*');
$prefixRoute->getType()->willReturn(RouteInterface::TYPE_PREFIX);
$prefixRoute->getType()->willReturn(Route::TYPE_PREFIX);
$prefixRoute->__invoke(Argument::cetera())->willReturn(new Response());
$this->request = $this->request->withRequestTarget('/cats/');
@ -164,16 +163,16 @@ class RouterTest extends TestCase
{
// Note: The longest route is also good for 2 points in Settlers of Catan.
$shortRoute = $this->prophesize(RouteInterface::class);
$shortRoute = $this->prophesize(Route::class);
$shortRoute->register(Argument::cetera());
$shortRoute->getTarget()->willReturn('/animals/*');
$shortRoute->getType()->willReturn(RouteInterface::TYPE_PREFIX);
$shortRoute->getType()->willReturn(Route::TYPE_PREFIX);
$shortRoute->__invoke(Argument::cetera())->willReturn(new Response());
$longRoute = $this->prophesize(RouteInterface::class);
$longRoute = $this->prophesize(Route::class);
$longRoute->register(Argument::cetera());
$longRoute->getTarget()->willReturn('/animals/cats/*');
$longRoute->getType()->willReturn(RouteInterface::TYPE_PREFIX);
$longRoute->getType()->willReturn(Route::TYPE_PREFIX);
$longRoute->__invoke(Argument::cetera())->willReturn(new Response());
$this->request = $this->request
@ -192,16 +191,16 @@ class RouterTest extends TestCase
public function testDispatchesPrefixRouteBeforePatternRoute()
{
$prefixRoute = $this->prophesize(RouteInterface::class);
$prefixRoute = $this->prophesize(Route::class);
$prefixRoute->register(Argument::cetera());
$prefixRoute->getTarget()->willReturn('/cats/*');
$prefixRoute->getType()->willReturn(RouteInterface::TYPE_PREFIX);
$prefixRoute->getType()->willReturn(Route::TYPE_PREFIX);
$prefixRoute->__invoke(Argument::cetera())->willReturn(new Response());
$patternRoute = $this->prophesize(RouteInterface::class);
$patternRoute = $this->prophesize(Route::class);
$patternRoute->register(Argument::cetera());
$patternRoute->getTarget()->willReturn('/cats/{id}');
$patternRoute->getType()->willReturn(RouteInterface::TYPE_PATTERN);
$patternRoute->getType()->willReturn(Route::TYPE_PATTERN);
$patternRoute->__invoke(Argument::cetera())->willReturn(new Response());
$this->request = $this->request->withRequestTarget('/cats/');
@ -219,18 +218,18 @@ class RouterTest extends TestCase
public function testDispatchesFirstMatchingPatternRoute()
{
$patternRoute1 = $this->prophesize(RouteInterface::class);
$patternRoute1 = $this->prophesize(Route::class);
$patternRoute1->register(Argument::cetera());
$patternRoute1->getTarget()->willReturn('/cats/{id}');
$patternRoute1->getType()->willReturn(RouteInterface::TYPE_PATTERN);
$patternRoute1->getType()->willReturn(Route::TYPE_PATTERN);
$patternRoute1->getPathVariables()->willReturn([]);
$patternRoute1->matchesRequestTarget(Argument::any())->willReturn(true);
$patternRoute1->__invoke(Argument::cetera())->willReturn(new Response());
$patternRoute2 = $this->prophesize(RouteInterface::class);
$patternRoute2 = $this->prophesize(Route::class);
$patternRoute2->register(Argument::cetera());
$patternRoute2->getTarget()->willReturn('/cats/{name}');
$patternRoute2->getType()->willReturn(RouteInterface::TYPE_PATTERN);
$patternRoute2->getType()->willReturn(Route::TYPE_PATTERN);
$patternRoute2->getPathVariables()->willReturn([]);
$patternRoute2->matchesRequestTarget(Argument::any())->willReturn(true);
$patternRoute2->__invoke(Argument::cetera())->willReturn(new Response());
@ -250,18 +249,18 @@ class RouterTest extends TestCase
public function testStopsTestingPatternsAfterFirstSuccessfulMatch()
{
$patternRoute1 = $this->prophesize(RouteInterface::class);
$patternRoute1 = $this->prophesize(Route::class);
$patternRoute1->register(Argument::cetera());
$patternRoute1->getTarget()->willReturn('/cats/{id}');
$patternRoute1->getType()->willReturn(RouteInterface::TYPE_PATTERN);
$patternRoute1->getType()->willReturn(Route::TYPE_PATTERN);
$patternRoute1->getPathVariables()->willReturn([]);
$patternRoute1->matchesRequestTarget(Argument::any())->willReturn(true);
$patternRoute1->__invoke(Argument::cetera())->willReturn(new Response());
$patternRoute2 = $this->prophesize(RouteInterface::class);
$patternRoute2 = $this->prophesize(Route::class);
$patternRoute2->register(Argument::cetera());
$patternRoute2->getTarget()->willReturn('/cats/{name}');
$patternRoute2->getType()->willReturn(RouteInterface::TYPE_PATTERN);
$patternRoute2->getType()->willReturn(Route::TYPE_PATTERN);
$patternRoute2->getPathVariables()->willReturn([]);
$patternRoute2->matchesRequestTarget(Argument::any())->willReturn(true);
$patternRoute2->__invoke(Argument::cetera())->willReturn(new Response());
@ -286,7 +285,7 @@ class RouterTest extends TestCase
$this->request = $this->request->withRequestTarget($target);
$this->route->getTarget()->willReturn($target);
$this->route->getType()->willReturn(RouteInterface::TYPE_PATTERN);
$this->route->getType()->willReturn(Route::TYPE_PATTERN);
$this->route->matchesRequestTarget(Argument::cetera())->willReturn(true);
$this->router->register('GET', $target, 'middleware');
@ -310,7 +309,7 @@ class RouterTest extends TestCase
$this->request = $this->request->withRequestTarget($target);
$this->route->getTarget()->willReturn($target);
$this->route->getType()->willReturn(RouteInterface::TYPE_PATTERN);
$this->route->getType()->willReturn(Route::TYPE_PATTERN);
$this->route->matchesRequestTarget(Argument::cetera())->willReturn(true);
$this->route->getPathVariables()->willReturn($variables);
@ -348,7 +347,7 @@ class RouterTest extends TestCase
$this->request = $this->request->withRequestTarget($target);
$this->route->getTarget()->willReturn($target);
$this->route->getType()->willReturn(RouteInterface::TYPE_PATTERN);
$this->route->getType()->willReturn(Route::TYPE_PATTERN);
$this->route->matchesRequestTarget(Argument::cetera())->willReturn(true);
$this->route->getPathVariables()->willReturn($variables);