From 4deb7e66805d679a95fed1eb0cbde9c2d921901b Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Fri, 9 Jul 2021 17:09:56 -0700 Subject: [PATCH 01/25] Composer --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index fe9d08c..3084a03 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,6 @@ "description": "An API framework", "require": { "php": "^7.4 || ^8.0", - "firehed/common": "^1.0.5", "firehed/input": "^2.1.5", "psr/container": "^1.0 || ^2.0", "psr/http-message": "^1.0", From 77162f9c102251578fa3ce993b1e314444743518 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Fri, 9 Jul 2021 17:11:51 -0700 Subject: [PATCH 02/25] Convert core interface --- src/Enums/HTTPMethod.php | 17 +---------------- src/Interfaces/EndpointInterface.php | 4 ++-- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/Enums/HTTPMethod.php b/src/Enums/HTTPMethod.php index 6fa438d..e03ffc1 100644 --- a/src/Enums/HTTPMethod.php +++ b/src/Enums/HTTPMethod.php @@ -4,24 +4,14 @@ namespace Firehed\API\Enums; -use Firehed\Common\Enum; - /** * Direct use of this class (via these static methods) is deprecated. Instead, * implementations should rely on the Request traits. * * This will be converted to a native Enum in PHP 8.1. - * - * @method static HTTPMethod DELETE() - * @method static HTTPMethod GET() - * @method static HTTPMethod OPTIONS() - * @method static HTTPMethod PATCH() - * @method static HTTPMethod POST() - * @method static HTTPMethod PUT() */ -class HTTPMethod extends Enum +interface HTTPMethod { - // Other methods exist, but these are the only relevant ones for RESTful // APIs const GET = 'GET'; @@ -30,9 +20,4 @@ class HTTPMethod extends Enum const PUT = 'PUT'; const DELETE = 'DELETE'; const OPTIONS = 'OPTIONS'; - - public function __toString() - { - return $this->getValue(); - } } diff --git a/src/Interfaces/EndpointInterface.php b/src/Interfaces/EndpointInterface.php index 6c34716..41663a1 100644 --- a/src/Interfaces/EndpointInterface.php +++ b/src/Interfaces/EndpointInterface.php @@ -64,7 +64,7 @@ public function getUri(): string; * Indiate the HTTP request method that must be used for inbound requests * to be routed to this endpoint. * - * @return HTTPMethod + * @return HTTPMethod::* */ - public function getMethod(): HTTPMethod; + public function getMethod(): string; } From bc8dd76549a72bd4b68c4068d608700fa291b652 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Fri, 9 Jul 2021 17:12:49 -0700 Subject: [PATCH 03/25] Update implementing traits --- src/Traits/Request/Delete.php | 5 ++--- src/Traits/Request/Get.php | 5 ++--- src/Traits/Request/Options.php | 5 ++--- src/Traits/Request/Patch.php | 4 ++-- src/Traits/Request/Post.php | 5 ++--- src/Traits/Request/Put.php | 4 ++-- 6 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/Traits/Request/Delete.php b/src/Traits/Request/Delete.php index 3e6e562..a8d5b78 100644 --- a/src/Traits/Request/Delete.php +++ b/src/Traits/Request/Delete.php @@ -7,9 +7,8 @@ trait Delete { - - public function getMethod(): HTTPMethod + public function getMethod(): string { - return HTTPMethod::DELETE(); + return HTTPMethod::DELETE; } } diff --git a/src/Traits/Request/Get.php b/src/Traits/Request/Get.php index 08a5d62..bcbd5b1 100644 --- a/src/Traits/Request/Get.php +++ b/src/Traits/Request/Get.php @@ -7,9 +7,8 @@ trait Get { - - public function getMethod(): HTTPMethod + public function getMethod(): string { - return HTTPMethod::GET(); + return HTTPMethod::GET; } } diff --git a/src/Traits/Request/Options.php b/src/Traits/Request/Options.php index 580f44d..d419c9a 100644 --- a/src/Traits/Request/Options.php +++ b/src/Traits/Request/Options.php @@ -7,9 +7,8 @@ trait Options { - - public function getMethod(): HTTPMethod + public function getMethod(): string { - return HTTPMethod::OPTIONS(); + return HTTPMethod::OPTIONS; } } diff --git a/src/Traits/Request/Patch.php b/src/Traits/Request/Patch.php index c0db3fc..070d4b2 100644 --- a/src/Traits/Request/Patch.php +++ b/src/Traits/Request/Patch.php @@ -7,8 +7,8 @@ trait Patch { - public function getMethod(): HTTPMethod + public function getMethod(): string { - return HTTPMethod::PATCH(); + return HTTPMethod::PATCH; } } diff --git a/src/Traits/Request/Post.php b/src/Traits/Request/Post.php index ddc41c1..96a3605 100644 --- a/src/Traits/Request/Post.php +++ b/src/Traits/Request/Post.php @@ -7,9 +7,8 @@ trait Post { - - public function getMethod(): HTTPMethod + public function getMethod() { - return HTTPMethod::POST(); + return HTTPMethod::POST; } } diff --git a/src/Traits/Request/Put.php b/src/Traits/Request/Put.php index c4fd40b..9e1a5fb 100644 --- a/src/Traits/Request/Put.php +++ b/src/Traits/Request/Put.php @@ -7,8 +7,8 @@ trait Put { - public function getMethod(): HTTPMethod + public function getMethod(): string { - return HTTPMethod::PUT(); + return HTTPMethod::PUT; } } From accdb9bb816ab9b4ff6d081a8f22396bceaff228 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Fri, 9 Jul 2021 17:13:44 -0700 Subject: [PATCH 04/25] Update fixture --- tests/EndpointFixture.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/EndpointFixture.php b/tests/EndpointFixture.php index c348673..385789d 100644 --- a/tests/EndpointFixture.php +++ b/tests/EndpointFixture.php @@ -15,6 +15,7 @@ class EndpointFixture implements Interfaces\EndpointInterface { + use Traits\Request\Get; const STATUS_ERROR = 999; @@ -54,11 +55,6 @@ public function validate($value): bool ]; } - public function getMethod(): Enums\HTTPMethod - { - return Enums\HTTPMethod::GET(); - } - public function execute(SafeInput $input): Response { // Use PHPUnit mocks outside of the TestCase... the DSL isn't quite as From 5a34aa8ca53e1156709a0c65ae09dd98847ce272 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Fri, 9 Jul 2021 17:14:47 -0700 Subject: [PATCH 05/25] Update trait tests --- tests/Traits/Request/DeleteTest.php | 2 +- tests/Traits/Request/GetTest.php | 2 +- tests/Traits/Request/OptionsTest.php | 2 +- tests/Traits/Request/PatchTest.php | 2 +- tests/Traits/Request/PostTest.php | 2 +- tests/Traits/Request/PutTest.php | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Traits/Request/DeleteTest.php b/tests/Traits/Request/DeleteTest.php index 6713ccf..c0f6ca5 100644 --- a/tests/Traits/Request/DeleteTest.php +++ b/tests/Traits/Request/DeleteTest.php @@ -14,7 +14,7 @@ public function testGetMethod(): void use Delete; }; $this->assertEquals( - \Firehed\API\Enums\HTTPMethod::DELETE(), + \Firehed\API\Enums\HTTPMethod::DELETE, $obj->getMethod(), 'getMethod did not return HTTP DELETE' ); diff --git a/tests/Traits/Request/GetTest.php b/tests/Traits/Request/GetTest.php index fd5754b..07f4a00 100644 --- a/tests/Traits/Request/GetTest.php +++ b/tests/Traits/Request/GetTest.php @@ -14,7 +14,7 @@ public function testGetMethod(): void use Get; }; $this->assertEquals( - \Firehed\API\Enums\HTTPMethod::GET(), + \Firehed\API\Enums\HTTPMethod::GET, $obj->getMethod(), 'getMethod did not return HTTP GET' ); diff --git a/tests/Traits/Request/OptionsTest.php b/tests/Traits/Request/OptionsTest.php index 96d3af9..a39f9e5 100644 --- a/tests/Traits/Request/OptionsTest.php +++ b/tests/Traits/Request/OptionsTest.php @@ -14,7 +14,7 @@ public function testGetMethod(): void use Options; }; $this->assertEquals( - \Firehed\API\Enums\HTTPMethod::OPTIONS(), + \Firehed\API\Enums\HTTPMethod::OPTIONS, $obj->getMethod(), 'getMethod did not return HTTP OPTIONS' ); diff --git a/tests/Traits/Request/PatchTest.php b/tests/Traits/Request/PatchTest.php index 27580d0..b64ccf1 100644 --- a/tests/Traits/Request/PatchTest.php +++ b/tests/Traits/Request/PatchTest.php @@ -14,7 +14,7 @@ public function testGetMethod(): void use Patch; }; $this->assertEquals( - \Firehed\API\Enums\HTTPMethod::PATCH(), + \Firehed\API\Enums\HTTPMethod::PATCH, $obj->getMethod(), 'getMethod did not return HTTP PATCH' ); diff --git a/tests/Traits/Request/PostTest.php b/tests/Traits/Request/PostTest.php index 62d376e..261b2e3 100644 --- a/tests/Traits/Request/PostTest.php +++ b/tests/Traits/Request/PostTest.php @@ -14,7 +14,7 @@ public function testGetMethod(): void use Post; }; $this->assertEquals( - \Firehed\API\Enums\HTTPMethod::POST(), + \Firehed\API\Enums\HTTPMethod::POST, $obj->getMethod(), 'getMethod did not return HTTP POST' ); diff --git a/tests/Traits/Request/PutTest.php b/tests/Traits/Request/PutTest.php index 5018119..a67bde7 100644 --- a/tests/Traits/Request/PutTest.php +++ b/tests/Traits/Request/PutTest.php @@ -14,7 +14,7 @@ public function testGetMethod(): void use Put; }; $this->assertEquals( - \Firehed\API\Enums\HTTPMethod::PUT(), + \Firehed\API\Enums\HTTPMethod::PUT, $obj->getMethod(), 'getMethod did not return HTTP PUT' ); From 4af28ced3ddc6f280d9e55524cfc6364324c0b4e Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Fri, 9 Jul 2021 17:19:18 -0700 Subject: [PATCH 06/25] Use reflection to validate --- src/Traits/EndpointTestCases.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Traits/EndpointTestCases.php b/src/Traits/EndpointTestCases.php index 8e8f8d2..00297d1 100644 --- a/src/Traits/EndpointTestCases.php +++ b/src/Traits/EndpointTestCases.php @@ -9,6 +9,7 @@ use Firehed\Input\Interfaces\ValidationInterface; use Firehed\Input\SafeInputTestTrait; use Firehed\Input\ValidationTestTrait; +use ReflectionClass; /** * Default test cases to be run against any object implementing @@ -123,10 +124,10 @@ protected function badUris(): array public function testGetMethod(): void { $method = $this->getEndpoint()->getMethod(); - $this->assertInstanceOf( - 'Firehed\API\Enums\HTTPMethod', - $method, - 'getMethod did not return an HTTPMethod enum' - ); + // 8.1: Enum logic + + $rc = new ReflectionClass(HTTPMethod::class); + $constants = $rc->getConstants(); + $this->assertContains($constants, $method, 'Invalid HTTP method'); } } From e5c5e5aec67c2df4509103714127c3b276957aaf Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Fri, 9 Jul 2021 17:20:55 -0700 Subject: [PATCH 07/25] Fix test --- src/Traits/EndpointTestCases.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Traits/EndpointTestCases.php b/src/Traits/EndpointTestCases.php index 00297d1..1e19a1d 100644 --- a/src/Traits/EndpointTestCases.php +++ b/src/Traits/EndpointTestCases.php @@ -5,6 +5,7 @@ namespace Firehed\API\Traits; use Firehed\API\Interfaces\EndpointInterface; +use Firehed\API\Enums\HTTPMethod; use Firehed\Input\Containers; use Firehed\Input\Interfaces\ValidationInterface; use Firehed\Input\SafeInputTestTrait; @@ -128,6 +129,6 @@ public function testGetMethod(): void $rc = new ReflectionClass(HTTPMethod::class); $constants = $rc->getConstants(); - $this->assertContains($constants, $method, 'Invalid HTTP method'); + $this->assertContains($method, $constants, 'Invalid HTTP method'); } } From b403bf16b33a9f3e0cfa36087d96f4e22b6d8870 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Fri, 9 Jul 2021 17:49:23 -0700 Subject: [PATCH 08/25] Start to remove classmapper from dispatcher --- src/Dispatcher.php | 71 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 7 deletions(-) diff --git a/src/Dispatcher.php b/src/Dispatcher.php index bcb6c95..14d2ac4 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -10,6 +10,7 @@ use Firehed\API\Interfaces\HandlesOwnErrorsInterface; use Firehed\Common\ClassMapper; use Firehed\Input\Containers\ParsedInput; +use InvalidArgumentException; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -19,6 +20,10 @@ use OutOfBoundsException; use UnexpectedValueException; +use function strtoupper; +use function preg_match; +use function array_key_exists; + class Dispatcher implements RequestHandlerInterface { const ENDPOINT_LIST = '__endpoint_list__.php'; @@ -146,17 +151,44 @@ public function dispatch(ServerRequestInterface $request): ResponseInterface return $mwDispatcher->handle($request); } + /** + * @return array{ + * ?class-string, + * ?array, + * } + */ + private function routeRequest(ServerRequestInterface $request): array + { + $endpoints = self::loadConfigFile($this->endpointList); + $method = strtoupper($request->getMethod()); + if (!array_key_exists($method, $endpoints)) { + return [null, null]; + } + $endpointsForMethod = $endpoints[$method]; + $requestPath = $request->getUri()->getPath(); + foreach ($endpointsForMethod as $uri => $fqcn) { + $pattern = '#^' . $uri . '#'; + if (preg_match($pattern, $requestPath, $match)) { + // TODO: filter numeric keys + return [$fqcn, $match]; + } + } + // nope + // print_r($endpointsForMethod); + return [null, null]; + } + private function doDispatch(ServerRequestInterface $request): ResponseInterface { /** @var ?Interfaces\EndpointInterface */ $endpoint = null; try { - [$fqcn, $uriData] = (new ClassMapper($this->endpointList)) - ->filter(strtoupper($request->getMethod())) - ->search($request->getUri()->getPath()); + // var_dump($this->endpointList); + [$fqcn, $uriData] = $this->routeRequest($request); if (!$fqcn) { throw new OutOfBoundsException('Endpoint not found', 404); } + assert($uriData !== null); if ($this->container && $this->container->has($fqcn)) { $endpoint = $this->container->get($fqcn); } else { @@ -223,12 +255,15 @@ private function parseInput(ServerRequestInterface $request): ParsedInput $mediaType = array_shift($directives); // Future: trim and format directives; e.g. ' charset=utf-8' => // ['charset' => 'utf-8'] - list($parser_class) = (new ClassMapper($this->parserList)) - ->search($mediaType); - if (!$parser_class) { + // list($parser_class) = (new ClassMapper($this->parserList)) + // ->search($mediaType); + // FIXME: string or array + $parsers = self::loadConfigFile($this->parserList); + if (!array_key_exists($mediaType, $parsers)) { throw new OutOfBoundsException('Unsupported Content-type', 415); } - $parser = new $parser_class; + $parserClass = $parsers[$mediaType]; + $parser = new $parserClass; $data = $parser->parse((string)$request->getBody()); } return new ParsedInput($data); @@ -242,4 +277,26 @@ private function getQueryStringData(ServerRequestInterface $request): ParsedInpu parse_str($query, $data); return new ParsedInput($data); } + + /** + * @param string|string[] $file + * @return string[] + */ + private static function loadConfigFile($file): array + { + if (is_array($file)) { + return $file; + } elseif (is_string($file)) { + if (!file_exists($file)) { + throw new InvalidArgumentException('Invalid file'); + // throw + } + // if php include + // elseif json + // Avoid weird scope issues + return (fn () => include $file)(); + } else { + throw new InvalidArgumentException('Invalid format'); + } + } } From a45d8c8a48d790e9f28a084a445390fc8dbb7611 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Fri, 9 Jul 2021 17:59:16 -0700 Subject: [PATCH 09/25] Finish inlining file building logic --- src/Dispatcher.php | 19 ++++++++++--------- tests/DispatcherTest.php | 9 ++++++--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Dispatcher.php b/src/Dispatcher.php index 14d2ac4..ebfaaa0 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -169,12 +169,17 @@ private function routeRequest(ServerRequestInterface $request): array foreach ($endpointsForMethod as $uri => $fqcn) { $pattern = '#^' . $uri . '#'; if (preg_match($pattern, $requestPath, $match)) { - // TODO: filter numeric keys + // Filter out numeric keys from match output - we only want to + // retain named captures + foreach ($match as $key => $_) { + if (is_int($key)) { + unset($match[$key]); + } + } return [$fqcn, $match]; } } - // nope - // print_r($endpointsForMethod); + // No match return [null, null]; } @@ -279,8 +284,8 @@ private function getQueryStringData(ServerRequestInterface $request): ParsedInpu } /** - * @param string|string[] $file - * @return string[] + * @param string|string[]|string[][] $file + * @return string[]|string[][] */ private static function loadConfigFile($file): array { @@ -289,11 +294,7 @@ private static function loadConfigFile($file): array } elseif (is_string($file)) { if (!file_exists($file)) { throw new InvalidArgumentException('Invalid file'); - // throw } - // if php include - // elseif json - // Avoid weird scope issues return (fn () => include $file)(); } else { throw new InvalidArgumentException('Invalid format'); diff --git a/tests/DispatcherTest.php b/tests/DispatcherTest.php index ecd8733..3e54465 100644 --- a/tests/DispatcherTest.php +++ b/tests/DispatcherTest.php @@ -11,6 +11,7 @@ use Firehed\API\Interfaces\HandlesOwnErrorsInterface; use Firehed\API\Errors\HandlerInterface; use Firehed\Input\Exceptions\InputException; +use Firehed\Input\Parsers; use InvalidArgumentException; use Nyholm\Psr7\ServerRequest; use OutOfBoundsException; @@ -735,10 +736,12 @@ private function getEndpointListForFixture(): array ]; } - private function getDefaultParserList(): string + private function getDefaultParserList(): array { - // This could also be dynamically built - return dirname(__DIR__).'/vendor/firehed/input/src/Parsers/__parser_list__.json'; + return [ + 'application/json' => Parsers\JSON::class, + 'application/x-www-form-urlencoded' => Parsers\URLEncoded::class, + ]; } /** From 8d82ee3bce02aabf0ed7163443816de26db37271 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 12 Jul 2021 09:12:59 -0700 Subject: [PATCH 10/25] Require composer (for classmapgenerator) --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 3084a03..f1a11da 100644 --- a/composer.json +++ b/composer.json @@ -3,6 +3,7 @@ "description": "An API framework", "require": { "php": "^7.4 || ^8.0", + "composer/composer": "^1.0 || ^2.0", "firehed/input": "^2.1.5", "psr/container": "^1.0 || ^2.0", "psr/http-message": "^1.0", From d6f8a1c417749e44b043525e1cc7cae4d194058f Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 12 Jul 2021 10:37:10 -0700 Subject: [PATCH 11/25] Migrate to Composer's ClassMapGenerator --- composer.json | 2 +- src/Console/CompileAll.php | 81 +++++++++++++++++++++++++++++--------- 2 files changed, 64 insertions(+), 19 deletions(-) diff --git a/composer.json b/composer.json index f1a11da..4c019c0 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "An API framework", "require": { "php": "^7.4 || ^8.0", - "composer/composer": "^1.0 || ^2.0", + "composer/composer": "^2.1", "firehed/input": "^2.1.5", "psr/container": "^1.0 || ^2.0", "psr/http-message": "^1.0", diff --git a/src/Console/CompileAll.php b/src/Console/CompileAll.php index a4a45c0..e0628b7 100644 --- a/src/Console/CompileAll.php +++ b/src/Console/CompileAll.php @@ -3,17 +3,26 @@ namespace Firehed\API\Console; +use Composer\Autoload\ClassMapGenerator; use Firehed\API\Config; use Firehed\API\Dispatcher; use Firehed\API\Interfaces\EndpointInterface; use Firehed\Input\Interfaces\ParserInterface; -use Firehed\Common\ClassMapGenerator; +use Firehed\Input\Parsers; +use ReflectionClass; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Logger\ConsoleLogger; use Symfony\Component\Console\Output\OutputInterface; +use function array_filter; +use function array_keys; +use function array_reduce; +use function array_values; +use function get_class; +use function gmdate; + class CompileAll extends Command { /** @var Config */ @@ -37,15 +46,27 @@ protected function execute(InputInterface $input, OutputInterface $output): int $logger->debug('Current directory: {cwd}', ['cwd' => getcwd()]); $logger->debug('Building classmap'); - // Build out the endpoint map - (new ClassMapGenerator()) - ->setPath(getcwd().'/'.$this->config->get('source')) - ->setInterface(EndpointInterface::class) - ->addCategory('getMethod') - ->setMethod('getURI') - ->setNamespace($this->config->get(Config::KEY_NAMESPACE)) - ->setOutputFile(Dispatcher::ENDPOINT_LIST) - ->generate(); + + $endpoints = $this->getFilteredClasses($this->config->get(Config::KEY_SOURCE), function (ReflectionClass $rc): bool { + if (!$rc->isInstantiable()) { + return false; + } + if (!$rc->implementsInterface(EndpointInterface::class)) { + return false; + } + return true; + }); + $endpointMap = array_reduce($endpoints, function (array $carry, ReflectionClass $rc) { + $instance = $rc->newInstanceWithoutConstructor(); + assert($instance instanceof EndpointInterface); // Filtered above + $carry[$instance->getMethod()][$instance->getUri()] = $rc->getName(); + return $carry; + }, []); + $endpointMap['@gener'.'ated'] = gmdate('c'); + file_put_contents( + Dispatcher::ENDPOINT_LIST, + sprintf("writeln(sprintf( 'Wrote endpoint map to %s', @@ -53,18 +74,42 @@ protected function execute(InputInterface $input, OutputInterface $output): int )); $logger->debug('Building parser map'); - // Also do the parser map - (new ClassMapGenerator()) - ->setPath(getcwd().'/'.'vendor/firehed/input/src/Parsers') - ->setInterface(ParserInterface::class) - ->setMethod('getSupportedMimeTypes') - ->setNamespace('Firehed\Input\Parsers') - ->setOutputFile(Dispatcher::PARSER_LIST) - ->generate(); + // For now, simply hardcode the values. + $parsers = [ + new Parsers\JSON(), + new Parsers\URLEncoded(), + ]; + $parserMap = array_reduce($parsers, function (array $carry, ParserInterface $parser) { + foreach ($parser->getSupportedMimeTypes() as $mime) { + $carry[$mime] = get_class($parser); + } + return $carry; + }, []); + $parserMap['@gener'.'ated'] = gmdate('c'); + file_put_contents( + Dispatcher::PARSER_LIST, + sprintf("writeln(sprintf( 'Wrote parser map to %s', Dispatcher::PARSER_LIST )); return 0; } + + /** + * @param callable(ReflectionClass): bool $filter + * @return ReflectionClass[] + */ + private function getFilteredClasses(string $directory, callable $filter): array + { + /** @var array */ + $cm = ClassMapGenerator::createMap($directory); + $classes = array_keys($cm); + $rcs = array_map(fn ($fqcn) => new ReflectionClass($fqcn), $classes); + $result = array_filter($rcs, $filter); + // Compact the result set + return array_values($result); + } } From 877eb722a37ebd829923d83840d98f4f7c9367d4 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 12 Jul 2021 10:49:10 -0700 Subject: [PATCH 12/25] Drop parser configuration --- src/Dispatcher.php | 52 ++++++++++++++++------------------------------ 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/src/Dispatcher.php b/src/Dispatcher.php index ebfaaa0..ead750f 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -10,6 +10,8 @@ use Firehed\API\Interfaces\HandlesOwnErrorsInterface; use Firehed\Common\ClassMapper; use Firehed\Input\Containers\ParsedInput; +use Firehed\Input\Interfaces\ParserInterface; +use Firehed\Input\Parsers; use InvalidArgumentException; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; @@ -27,7 +29,6 @@ class Dispatcher implements RequestHandlerInterface { const ENDPOINT_LIST = '__endpoint_list__.php'; - const PARSER_LIST = '__parser_list__.php'; /** @var ?ContainerInterface */ private $container; @@ -41,8 +42,11 @@ class Dispatcher implements RequestHandlerInterface /** @var string | string[][] */ private $endpointList = self::ENDPOINT_LIST; - /** @var string | string[] */ - private $parserList = self::PARSER_LIST; + /** @var array> */ + private $parsers = [ + 'application/json' => Parsers\JSON::class, + 'application/x-www-form-urlencoded' => Parsers\URLEncoded::class, + ]; /** @var MiddlewareInterface[] */ private $psrMiddleware = []; @@ -89,22 +93,6 @@ public function setContainer(ContainerInterface $container): self return $this; } - /** - * Set the parser list. Can be an array consumable by ClassMapper or - * a string representing a file parsable by same. The list must map - * MIME-types to Firehed\Input\ParserInterface class names. - * - * @internal Overrides the standard parser list. Used primarily for unit - * testing. - * @param string | string[] $parserList The parser list or its path - * @return self - */ - public function setParserList($parserList): self - { - $this->parserList = $parserList; - return $this; - } - /** * Set the endpoint list. Can be an array consumable by ClassMapper or * a string representing a file parsable by same. The list must be @@ -159,7 +147,7 @@ public function dispatch(ServerRequestInterface $request): ResponseInterface */ private function routeRequest(ServerRequestInterface $request): array { - $endpoints = self::loadConfigFile($this->endpointList); + $endpoints = self::loadEndpoints($this->endpointList); $method = strtoupper($request->getMethod()); if (!array_key_exists($method, $endpoints)) { return [null, null]; @@ -260,14 +248,10 @@ private function parseInput(ServerRequestInterface $request): ParsedInput $mediaType = array_shift($directives); // Future: trim and format directives; e.g. ' charset=utf-8' => // ['charset' => 'utf-8'] - // list($parser_class) = (new ClassMapper($this->parserList)) - // ->search($mediaType); - // FIXME: string or array - $parsers = self::loadConfigFile($this->parserList); - if (!array_key_exists($mediaType, $parsers)) { + if (!array_key_exists($mediaType, $this->parsers)) { throw new OutOfBoundsException('Unsupported Content-type', 415); } - $parserClass = $parsers[$mediaType]; + $parserClass = $this->parsers[$mediaType]; $parser = new $parserClass; $data = $parser->parse((string)$request->getBody()); } @@ -284,18 +268,18 @@ private function getQueryStringData(ServerRequestInterface $request): ParsedInpu } /** - * @param string|string[]|string[][] $file - * @return string[]|string[][] + * @param string|string[][] $data + * @return string[][] */ - private static function loadConfigFile($file): array + private static function loadEndpoints($data): array { - if (is_array($file)) { - return $file; - } elseif (is_string($file)) { - if (!file_exists($file)) { + if (is_array($data)) { + return $data; + } elseif (is_string($data)) { + if (!file_exists($data)) { throw new InvalidArgumentException('Invalid file'); } - return (fn () => include $file)(); + return (fn () => include $data)(); } else { throw new InvalidArgumentException('Invalid format'); } From 407ba79de67638652b557c27bf489a6839d78896 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 12 Jul 2021 11:00:04 -0700 Subject: [PATCH 13/25] Improve type information in dispatcher --- src/Dispatcher.php | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Dispatcher.php b/src/Dispatcher.php index ead750f..ac89d3f 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -26,6 +26,9 @@ use function preg_match; use function array_key_exists; +/** + * @phpstan-type EndpointMap array>> + */ class Dispatcher implements RequestHandlerInterface { const ENDPOINT_LIST = '__endpoint_list__.php'; @@ -39,7 +42,7 @@ class Dispatcher implements RequestHandlerInterface /** @var bool */ private $containerHasErrorHandler = false; - /** @var string | string[][] */ + /** @var string | EndpointMap */ private $endpointList = self::ENDPOINT_LIST; /** @var array> */ @@ -101,7 +104,7 @@ public function setContainer(ContainerInterface $container): self * * @internal Overrides the standard endpoint list. Used primarily for unit * testing. - * @param string | string[][] $endpointList The endpoint list or its path + * @param string|EndpointMap $endpointList The endpoint list or its path * @return self */ public function setEndpointList($endpointList): self @@ -156,15 +159,16 @@ private function routeRequest(ServerRequestInterface $request): array $requestPath = $request->getUri()->getPath(); foreach ($endpointsForMethod as $uri => $fqcn) { $pattern = '#^' . $uri . '#'; - if (preg_match($pattern, $requestPath, $match)) { + if (preg_match($pattern, $requestPath, $matches)) { // Filter out numeric keys from match output - we only want to // retain named captures - foreach ($match as $key => $_) { + foreach ($matches as $key => $value) { if (is_int($key)) { - unset($match[$key]); + unset($matches[$key]); } } - return [$fqcn, $match]; + /** @var array $matches */ + return [$fqcn, $matches]; } } // No match @@ -176,7 +180,6 @@ private function doDispatch(ServerRequestInterface $request): ResponseInterface /** @var ?Interfaces\EndpointInterface */ $endpoint = null; try { - // var_dump($this->endpointList); [$fqcn, $uriData] = $this->routeRequest($request); if (!$fqcn) { throw new OutOfBoundsException('Endpoint not found', 404); @@ -268,8 +271,8 @@ private function getQueryStringData(ServerRequestInterface $request): ParsedInpu } /** - * @param string|string[][] $data - * @return string[][] + * @param string|EndpointMap $data + * @return EndpointMap */ private static function loadEndpoints($data): array { From c91514314d9a6d0eaadd1b2e8e22913938816837 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 12 Jul 2021 11:00:35 -0700 Subject: [PATCH 14/25] Missing type --- src/Traits/Request/Post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/Request/Post.php b/src/Traits/Request/Post.php index 96a3605..803b0a0 100644 --- a/src/Traits/Request/Post.php +++ b/src/Traits/Request/Post.php @@ -7,7 +7,7 @@ trait Post { - public function getMethod() + public function getMethod(): string { return HTTPMethod::POST; } From c856b8d774bf780bf98a7fed6a1cb1db42639cac Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 12 Jul 2021 11:08:22 -0700 Subject: [PATCH 15/25] Futher improve endpoint list type info --- src/Dispatcher.php | 2 +- tests/DispatcherTest.php | 45 ++++++++-------------------------------- 2 files changed, 10 insertions(+), 37 deletions(-) diff --git a/src/Dispatcher.php b/src/Dispatcher.php index ac89d3f..d18de69 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -27,7 +27,7 @@ use function array_key_exists; /** - * @phpstan-type EndpointMap array>> + * @phpstan-type EndpointMap array>> */ class Dispatcher implements RequestHandlerInterface { diff --git a/tests/DispatcherTest.php b/tests/DispatcherTest.php index 3e54465..32ad99c 100644 --- a/tests/DispatcherTest.php +++ b/tests/DispatcherTest.php @@ -7,11 +7,11 @@ use Exception; use Firehed\API\Authentication; use Firehed\API\Authorization; +use Firehed\API\Enums\HTTPMethod; use Firehed\API\Interfaces\EndpointInterface; use Firehed\API\Interfaces\HandlesOwnErrorsInterface; use Firehed\API\Errors\HandlerInterface; use Firehed\Input\Exceptions\InputException; -use Firehed\Input\Parsers; use InvalidArgumentException; use Nyholm\Psr7\ServerRequest; use OutOfBoundsException; @@ -81,17 +81,6 @@ public function testSetEndpointListReturnsSelf(): void ); } - /** @covers ::setParserList */ - public function testSetParserListReturnsSelf(): void - { - $d = new Dispatcher(); - $this->assertSame( - $d, - $d->setParserList('list'), - 'setParserList did not return $this' - ); - } - // ----(Success case)------------------------------------------------------- /** @@ -112,7 +101,6 @@ public function testDataReachesEndpoint(): void $response = (new Dispatcher()) ->setEndpointList($this->getEndpointListForFixture()) - ->setParserList($this->getDefaultParserList()) ->dispatch($req); $this->checkResponse($response, 200); $data = json_decode((string)$response->getBody(), true); @@ -143,7 +131,6 @@ public function testQueryStringDataReachesEndpoint(): void $response = (new Dispatcher()) ->setEndpointList($this->getEndpointListForFixture()) - ->setParserList($this->getDefaultParserList()) ->dispatch($req); $this->checkResponse($response, 200); $data = json_decode((string)$response->getBody(), true); @@ -215,13 +202,12 @@ public function testPsr15(): void ->method('execute') ->willReturn($response); - $routes = ['GET' => ['/c' => 'EP']]; + $routes = [HTTPMethod::GET => ['/c' => 'EP']]; $res = $dispatcher ->addMiddleware($mw1) ->addMiddleware($mw2) ->setContainer($this->getMockContainer(['EP' => $endpoint])) ->setEndpointList($routes) - ->setParserList($this->getDefaultParserList()) ->dispatch($request); $this->assertSame($modifiedResponse, $res, 'Dispatcher returned different response'); @@ -252,7 +238,7 @@ function ($caught) use ($execute, $error) { $req = $this->getMockRequestWithUriPath('/cb', 'GET'); $list = [ - 'GET' => [ + HTTPMethod::GET => [ '/cb' => 'CBClass', ], ]; @@ -260,7 +246,6 @@ function ($caught) use ($execute, $error) { $ret = (new Dispatcher()) ->setContainer($this->getMockContainer(['CBClass' => $endpoint])) ->setEndpointList($list) - ->setParserList($this->getDefaultParserList()) ->dispatch($req); $this->fail( "The exception thrown from the error handler's failure should ". @@ -299,7 +284,6 @@ public function testNoRouteMatchReturns404(): void $this->expectExceptionCode(404); $ret = (new Dispatcher()) ->setEndpointList([]) // No routes - ->setParserList([]) ->dispatch($req); } @@ -317,7 +301,6 @@ public function testFailedInputValidationCanReachErrorHandlers(): void try { $response = (new Dispatcher()) ->setEndpointList($this->getEndpointListForFixture()) - ->setParserList($this->getDefaultParserList()) ->dispatch($req); $this->fail('An exception should have been thrown'); } catch (Throwable $e) { @@ -334,7 +317,6 @@ public function testUnsupportedContentTypeCanReachErrorHandlers(): void try { $response = (new Dispatcher()) ->setEndpointList($this->getEndpointListForFixture()) - ->setParserList($this->getDefaultParserList()) ->dispatch($req); $this->fail('An exception should have been thrown'); } catch (Throwable $e) { @@ -354,7 +336,6 @@ public function testMatchingContentTypeWithDirectives(): void $req = $req->withHeader('Content-type', $contentType); $response = (new Dispatcher()) ->setEndpointList($this->getEndpointListForFixture()) - ->setParserList($this->getDefaultParserList()) ->dispatch($req); $this->checkResponse($response, 200); } @@ -449,7 +430,6 @@ public function testErrorHandlerHandles404(): void $request = $this->getMockRequestWithUriPath('/'); $finalResponse = (new Dispatcher()) ->setEndpointList([]) - ->setParserList([]) ->setContainer($container) ->dispatch($request); $this->assertSame($response, $finalResponse); @@ -704,7 +684,7 @@ private function executeMockRequestOnEndpoint( ): ResponseInterface { $req = $this->getMockRequestWithUriPath('/container', 'GET', []); $list = [ - 'GET' => [ + HTTPMethod::GET => [ '/container' => 'ClassThatDoesNotExist', ], ]; @@ -718,32 +698,25 @@ private function executeMockRequestOnEndpoint( $response = $dispatcher ->setContainer($this->getMockContainer($containerValues)) ->setEndpointList($list) - ->setParserList($this->getDefaultParserList()) ->dispatch($req); return $response; } - /** @return string[][] */ + /** + * @return array>> + */ private function getEndpointListForFixture(): array { return [ - 'GET' => [ + HTTPMethod::GET => [ '/user/(?P[1-9]\d*)' => __NAMESPACE__.'\EndpointFixture' ], - 'POST' => [ + HTTPMethod::POST => [ '/user/(?P[1-9]\d*)' => __NAMESPACE__.'\EndpointFixture' ], ]; } - private function getDefaultParserList(): array - { - return [ - 'application/json' => Parsers\JSON::class, - 'application/x-www-form-urlencoded' => Parsers\URLEncoded::class, - ]; - } - /** * @param array $values */ From 0e95f5536ae3671406df3359f730d07dfabc2628 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 12 Jul 2021 11:14:26 -0700 Subject: [PATCH 16/25] Continue clean up --- CHANGELOG.md | 5 ++++ src/Console/CompileAll.php | 41 +++++++++----------------------- src/Dispatcher.php | 1 + tests/Console/CompileAllTest.php | 14 ----------- 4 files changed, 17 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a683938..677788e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - Framework-generated files are now auto-detected thanks to the above compile requirement (#84) - `Dispatcher::setEndpointList()` and `Dispatcher::setParserList()` are now internal use only, and are no longer called in the generated front controller (#84) - `Dispatcher::dispatch()` now requires `ServerRequestInterface` as a parameter. This replaces `setRequest` (#101) +- The body parser list (based on MIME-types) is now explicitly hardcoded. + Previously this was tied to a scanned vendor directory, so in practice nothing has changed. + This may become configurable in the future. +- Dispatcher::ENDPOINT_LIST has been marked internal ### Deprecated - Direct use of the HTTPMethod class is considered deprecated. @@ -44,6 +48,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `Dispatcher::setAuthProviders()` (use setContainer) - `Dispatcher::setErrorHandler()` (use setContainer) - `Dispatcher::setRequest()` (provide the request directly to `::dispatch()`) +- `Dispatcher::PARSER_LIST` constant - `Interfaces\EndpointInterface::authenticate()` - this drops legacy authentication support entirely, and will no longer be used even if still defined in implementing classes - `Traits\Authentication\BearerToken` - `Traits\DeleteRequest` diff --git a/src/Console/CompileAll.php b/src/Console/CompileAll.php index e0628b7..8e8c9ca 100644 --- a/src/Console/CompileAll.php +++ b/src/Console/CompileAll.php @@ -47,15 +47,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int $logger->debug('Current directory: {cwd}', ['cwd' => getcwd()]); $logger->debug('Building classmap'); - $endpoints = $this->getFilteredClasses($this->config->get(Config::KEY_SOURCE), function (ReflectionClass $rc): bool { - if (!$rc->isInstantiable()) { - return false; + $endpoints = $this->getFilteredClasses( + $this->config->get(Config::KEY_SOURCE), + function (ReflectionClass $rc): bool { + if (!$rc->isInstantiable()) { + return false; + } + if (!$rc->implementsInterface(EndpointInterface::class)) { + return false; + } + return true; } - if (!$rc->implementsInterface(EndpointInterface::class)) { - return false; - } - return true; - }); + ); $endpointMap = array_reduce($endpoints, function (array $carry, ReflectionClass $rc) { $instance = $rc->newInstanceWithoutConstructor(); assert($instance instanceof EndpointInterface); // Filtered above @@ -73,28 +76,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int Dispatcher::ENDPOINT_LIST )); - $logger->debug('Building parser map'); - // For now, simply hardcode the values. - $parsers = [ - new Parsers\JSON(), - new Parsers\URLEncoded(), - ]; - $parserMap = array_reduce($parsers, function (array $carry, ParserInterface $parser) { - foreach ($parser->getSupportedMimeTypes() as $mime) { - $carry[$mime] = get_class($parser); - } - return $carry; - }, []); - $parserMap['@gener'.'ated'] = gmdate('c'); - file_put_contents( - Dispatcher::PARSER_LIST, - sprintf("writeln(sprintf( - 'Wrote parser map to %s', - Dispatcher::PARSER_LIST - )); return 0; } diff --git a/src/Dispatcher.php b/src/Dispatcher.php index d18de69..a51e59d 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -31,6 +31,7 @@ */ class Dispatcher implements RequestHandlerInterface { + /** @internal */ const ENDPOINT_LIST = '__endpoint_list__.php'; /** @var ?ContainerInterface */ diff --git a/tests/Console/CompileAllTest.php b/tests/Console/CompileAllTest.php index 91c8d32..9d49fdc 100644 --- a/tests/Console/CompileAllTest.php +++ b/tests/Console/CompileAllTest.php @@ -43,14 +43,10 @@ public function testExecute(): void $tester = new CommandTester($command); $tester->execute([]); $this->assertFileExists(Dispatcher::ENDPOINT_LIST, 'Endpoint list not generated'); - $this->assertFileExists(Dispatcher::PARSER_LIST, 'Parser list not generated'); $endpoints = include Dispatcher::ENDPOINT_LIST; - $parsers = include Dispatcher::PARSER_LIST; $this->validateEndpointList($endpoints); - $this->validateParserList($parsers); } finally { @unlink(Dispatcher::ENDPOINT_LIST); - @unlink(Dispatcher::PARSER_LIST); } } @@ -63,14 +59,4 @@ private function validateEndpointList(array $data): void $this->assertArrayHasKey('GET', $data); $this->assertContains(EndpointFixture::class, $data['GET']); } - - /** - * @param string[] $data - */ - private function validateParserList(array $data): void - { - $this->assertArrayHasKey('@gener'.'ated', $data); - $this->assertArrayHasKey('application/json', $data); - $this->assertArrayHasKey('application/x-www-form-urlencoded', $data); - } } From 2dedbf521fbf2750aa8ed26f227b8488c3040877 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 12 Jul 2021 11:21:44 -0700 Subject: [PATCH 17/25] Force-ignore intentional errors --- phpstan-baseline.neon | 19 +++++++++++++++++-- phpstan.neon | 6 +++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5dfb686..8229c26 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,12 +1,12 @@ parameters: ignoreErrors: - - message: "#^Parameter \\#1 \\$exception of method PHPUnit\\\\Framework\\\\TestCase\\:\\:expectException\\(\\) expects class\\-string\\, string given\\.$#" + message: "#^Method Firehed\\\\API\\\\ConfigTest\\:\\:constructProvider\\(\\) should return array\\\\> but returns array\\\\|class\\-string\\>\\>\\.$#" count: 1 path: tests/ConfigTest.php - - message: "#^Method Firehed\\\\API\\\\ConfigTest\\:\\:constructProvider\\(\\) should return array\\\\> but returns array\\\\|class\\-string\\>\\>\\.$#" + message: "#^Parameter \\#1 \\$exception of method PHPUnit\\\\Framework\\\\TestCase\\:\\:expectException\\(\\) expects class\\-string\\, string given\\.$#" count: 1 path: tests/ConfigTest.php @@ -15,3 +15,18 @@ parameters: count: 1 path: tests/ContainerTest.php + - + message: "#^Parameter \\#1 \\$endpointList of method Firehed\\\\API\\\\Dispatcher\\:\\:setEndpointList\\(\\) expects array\\<'DELETE'\\|'GET'\\|'OPTIONS'\\|'PATCH'\\|'POST'\\|'PUT', array\\\\>\\>\\|string, array\\('GET' \\=\\> array\\('/c' \\=\\> 'EP'\\)\\) given\\.$#" + count: 1 + path: tests/DispatcherTest.php + + - + message: "#^Parameter \\#1 \\$endpointList of method Firehed\\\\API\\\\Dispatcher\\:\\:setEndpointList\\(\\) expects array\\<'DELETE'\\|'GET'\\|'OPTIONS'\\|'PATCH'\\|'POST'\\|'PUT', array\\\\>\\>\\|string, array\\('GET' \\=\\> array\\('/cb' \\=\\> 'CBClass'\\)\\) given\\.$#" + count: 1 + path: tests/DispatcherTest.php + + - + message: "#^Parameter \\#1 \\$endpointList of method Firehed\\\\API\\\\Dispatcher\\:\\:setEndpointList\\(\\) expects array\\<'DELETE'\\|'GET'\\|'OPTIONS'\\|'PATCH'\\|'POST'\\|'PUT', array\\\\>\\>\\|string, array\\('GET' \\=\\> array\\('/container' \\=\\> 'ClassThatDoesNotExi…'\\)\\) given\\.$#" + count: 1 + path: tests/DispatcherTest.php + diff --git a/phpstan.neon b/phpstan.neon index 2c98816..4ed514e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,10 +2,10 @@ includes: - phpstan-baseline.neon - vendor/phpstan/phpstan-phpunit/extension.neon parameters: - ignoreErrors: - - '#Call to method setInterface\(\) on an unknown class Firehed\\Common\\this#' excludes_analyse: - vendor - level: 8 + level: max + paths: + - . stubFiles: - tests/stubs/xdebug_get_headers.stub From 2c94da5907e1f4f7c4ad0a811305be292bfeb6de Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 12 Jul 2021 11:38:26 -0700 Subject: [PATCH 18/25] Imports --- src/Dispatcher.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Dispatcher.php b/src/Dispatcher.php index a51e59d..70825f2 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -22,9 +22,17 @@ use OutOfBoundsException; use UnexpectedValueException; -use function strtoupper; -use function preg_match; use function array_key_exists; +use function array_shift; +use function assert; +use function explode; +use function file_exists; +use function is_array; +use function is_int; +use function is_string; +use function parse_str; +use function preg_match; +use function strtoupper; /** * @phpstan-type EndpointMap array>> From 1344f04427184e5189db355f61e510280c04ef26 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 12 Jul 2021 11:51:24 -0700 Subject: [PATCH 19/25] test on 8.1 --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9f2f8a4..ad8cf4f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,7 @@ jobs: php: - '7.4' - '8.0' + - '8.1-nightly' steps: - name: Check out code From 7932bc33fd33c564edf4212bd3e9bddeb5f485e6 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 12 Jul 2021 11:56:00 -0700 Subject: [PATCH 20/25] Conditional definition --- src/Enums/HTTPMethod.php | 26 ++++++++++++-------------- src/Enums/HTTPMethodGTE81.php | 18 ++++++++++++++++++ src/Enums/HTTPMethodLTE80.php | 25 +++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 src/Enums/HTTPMethodGTE81.php create mode 100644 src/Enums/HTTPMethodLTE80.php diff --git a/src/Enums/HTTPMethod.php b/src/Enums/HTTPMethod.php index e03ffc1..8d2dc42 100644 --- a/src/Enums/HTTPMethod.php +++ b/src/Enums/HTTPMethod.php @@ -4,20 +4,18 @@ namespace Firehed\API\Enums; +use function class_alias; +use function version_compare; + +use const PHP_VERSION; + /** - * Direct use of this class (via these static methods) is deprecated. Instead, - * implementations should rely on the Request traits. - * - * This will be converted to a native Enum in PHP 8.1. + * This file generates an alias for HTTPMethod that uses either native enums + * (on supported PHP versions) or interface constants. The two implementations + * are not to be used directly. */ -interface HTTPMethod -{ - // Other methods exist, but these are the only relevant ones for RESTful - // APIs - const GET = 'GET'; - const PATCH = 'PATCH'; - const POST = 'POST'; - const PUT = 'PUT'; - const DELETE = 'DELETE'; - const OPTIONS = 'OPTIONS'; +if (version_compare(PHP_VERSION, '8.1.0', '>=')) { + class_alias(HTTPMethodGTE81::class, HTTPMethod::class); +} else { + class_alias(HTTPMethodLTE80::class, HTTPMethod::class); } diff --git a/src/Enums/HTTPMethodGTE81.php b/src/Enums/HTTPMethodGTE81.php new file mode 100644 index 0000000..d4cea5e --- /dev/null +++ b/src/Enums/HTTPMethodGTE81.php @@ -0,0 +1,18 @@ + Date: Mon, 12 Jul 2021 11:57:59 -0700 Subject: [PATCH 21/25] Ignore platform on nightly --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ad8cf4f..819ca78 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,6 +57,7 @@ jobs: --no-progress --no-suggest --prefer-dist + ${{ matrix.php == '8.1-nightly' && '--ignore-platform-reqs' }} - name: Install lowest dependencies if: ${{ matrix.dependencies == 'low' }} @@ -67,6 +68,7 @@ jobs: --no-suggest --prefer-dist --prefer-lowest + ${{ matrix.php == '8.1-nightly' && '--ignore-platform-reqs' }} - name: PHPUnit run: vendor/bin/phpunit From 08e23be3ab2cccc6fa00c0d32b1c74612d221246 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 12 Jul 2021 12:00:40 -0700 Subject: [PATCH 22/25] Debug mode on nightly too --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 819ca78..11b048d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,6 +73,7 @@ jobs: - name: PHPUnit run: vendor/bin/phpunit --coverage-clover coverage.xml + ${{ matrix.php == '8.1-nightly' && '--debug' }} - name: Submit code coverage if: ${{ always() }} From fe1aa664ba4dd4dd673f313ea966e6b5c8fba8bf Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 12 Jul 2021 12:03:24 -0700 Subject: [PATCH 23/25] Exclude 8.1-only file from coverage --- phpunit.xml.dist | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9d2221c..b7e819e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,11 +11,11 @@ tests - - - src - - + + + src/Enums/HTTPMethodGTE81.php + + From a4f1375c932069921ea6925ee355ccadc5ada074 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 12 Jul 2021 12:06:57 -0700 Subject: [PATCH 24/25] PReent weird install --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 11b048d..59c2099 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -68,12 +68,12 @@ jobs: --no-suggest --prefer-dist --prefer-lowest - ${{ matrix.php == '8.1-nightly' && '--ignore-platform-reqs' }} + ${{ matrix.php == '8.1-nightly' && '--ignore-platform-reqs' || '' }} - name: PHPUnit run: vendor/bin/phpunit --coverage-clover coverage.xml - ${{ matrix.php == '8.1-nightly' && '--debug' }} + ${{ matrix.php == '8.1-nightly' && '--debug' || '' }} - name: Submit code coverage if: ${{ always() }} From bf085bead5f8a25dddcb94879f5c7f33b9217f23 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 12 Jul 2021 12:07:48 -0700 Subject: [PATCH 25/25] Missed one --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 59c2099..14b3c43 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,7 +57,7 @@ jobs: --no-progress --no-suggest --prefer-dist - ${{ matrix.php == '8.1-nightly' && '--ignore-platform-reqs' }} + ${{ matrix.php == '8.1-nightly' && '--ignore-platform-reqs' || '' }} - name: Install lowest dependencies if: ${{ matrix.dependencies == 'low' }}