Skip to content

Commit

Permalink
Added AsyncRouter
Browse files Browse the repository at this point in the history
  • Loading branch information
Aurimas Niekis committed May 31, 2016
1 parent 14f6881 commit 4cb4616
Show file tree
Hide file tree
Showing 4 changed files with 332 additions and 1 deletion.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"require": {
"php": ">=7.0",
"nikic/fast-route": ">=0.7.0,<1.0.0",
"psr/http-message": "~1.0"
"psr/http-message": "~1.0",
"thruster/promise": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "~5.0"
Expand Down
53 changes: 53 additions & 0 deletions src/AsyncRouteHandlerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Thruster\Component\HttpRouter;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Thruster\Component\Promise\ExtendedPromiseInterface;

/**
* Interface AsyncRouteHandlerInterface
*
* @package Thruster\Component\HttpRouter
* @author Aurimas Niekis <[email protected]>
*/
interface AsyncRouteHandlerInterface
{
/**
* @param ServerRequestInterface $request
* @param ResponseInterface $response
* @param callable $callback
*
* @return ExtendedPromiseInterface
*/
public function handleRoute(
ServerRequestInterface $request,
ResponseInterface $response,
callable $callback
) : ExtendedPromiseInterface;

/**
* @param ServerRequestInterface $request
* @param ResponseInterface $response
* @param array $allowedMethods
*
* @return ExtendedPromiseInterface
*/
public function handleRouteMethodNotAllowed(
ServerRequestInterface $request,
ResponseInterface $response,
array $allowedMethods
) : ExtendedPromiseInterface;

/**
* @param ServerRequestInterface $request
* @param ResponseInterface $response
*
* @return ExtendedPromiseInterface
*/
public function handleRouteNotFound(
ServerRequestInterface $request,
ResponseInterface $response
) : ExtendedPromiseInterface;
}
61 changes: 61 additions & 0 deletions src/AsyncRouter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace Thruster\Component\HttpRouter;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Thruster\Component\HttpRouter\Exception\RouteMethodNotAllowedException;
use Thruster\Component\HttpRouter\Exception\RouteNotFoundException;
use Thruster\Component\Promise\ExtendedPromiseInterface;
use Thruster\Component\Promise\FulfilledPromise;

/**
* Class AsyncRouter
*
* @package Thruster\Component\HttpRouter
* @author Aurimas Niekis <[email protected]>
*/
class AsyncRouter extends Router
{
/**
* @var AsyncRouteHandlerInterface
*/
protected $routeHandler;

public function __construct(
RouteProviderInterface $provider,
AsyncRouteHandlerInterface $handler = null,
array $options = []
) {
parent::__construct($provider, null, $options);

$this->routeHandler = $handler;
}

public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
callable $next = null
) : ExtendedPromiseInterface {
try {
list($callback, $request) = $this->internalRequestHandle($request);

$promise = $this->routeHandler->handleRoute($request, $response, $callback);
} catch (RouteMethodNotAllowedException $e) {
$promise = $this->routeHandler->handleRouteMethodNotAllowed($request, $response, $e->getAllowedMethods());
} catch (RouteNotFoundException $e) {
$promise = $this->routeHandler->handleRouteNotFound($request, $response);
}

if (null !== $next) {
return $promise->then(function (ResponseInterface $response) use ($request, $next) {
$response = $next($request, $response);

return new FulfilledPromise($response);
});
}

return $promise;
}

}
216 changes: 216 additions & 0 deletions src/Tests/AsyncRouterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
<?php

namespace Thruster\Component\HttpRouter\Tests;

use FastRoute\Dispatcher;
use Psr\Http\Message\ResponseInterface;
use Thruster\Component\HttpRouter\AsyncRouter;
use Thruster\Component\Promise\FulfilledPromise;

/**
* Class AsyncRouterTest
*
* @package Thruster\Component\HttpRouter\Tests
* @author Aurimas Niekis <[email protected]>
*/
class AsyncRouterTest extends \PHPUnit_Framework_TestCase
{

public function testRouteReturnsPromise()
{
$provider = $this->getMockBuilder('\Thruster\Component\HttpRouter\RouteProviderInterface')
->setMethods(['getRoutes', 'foo'])
->getMockForAbstractClass();

$handler = $this->getMockBuilder('\Thruster\Component\HttpRouter\AsyncRouteHandlerInterface')
->setMethods(['handleRoute', 'handleRouteMethodNotAllowed', 'handleRouteNotFound'])
->getMockForAbstractClass();

$request = $this->getMockForAbstractClass('\Psr\Http\Message\ServerRequestInterface');
$response = $this->getMockForAbstractClass('\Psr\Http\Message\ResponseInterface');
$uri = $this->getMockForAbstractClass('\Psr\Http\Message\UriInterface');
$dispatcher = $this->getMockForAbstractClass('\FastRoute\Dispatcher');

$request->expects($this->once())->method('getMethod')->willReturn('GET');
$request->expects($this->once())->method('getUri')->willReturn($uri);

$uri->expects($this->once())->method('getPath')->willReturn('/');

$dispatcher->expects($this->once())->method('dispatch')->with('GET', '/')->willReturn(
[
Dispatcher::FOUND, 'foo', []
]
);

$request->expects($this->at(2))->method('withAttribute')->with('route_name', 'foo')->willReturnSelf();
$request->expects($this->at(3))->method('withAttribute')->with('route_params', [])->willReturnSelf();

$provider->expects($this->once())->method('getRoutes')->willReturn(
[
'foo' => ['GET', '/', 'foo']
]
);

$router = new AsyncRouter($provider, $handler);
$router->setDispatcher($dispatcher);
$router->buildRoutes();

$promise = new FulfilledPromise($response);

$handler->expects($this->once())
->method('handleRoute')
->with($request, $response, [$provider, 'foo'])
->willReturn($promise);

$receivedPromise = $router($request, $response);

$receivedPromise->done(function (ResponseInterface $givenResponse) use ($response) {
$this->assertEquals($response, $givenResponse);
});
}

public function testRouteReturnsPromiseCallback()
{
$provider = $this->getMockBuilder('\Thruster\Component\HttpRouter\RouteProviderInterface')
->setMethods(['getRoutes', 'foo'])
->getMockForAbstractClass();

$handler = $this->getMockBuilder('\Thruster\Component\HttpRouter\AsyncRouteHandlerInterface')
->setMethods(['handleRoute', 'handleRouteMethodNotAllowed', 'handleRouteNotFound'])
->getMockForAbstractClass();

$request = $this->getMockForAbstractClass('\Psr\Http\Message\ServerRequestInterface');
$response = $this->getMockForAbstractClass('\Psr\Http\Message\ResponseInterface');
$uri = $this->getMockForAbstractClass('\Psr\Http\Message\UriInterface');
$dispatcher = $this->getMockForAbstractClass('\FastRoute\Dispatcher');

$request->expects($this->once())->method('getMethod')->willReturn('GET');
$request->expects($this->once())->method('getUri')->willReturn($uri);

$uri->expects($this->once())->method('getPath')->willReturn('/');

$dispatcher->expects($this->once())->method('dispatch')->with('GET', '/')->willReturn(
[
Dispatcher::FOUND, 'foo', []
]
);

$request->expects($this->at(2))->method('withAttribute')->with('route_name', 'foo')->willReturnSelf();
$request->expects($this->at(3))->method('withAttribute')->with('route_params', [])->willReturnSelf();

$provider->expects($this->once())->method('getRoutes')->willReturn(
[
'foo' => ['GET', '/', 'foo']
]
);

$router = new AsyncRouter($provider, $handler);
$router->setDispatcher($dispatcher);
$router->buildRoutes();

$promise = new FulfilledPromise($response);

$handler->expects($this->once())
->method('handleRoute')
->with($request, $response, [$provider, 'foo'])
->willReturn($promise);

$called = false;
$receivedPromise = $router($request, $response, function($request, $response) use (&$called) {
$called = true;

return $response;
});

$this->assertTrue($called);

$receivedPromise->done(function (ResponseInterface $givenResponse) use ($response) {
$this->assertEquals($response, $givenResponse);
});
}

public function testMiddlewareHandlerMethodNotAllowed()
{
$provider = $this->getMockBuilder('\Thruster\Component\HttpRouter\RouteProviderInterface')
->setMethods(['getRoutes', 'foo'])
->getMockForAbstractClass();

$handler = $this->getMockBuilder('\Thruster\Component\HttpRouter\AsyncRouteHandlerInterface')
->setMethods(['handleRoute', 'handleRouteMethodNotAllowed', 'handleRouteNotFound'])
->getMockForAbstractClass();

$request = $this->getMockForAbstractClass('\Psr\Http\Message\ServerRequestInterface');
$response = $this->getMockForAbstractClass('\Psr\Http\Message\ResponseInterface');
$uri = $this->getMockForAbstractClass('\Psr\Http\Message\UriInterface');
$dispatcher = $this->getMockForAbstractClass('\FastRoute\Dispatcher');

$request->expects($this->once())->method('getMethod')->willReturn('GET');
$request->expects($this->once())->method('getUri')->willReturn($uri);

$uri->expects($this->once())->method('getPath')->willReturn('/');

$dispatcher->expects($this->once())->method('dispatch')->with('GET', '/')->willReturn(
[
Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'POST']
]
);

$provider->expects($this->once())->method('getRoutes')->willReturn(
[
'foo' => ['GET', '/', 'foo']
]
);

$handler->expects($this->once())
->method('handleRouteMethodNotAllowed')
->with($this->anything(), $this->anything(), ['GET', 'POST']);

$router = new AsyncRouter($provider, $handler);
$router->setDispatcher($dispatcher);
$router->buildRoutes();

$router($request, $response);
}

public function testMiddlewareHandlerNotFound()
{
$provider = $this->getMockBuilder('\Thruster\Component\HttpRouter\RouteProviderInterface')
->setMethods(['getRoutes', 'foo'])
->getMockForAbstractClass();

$handler = $this->getMockBuilder('\Thruster\Component\HttpRouter\AsyncRouteHandlerInterface')
->setMethods(['handleRoute', 'handleRouteMethodNotAllowed', 'handleRouteNotFound'])
->getMockForAbstractClass();

$request = $this->getMockForAbstractClass('\Psr\Http\Message\ServerRequestInterface');
$response = $this->getMockForAbstractClass('\Psr\Http\Message\ResponseInterface');
$uri = $this->getMockForAbstractClass('\Psr\Http\Message\UriInterface');
$dispatcher = $this->getMockForAbstractClass('\FastRoute\Dispatcher');

$request->expects($this->once())->method('getMethod')->willReturn('GET');
$request->expects($this->once())->method('getUri')->willReturn($uri);

$uri->expects($this->once())->method('getPath')->willReturn('/');

$dispatcher->expects($this->once())->method('dispatch')->with('GET', '/')->willReturn(
[
Dispatcher::NOT_FOUND, 'foo', []
]
);

$provider->expects($this->once())->method('getRoutes')->willReturn(
[
'foo' => ['GET', '/', 'foo']
]
);

$handler->expects($this->once())
->method('handleRouteNotFound');

$router = new AsyncRouter($provider, $handler);
$router->setDispatcher($dispatcher);
$router->buildRoutes();

$router($request, $response);
}
}

0 comments on commit 4cb4616

Please sign in to comment.