From 3e85bc85c5dde3f9aa0bb6b55a3d21db1b91a4ee Mon Sep 17 00:00:00 2001 From: evgsavosin Date: Tue, 6 Sep 2022 01:13:41 +0300 Subject: [PATCH] Update README.md --- README.md | 144 ++++++++++++++++++++++++-------- src/HttpMethod.php | 4 +- src/Resolver/Resolver.php | 11 ++- src/Resolver/ResolverResult.php | 5 ++ src/SimpleRouter.php | 7 ++ tests/SimpleRouterTest.php | 40 ++++++++- 6 files changed, 166 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 19c0b87..7603b3b 100644 --- a/README.md +++ b/README.md @@ -14,62 +14,132 @@ Install via composer: composer require evgsavosin/choco-router ``` -## Usage (deprecated) -Basic usage: +## Usage +### Basic usage +To use it is necessary to define the call of classes using for example PSR-11 implementation. ```php get('/foo', 'foo'); -$router->get('/foo/bar', function () { - // ... -}); +$router->addRoute(HttpMethod::GET, '/foo', fn (): string => 'Foo!' ); +$router->addRoute( + HttpMethod::POST, + '/foo/{bar}', + fn (mixed $value): string => "Foo bar and {$value}!", + ['bar' => '[0-9]+'] +); try { - $result = $router->dispatch( - $_SERVER['REQUEST_METHOD'], - $_SERVER['REQUEST_URI'] - ); - - // Call class method or function - call_user_func($result['handler'], $result['args']); + $router->resolve( + $_SERVER['REQUEST_METHOD'], + $_SERVER['REQUEST_URI'] + )->callableResolve(function (mixed $handler, array $arguments): mixed { + if (is_string($handler)) { + [$controllerName, $methodName] = explode('@', $handler); + + // PSR-11 implementation for classes: controllers, actions and etc... + } elseif (is_callable($handler)) { + $handler(...$arguments); + } + }); } catch (HttpException $e) { - if ($e->getCode() == HttpException::NOT_FOUND) { - // Handle not found page + if ($e->getCode() === HttpException::NOT_FOUND) { + // Handle 404... + } else if ($e->getCode() === HttpException::BAD_REQUEST) { + // Handle 400... } } +``` -// Declare function -function foo(array $args) { - // ... -} +### Route definition +The route can be defined with method: `addRoute(HttpMethod $httpMethod, string $uri, mixed $handler, array $parameters = []): void`. Parameters can be passed `{bar}` or `{bar?}` with regular expressions `['bar' => '[0-9]+']`. +Real example: +```php +$router->addRoute(HttpMethod::GET, '/foo/{bar?}', 'foo-bar', ['bar' => '[0-9]+']); ``` +> A question mark means the parameter is optional. +The route group is defined using `addGroup(string $prefix, callable $callback): void` method. Real example: +```php +$router->addGroup('/gorge', function (RouteCollection $r): void { + $router->addRoute(HttpMethod::GET, '/foo/{bar?}', 'foo-bar', ['bar' => '[0-9]+']); +}); +``` + +### HTTP Methods + +Full list of methods: -Available methods: ```php -$router->group('/foo', function () use ($router) { - $router->get('/bar', 'foo'); - $router->post('/bar', 'foo'); - $router->delete('/bar', 'foo'); - $router->put('/bar', 'foo'); - $router->map(['POST', 'GET'], '/bar/baz', 'foo'); -}); +HttpMethod::CONNECT +HttpMethod::HEAD +HttpMethod::GET +HttpMethod::POST +HttpMethod::PUT +HttpMethod::DELETE +HttpMethod::OPTIONS ``` -Parameters: +### Configuration + +You can set the configuration when initializing a simple router: ```php -$router->get('/foo/{bar}/{baz?}', 'foo', [ - 'bar' => '[a-zA-Z]', - 'baz' => '[0-9]' +$router = new SimpleRouter([ + 'cacheDisable' => false, + 'cacheDriver' => FileDriver::class, + 'cacheOptions' => [] + + /* + For memcached driver, there passed array of servers. + For file driver, there passed path to cache directory. + */ ]); ``` -> A question mark means the parameter is optional +### Attributes + +The router supports attributes from PHP 8.0. Example: + +```php +use App\Action\FooAction; + +$router = new SimpleRouter(); +$router->load([FooAction::class]); +$router->resolve(/*...*/)->callableResolve(/*...*/); +``` + +### Cache + +Router has support cache system with defined drivers: +- `ChocoRouter\Cache\Drivers\FileDriver::class`; +- `ChocoRouter\Cache\Drivers\ApcuDriver::class`; +- `ChocoRouter\Cache\Drivers\MemcachedDriver::class`. + +For use cache move definition routes to cache callback: +```php +$router = new SimpleRouter([ + 'cacheDriver' => FileDriver::class +]); + +$router->cache(static function (RouteCollection $r): void { + $r->addRoute(HttpMethod::GET, '/foo/{bar}', App\Actions\FooAction::class, ['bar' => '[0-9]+']); +}); + +$router->resolve(/*...*/)->callableResolve(/*...*/); +``` + +## Contributing + +The author has not yet had time to write instructions, but any pull request or issue will be glad. + +## License + +Choco Router has MIT License. \ No newline at end of file diff --git a/src/HttpMethod.php b/src/HttpMethod.php index 510e8ea..90cb9e2 100644 --- a/src/HttpMethod.php +++ b/src/HttpMethod.php @@ -9,8 +9,8 @@ * @author Evgeny Savosin */ enum HttpMethod: string { - CASE CONNECT = 'CONNECT'; - CASE HEAD = 'HEAD'; + case CONNECT = 'CONNECT'; + case HEAD = 'HEAD'; case GET = 'GET'; case POST = 'POST'; case PUT = 'PUT'; diff --git a/src/Resolver/Resolver.php b/src/Resolver/Resolver.php index 317aab6..1248cfb 100644 --- a/src/Resolver/Resolver.php +++ b/src/Resolver/Resolver.php @@ -36,7 +36,10 @@ public function resolve(string $httpMethod, string $uri): ?ResolverResult return new ResolverResult( $route, $uri, - $this->matchParameters($route->getParameters(), $matches) + $this->matchParameters( + $route->getParameters(), + $matches ?? [] + ) ); } } @@ -44,8 +47,12 @@ public function resolve(string $httpMethod, string $uri): ?ResolverResult return null; } - public function matchParameters(array $parameters, array $values): array + public function matchParameters(array $parameters, array $values = []): ?array { + if ($parameters === []) { + return []; + } + $i = 1; $parameterValues = []; diff --git a/src/Resolver/ResolverResult.php b/src/Resolver/ResolverResult.php index 6e69706..8d313a8 100644 --- a/src/Resolver/ResolverResult.php +++ b/src/Resolver/ResolverResult.php @@ -37,4 +37,9 @@ public function getParameters(): array { return $this->parameters; } + + public function callableResolve(callable $handler): mixed + { + return $handler($this->getHandler(), $this->getParameters()); + } } \ No newline at end of file diff --git a/src/SimpleRouter.php b/src/SimpleRouter.php index 9ee1159..23431a3 100644 --- a/src/SimpleRouter.php +++ b/src/SimpleRouter.php @@ -4,6 +4,7 @@ namespace ChocoRouter; +use ChocoRouter\Attribute\AttributeLoader; use ChocoRouter\Cache\{Cache, CacheKey, DisableCacheException}; use ChocoRouter\Resolver\ResolverResult; use Closure; @@ -30,6 +31,12 @@ public function __construct(array $options = []) $collection = new RouteCollection(); $this->router = new Router($collection); + $this->attributeLoader = new AttributeLoader($collection); + } + + public function load(array $classes): void + { + $this->attributeLoader->load($classes); } public function getCollection(): RouteCollection diff --git a/tests/SimpleRouterTest.php b/tests/SimpleRouterTest.php index 9b5805b..6d1c33b 100644 --- a/tests/SimpleRouterTest.php +++ b/tests/SimpleRouterTest.php @@ -6,6 +6,7 @@ use ChocoRouter\Cache\Drivers\FileDriver; use PHPUnit\Framework\TestCase; +use Tests\Controllers\{FooController, BazAction}; use ChocoRouter\Resolver\ResolverResult; use ChocoRouter\HttpMethod; use ChocoRouter\RouteCollection; @@ -16,10 +17,12 @@ final class SimpleRouterTest extends TestCase public function testOneParameterHandling(): void { $router = new SimpleRouter(); - $router->addRoute(HttpMethod::GET, '/foo/{bar}', 'foo-bar', ['bar' => '[0-9]+']); + $router->addRoute(HttpMethod::GET, '/foo/{bar}', fn (): string => 'foo-bar', ['bar' => '[0-9]+']); $result = $router->resolve('GET', '/foo/1'); - - $this->assertInstanceOf(ResolverResult::class, $result); + $this->assertEquals( + 'foo-bar', + $result->callableResolve(fn (mixed $handler, array $arguments): mixed => $handler($arguments)) + ); } public function testTwoParametersHandling(): void @@ -35,6 +38,22 @@ public function testTwoParametersHandling(): void $this->assertInstanceOf(ResolverResult::class, $result); } + public function testGroup(): void + { + $router = new SimpleRouter(); + + $router->addGroup('/gorge', function (RouteCollection $r): void { + $r->addRoute(HttpMethod::GET, '/foo/{bar}/{quxx?}', 'foo-bar', [ + 'quxx' => '[a-zA-Z]+', + 'bar' => '[0-9]+' + ]); + }); + + $result = $router->resolve('GET', '/gorge/foo/1/'); + + $this->assertInstanceOf(ResolverResult::class, $result); + } + public function testCacheViaFileDriver(): void { $router = new SimpleRouter([ @@ -42,11 +61,24 @@ public function testCacheViaFileDriver(): void 'cacheDriver' => FileDriver::class ]); - $router->cache(static function (RouteCollection $r) { + $router->cache(static function (RouteCollection $r): void { $r->addRoute(HttpMethod::GET, '/foo/{bar}', 'foo-bar', ['bar' => '[0-9]+']); }); $result = $router->resolve('GET', '/foo/1'); $this->assertInstanceOf(ResolverResult::class, $result); } + + public function testLoadController(): void + { + $router = new SimpleRouter(); + + $router->load([ + FooController::class, + BazAction::class + ]); + + $result = $router->resolve('GET', '/foo'); + $this->assertInstanceOf(ResolverResult::class, $result); + } } \ No newline at end of file