diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..c3fe288e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## v2.13.0 + +* Supports for events using the `symfony/event-dispatcher`. diff --git a/README.md b/README.md index 6bfc1d61..999c2abf 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ## Installation ```bash -composer require 'sunrise/http-router:^2.11' +composer require 'sunrise/http-router:^2.13' ``` ## Support for OpenAPI (Swagger) Specification (optional) @@ -570,6 +570,32 @@ use Sunrise\Http\Router\Command\RouteListCommand; new RouteListCommand($router); ``` +### Events + +> Available from version 2.13 + +```bash +composer require symfony/event-dispatcher +``` + +```php +use Sunrise\Http\Router\Event\RouteEvent; +use Symfony\Component\EventDispatcher\EventDispatcher; + +$eventDispatcher = new EventDispatcher(); + +$eventDispatcher->addListener(RouteEvent::NAME, function (RouteEvent $event) { + // gets the matched route: + $event->getRoute(); + // gets the current request: + $event->getRequest(); + // overrides the current request: + $event->setRequest(ServerRequestInterface $request); +}); + +$router->setEventDispatcher($eventDispatcher); +``` + --- ## Test run diff --git a/composer.json b/composer.json index 3d7a8e3b..2760633c 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,8 @@ "sunrise/coding-standard": "1.0.0", "sunrise/http-factory": "1.1.0", "doctrine/annotations": "^1.6", - "symfony/console": "^4.4" + "symfony/console": "^4.4", + "symfony/event-dispatcher": "^4.4" }, "autoload": { "files": [ diff --git a/src/Event/RouteEvent.php b/src/Event/RouteEvent.php new file mode 100644 index 00000000..08bda16c --- /dev/null +++ b/src/Event/RouteEvent.php @@ -0,0 +1,81 @@ + + * @copyright Copyright (c) 2018, Anatoly Fenric + * @license https://github.com/sunrise-php/http-router/blob/master/LICENSE + * @link https://github.com/sunrise-php/http-router + */ + +namespace Sunrise\Http\Router\Event; + +/** + * Import classes + */ +use Psr\Http\Message\ServerRequestInterface; +use Sunrise\Http\Router\RouteInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * RouteEvent + * + * @since 2.13.0 + */ +final class RouteEvent extends Event +{ + + /** + * @var string + */ + public const NAME = 'router.route'; + + /** + * @var RouteInterface + */ + private $route; + + /** + * @var ServerRequestInterface + */ + private $request; + + /** + * Constructor of the class + * + * @param RouteInterface $route + * @param ServerRequestInterface $request + */ + public function __construct(RouteInterface $route, ServerRequestInterface $request) + { + $this->route = $route; + $this->request = $request; + } + + /** + * @return RouteInterface + */ + public function getRoute() : RouteInterface + { + return $this->route; + } + + /** + * @return ServerRequestInterface + */ + public function getRequest() : ServerRequestInterface + { + return $this->request; + } + + /** + * @param ServerRequestInterface $request + * + * @return void + */ + public function setRequest(ServerRequestInterface $request) : void + { + $this->request = $request; + } +} diff --git a/src/Router.php b/src/Router.php index 7f3a2a8b..5527739f 100644 --- a/src/Router.php +++ b/src/Router.php @@ -19,6 +19,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; +use Sunrise\Http\Router\Event\RouteEvent; use Sunrise\Http\Router\Exception\InvalidArgumentException; use Sunrise\Http\Router\Exception\MethodNotAllowedException; use Sunrise\Http\Router\Exception\PageNotFoundException; @@ -26,6 +27,7 @@ use Sunrise\Http\Router\Loader\LoaderInterface; use Sunrise\Http\Router\RequestHandler\CallableRequestHandler; use Sunrise\Http\Router\RequestHandler\QueueableRequestHandler; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * Import functions @@ -90,6 +92,15 @@ class Router implements MiddlewareInterface, RequestHandlerInterface, RequestMet */ private $matchedRoute = null; + /** + * The router's event dispatcher + * + * @var EventDispatcherInterface|null + * + * @since 2.13.0 + */ + private $eventDispatcher = null; + /** * Gets the router host table * @@ -132,6 +143,18 @@ public function getMatchedRoute() : ?RouteInterface return $this->matchedRoute; } + /** + * Gets the router's event dispatcher + * + * @return EventDispatcherInterface|null + * + * @since 2.13.0 + */ + public function getEventDispatcher() : ?EventDispatcherInterface + { + return $this->eventDispatcher; + } + /** * Adds the given patterns to the router * @@ -246,6 +269,20 @@ public function addMiddleware(MiddlewareInterface ...$middlewares) : void } } + /** + * Sets the given event dispatcher to the router + * + * @param EventDispatcherInterface|null $eventDispatcher + * + * @return void + * + * @since 2.13.0 + */ + public function setEventDispatcher(?EventDispatcherInterface $eventDispatcher) : void + { + $this->eventDispatcher = $eventDispatcher; + } + /** * Gets allowed methods * @@ -374,6 +411,13 @@ public function run(ServerRequestInterface $request) : ResponseInterface $routing = new CallableRequestHandler(function (ServerRequestInterface $request) : ResponseInterface { $route = $this->match($request); $this->matchedRoute = $route; + + if (isset($this->eventDispatcher)) { + $event = new RouteEvent($route, $request); + $this->eventDispatcher->dispatch($event, RouteEvent::NAME); + $request = $event->getRequest(); + } + return $route->handle($request); }); @@ -396,6 +440,12 @@ public function handle(ServerRequestInterface $request) : ResponseInterface $route = $this->match($request); $this->matchedRoute = $route; + if (isset($this->eventDispatcher)) { + $event = new RouteEvent($route, $request); + $this->eventDispatcher->dispatch($event, RouteEvent::NAME); + $request = $event->getRequest(); + } + $middlewares = $this->getMiddlewares(); if (empty($middlewares)) { return $route->handle($request); diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 77e8511f..95a439e8 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; +use Sunrise\Http\Router\Event\RouteEvent; use Sunrise\Http\Router\Exception\InvalidArgumentException; use Sunrise\Http\Router\Exception\MethodNotAllowedException; use Sunrise\Http\Router\Exception\PageNotFoundException; @@ -19,6 +20,7 @@ use Sunrise\Http\Router\Router; use Sunrise\Http\Message\ResponseFactory; use Sunrise\Http\ServerRequest\ServerRequestFactory; +use Symfony\Component\EventDispatcher\EventDispatcher; /** * Import functions @@ -819,4 +821,74 @@ public function testMatchWithHosts() : void $router->match((new ServerRequestFactory) ->createServerRequest('GET', 'http://localhost/ping')); } + + /** + * @return void + */ + public function testEventDispatcher() : void + { + $router = new Router(); + $this->assertNull($router->getEventDispatcher()); + + $eventDispatcher = new EventDispatcher(); + $router->setEventDispatcher($eventDispatcher); + $this->assertSame($eventDispatcher, $router->getEventDispatcher()); + + $router->setEventDispatcher(null); + $this->assertNull($router->getEventDispatcher()); + } + + /** + * @return void + */ + public function testRouteEvent() : void + { + $routes = [ + new Fixtures\Route(), + new Fixtures\Route(), + new Fixtures\Route(), + ]; + + $request = (new ServerRequestFactory) + ->createServerRequest( + $routes[1]->getMethods()[1], + $routes[1]->getPath() + ); + + $eventDispatcher = new EventDispatcher(); + $eventDispatcher->addListener(RouteEvent::NAME, function (RouteEvent $event) use ($routes, $request) { + $this->assertSame($routes[1]->getName(), $event->getRoute()->getName()); + $this->assertSame($request, $event->getRequest()); + }); + + $router = new Router(); + $router->addRoute(...$routes); + $router->setEventDispatcher($eventDispatcher); + $router->run($request); + } + + /** + * @return void + */ + public function testRouteEventOverrideRequest() : void + { + $route = new Fixtures\Route(); + + $request = (new ServerRequestFactory) + ->createServerRequest( + $route->getMethods()[0], + $route->getPath() + ); + + $eventDispatcher = new EventDispatcher(); + $eventDispatcher->addListener(RouteEvent::NAME, function (RouteEvent $event) use ($request) { + $event->setRequest($request->withAttribute('foo', 'bar')); + $this->assertNotSame($request, $event->getRequest()); + }); + + $router = new Router(); + $router->addRoute($route); + $router->setEventDispatcher($eventDispatcher); + $router->handle($request); + } }