Skip to content

Commit

Permalink
add route group
Browse files Browse the repository at this point in the history
  • Loading branch information
wpjscc committed Oct 18, 2023
1 parent 7f64e51 commit dae9020
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 2 deletions.
36 changes: 34 additions & 2 deletions src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ class App
/** @var ReactiveHandler|SapiHandler */
private $sapi;

/** @var string */
protected $currentGroupPrefix;

/** @var callable|class-string[] */
protected $currentGroupHandlers = [];

/**
* Instantiate new X application
*
Expand Down Expand Up @@ -190,7 +196,27 @@ public function options(string $route, $handler, ...$handlers): void
*/
public function any(string $route, $handler, ...$handlers): void
{
$this->map(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], $route, $handler, ...$handlers);
$this->map(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], $this->currentGroupPrefix . $route, $handler, ...$handlers);
}

/**
* Create a route group with a common prefix.
*
* All routes created in the passed callback will have the given group prefix prepended.
*
* @param string $prefix
* @param callable|class-string[] $handlers
* @param callable $callback
*/
public function addGroup(string $prefix, array $handlers, callable $callback): void
{
$previousGroupPrefix = $this->currentGroupPrefix;
$previousGroupHandlers = $this->currentGroupHandlers;
$this->currentGroupPrefix = $previousGroupPrefix . $prefix;
$this->currentGroupHandlers = array_merge($previousGroupHandlers, $handlers);
$callback($this);
$this->currentGroupPrefix = $previousGroupPrefix;
$this->currentGroupHandlers = $previousGroupHandlers;
}

/**
Expand All @@ -202,7 +228,13 @@ public function any(string $route, $handler, ...$handlers): void
*/
public function map(array $methods, string $route, $handler, ...$handlers): void
{
$this->router->map($methods, $route, $handler, ...$handlers);
if (!empty($this->currentGroupHandlers)) {
\array_unshift($handlers, $handler);
$currentGroupHandlers = $this->currentGroupHandlers;
$handler = array_pop($currentGroupHandlers);
\array_unshift($handlers, ...$currentGroupHandlers);
}
$this->router->map($methods, $this->currentGroupPrefix . $route, $handler, ...$handlers);
}

/**
Expand Down
204 changes: 204 additions & 0 deletions tests/AppTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1649,6 +1649,210 @@ public function testInvokeWithMatchingRouteReturnsInternalServerErrorResponseWhe
$this->assertStringContainsString("<p>Expected request handler to return <code>Psr\Http\Message\ResponseInterface</code> but got <code>null</code>.</p>\n", (string) $response->getBody());
}

public function testInvokeWithMatchingGroupRouteReturnsResponseFromMatchingRouteHandler(): void
{
$app = $this->createAppWithoutLogger();
$app->addGroup('/users', [], function ($app) {
$app->get('', function () {
return new Response(
200,
[
'Content-Type' => 'text/html'
],
"OK\n"
);
});
});
$request = new ServerRequest('GET', 'http://localhost/users');

$response = $app($request);
assert($response instanceof ResponseInterface);

$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('text/html', $response->getHeaderLine('Content-Type'));
$this->assertEquals("OK\n", (string) $response->getBody());
}

public function testInvokeWithMatchingChildGroupRouteReturnsResponseFromMatchingRouteHandler(): void
{
$app = $this->createAppWithoutLogger();
$app->addGroup('/users', [], function ($app) {
$app->addGroup('/{name}', [], function ($app) {
$app->get('/posts/{post}', function (ServerRequestInterface $request) {
$name = $request->getAttribute('name');
$post = $request->getAttribute('post');
assert(is_string($name));
assert(is_string($post));

return new Response(
200,
[
'Content-Type' => 'text/html'
],
"OK $name $post\n"
);
});
});
});
$request = new ServerRequest('GET', 'http://localhost/users/alice/posts/first');

$response = $app($request);
assert($response instanceof ResponseInterface);

$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('text/html', $response->getHeaderLine('Content-Type'));
$this->assertEquals("OK alice first\n", (string) $response->getBody());
}

public function testConstractAndGroupRouteWithMiddlewareReturnContructMiddleware(): void
{

$middleware1 = function () {
return new Response(
200,
[
'Content-Type' => 'text/html'
],
"OK1\n"
);
};
$middleware2 = function () {
return new Response(
200,
[
'Content-Type' => 'text/html'
],
"OK2\n"
);
};

$app = $this->createAppWithoutLogger(
$middleware1
);
$app->addGroup('/users', [
$middleware2
], function ($app) {
$app->get('', function () {
return new Response(
200,
[
'Content-Type' => 'text/html'
],
"OK3\n"
);
});
});
$request = new ServerRequest('GET', 'http://localhost/users');

$response = $app($request);
assert($response instanceof ResponseInterface);

$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('text/html', $response->getHeaderLine('Content-Type'));
$this->assertEquals("OK1\n", (string) $response->getBody());
}

public function testGroupRouteWithMiddlewareReturnGroupMiddleware(): void
{

$middleware2 = function () {
return new Response(
200,
[
'Content-Type' => 'text/html'
],
"OK2\n"
);
};

$app = $this->createAppWithoutLogger();
$app->addGroup('/users', [
$middleware2
], function ($app) {
$app->get('', function () {
return new Response(
200,
[
'Content-Type' => 'text/html'
],
"OK3\n"
);
});
});
$request = new ServerRequest('GET', 'http://localhost/users');

$response = $app($request);
assert($response instanceof ResponseInterface);

$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('text/html', $response->getHeaderLine('Content-Type'));
$this->assertEquals("OK2\n", (string) $response->getBody());
}

public function testGroupRouteWithMiddlewareReturnRouteMiddleware(): void
{

$middleware2 = function ($request, $next) {
return $next($request);
};

$app = $this->createAppWithoutLogger();
$app->addGroup('/users', [
$middleware2
], function ($app) {
$app->get('', function () {
return new Response(
200,
[
'Content-Type' => 'text/html'
],
"OK3\n"
);
});
});
$request = new ServerRequest('GET', 'http://localhost/users');

$response = $app($request);
assert($response instanceof ResponseInterface);

$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('text/html', $response->getHeaderLine('Content-Type'));
$this->assertEquals("OK3\n", (string) $response->getBody());
}

public function testSetRequestGroupRouteWithMiddlewareAndReturnRouteMiddleware(): void
{

$middleware2 = function ($request, $next) {
return $next($request->withAttribute('group', 'group'));
};

$app = $this->createAppWithoutLogger();
$app->addGroup('/users', [
$middleware2
], function ($app) {
$app->get('', function ($request) {
$group = $request->getAttribute('group');
assert(is_string($group));
return new Response(
200,
[
'Content-Type' => 'text/html'
],
"OK {$group}\n"
);
});
});
$request = new ServerRequest('GET', 'http://localhost/users');

$response = $app($request);
assert($response instanceof ResponseInterface);

$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('text/html', $response->getHeaderLine('Content-Type'));
$this->assertEquals("OK group\n", (string) $response->getBody());
}

private function createAppWithoutLogger(callable ...$middleware): App
{
$app = new App(...$middleware);
Expand Down

0 comments on commit dae9020

Please sign in to comment.