From 0a1df5a153907370c00436949875c8ec93b2233e Mon Sep 17 00:00:00 2001 From: Fred Emmott Date: Mon, 21 Dec 2015 00:46:16 +0000 Subject: [PATCH] Try to make Hack docks site re-usable First step. Just putting it up so I can pull it into a few projects easily to see how well it works. refs hhvm/user-documentation#206 --- .gitignore | 2 + .hhconfig | 1 + composer.json | 12 +++ composer.lock | 62 +++++++++++++++ src/BaseRouter.php | 120 ++++++++++++------------------ src/MethodNotAllowedException.php | 22 ++++++ src/NotFoundException.php | 22 ++++++ src/RoutingException.php | 30 ++++++++ src/UnknownException.php | 30 ++++++++ src/http_exceptions.php | 52 ------------- 10 files changed, 228 insertions(+), 125 deletions(-) create mode 100644 .gitignore create mode 100644 .hhconfig create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 src/MethodNotAllowedException.php create mode 100644 src/NotFoundException.php create mode 100644 src/RoutingException.php create mode 100644 src/UnknownException.php delete mode 100644 src/http_exceptions.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..067dc81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.swp +vendor/ diff --git a/.hhconfig b/.hhconfig new file mode 100644 index 0000000..0c2153c --- /dev/null +++ b/.hhconfig @@ -0,0 +1 @@ +assume_php=false diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..9934632 --- /dev/null +++ b/composer.json @@ -0,0 +1,12 @@ +{ + "name": "fredemmott/hack-router", + "description": "URI routing for Hack", + "keywords": ["hack", "router", "routing", "hhvm"], + "homepage": "https://github.com/fredemmott/hack-router", + "require": { + "nikic/fast-route": "^0.7.0" + }, + "autoload": { + "classmap": ["src/"], + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..f5cf001 --- /dev/null +++ b/composer.lock @@ -0,0 +1,62 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "fc2621295bfc4fa01e7dea5f3af036c7", + "content-hash": "f3fbe78f56a6ec058c3e2b45c787801c", + "packages": [ + { + "name": "nikic/fast-route", + "version": "v0.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "8164b4a0d8afde4eae5f1bfc39084972ba23ad36" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/8164b4a0d8afde4eae5f1bfc39084972ba23ad36", + "reference": "8164b4a0d8afde4eae5f1bfc39084972ba23ad36", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "FastRoute\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "time": "2015-12-20 19:50:12" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/src/BaseRouter.php b/src/BaseRouter.php index d18db45..8170e89 100644 --- a/src/BaseRouter.php +++ b/src/BaseRouter.php @@ -9,98 +9,72 @@ * */ +namespace Facebook\HackRouter; -use HHVM\UserDocumentation\ArgAssert; -use HHVM\UserDocumentation\BuildPaths; -use HHVM\UserDocumentation\LocalConfig; +abstract class BaseRouter< + TBaseController, + TGETController as TBaseController, + TPOSTController as TBaseController +> { + abstract protected function getGETRoutes(): ImmMap>; + abstract protected function getPOSTRoutes(): ImmMap>; -class Router { - private function getReadOnlyRoutes( - ): KeyedIterable> { - return ImmMap { - '/' => HomePageController::class, - '/search' => SearchController::class, - '/{product:(?:hack|hhvm)}/' - => GuidesListController::class, - '/{product:(?:hack)}/reference/' - => APIListController::class, - '/{product:(?:hack)}/reference/{type:(?:class|function|interface|trait)}/' - => APIListController::class, - '/{product:(?:hack)}/reference/{type:(?:class|function|interface|trait)}/{name}/' - => APIGenericPageController::class, - '/{product:(?:hack)}/reference/{type:(?:class|interface|trait)}/{class}/{method}/' - => APIMethodPageController::class, - '/{product:(?:hack|hhvm)}/{guide}/' - => RedirectToGuideFirstPageController::class, - '/{product:(?:hack|hhvm)}/{guide}/{page}' - => GuidePageController::class, - '/manual/en/{legacy_id}.php' - => LegacyRedirectController::class, - '/robots.txt' - => RobotsTxtController::class, - '/__content' - => WebPageContentController::class, - '/s/{checksum}{file:/.+}' - => StaticResourcesController::class, - '/j/{keyword}' - => JumpController::class, - }; - } - - private function getWriteRoutes( - ): KeyedIterable> { - return ImmMap { - '/__survey/go' - => SurveyRedirectController::class, - '/__survey/nothanks' - => SurveyNoThanksController::class, - }; - } - - private function getDispatcher(): \FastRoute\Dispatcher { - return \FastRoute\cachedDispatcher( - function(\FastRoute\RouteCollector $r): void { - foreach ($this->getReadOnlyRoutes() as $route => $classname) { - $r->addRoute('GET', $route, $classname); - } - - foreach ($this->getWriteRoutes() as $route => $classname) { - $r->addRoute('POST', $route, $classname); - } - }, - /* HH_FIXME[4110] nikic/fastroute#83*/ - shape( - 'cacheFile' => BuildPaths::FASTROUTE_CACHE, - 'cacheDisabled' => !LocalConfig::CACHE_ROUTES, - ), - ); + protected function getCacheFilePath(): ?string { + return null; } public function routeRequest( - \Psr\Http\Message\ServerRequestInterface $request - ): (classname, ImmMap) { - $path = $request->getUri()->getPath(); + string $method, + string $path, + ): (classname, ImmMap) { $route = $this->getDispatcher()->dispatch( - $request->getMethod(), + $method, $path, ); switch ($route[0]) { case \FastRoute\Dispatcher::NOT_FOUND: - throw new HTTPNotFoundException($path); + throw new NotFoundException($method, $path); case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: - throw new HTTPMethodNotAllowedException($path); + throw new MethodNotAllowedException($method, $path); case \FastRoute\Dispatcher::FOUND: return tuple( - ArgAssert::isClassname($route[1], WebController::class), + $route[1], (new Map($route[2])) ->map($encoded ==> urldecode($encoded)) ->toImmMap(), ); } - invariant_violation( - "Unknown fastroute result: %s", - var_export($route[0], true), + throw new UnknownException($route, $method, $path); + } + + private function getDispatcher(): \FastRoute\Dispatcher { + $cache_file = $this->getCacheFilePath(); + if ($cache_file !== null) { + $options = shape( + 'cacheFile' => $cache_file, + 'cacheDisabled' => false, + ); + } else { + $options = shape( + 'cacheDisabled' => true, + ); + } + + return \FastRoute\cachedDispatcher( + $rc ==> $this->addRoutesToCollector($rc), + $options, ); } + + private function addRoutesToCollector( + \FastRoute\RouteCollector $r, + ): void { + foreach ($this->getGETRoutes() as $route => $classname) { + $r->addRoute('GET', $route, $classname); + } + foreach ($this->getPOSTRoutes() as $route => $classname) { + $r->addRoute('POST', $route, $classname); + } + } } diff --git a/src/MethodNotAllowedException.php b/src/MethodNotAllowedException.php new file mode 100644 index 0000000..0cb52e7 --- /dev/null +++ b/src/MethodNotAllowedException.php @@ -0,0 +1,22 @@ +requestMethod; + } + + public function getRequestedPath(): string { + return $this->requestedPath; + } +} diff --git a/src/UnknownException.php b/src/UnknownException.php new file mode 100644 index 0000000..e53b41b --- /dev/null +++ b/src/UnknownException.php @@ -0,0 +1,30 @@ + $fastRouteResult, + string $method, + string $path, + ) { + parent::__construct( + "Unknown FastRoute result: ".var_export($fastRouteResult, true), + $method, + $path, + ); + } + + public function getFastRouteResult(): array { + return $this->fastRouteResult; + } +} diff --git a/src/http_exceptions.php b/src/http_exceptions.php deleted file mode 100644 index 6285439..0000000 --- a/src/http_exceptions.php +++ /dev/null @@ -1,52 +0,0 @@ -; -} -abstract class RoutingException extends HTTPException {} - -final class HTTPNotFoundException extends RoutingException { - public async function getResponse( - ServerRequestInterface $request, - ): Awaitable { - return await (new HTTP404Controller(ImmMap { }, $request))->getResponse(); - } -} - -final class HTTPMethodNotAllowedException extends RoutingException { - public async function getResponse( - ServerRequestInterface $_, - ): Awaitable { - return Response::newEmpty()->withStatus(405); - } -} - -final class RedirectException extends HTTPException { - public function __construct( - private string $destination, - ) { - parent::__construct(); - } - - public async function getResponse( - ServerRequestInterface $_, - ): Awaitable { - return Response::newEmpty() - ->withStatus(301) - ->withAddedHeader('Location', $this->destination); - } -}