Skip to content

Commit 1307e8a

Browse files
authored
Merge pull request #596 from flightphp/feat/route-compact-syntax
Group compact syntax
2 parents 128b6e3 + cc1597a commit 1307e8a

File tree

7 files changed

+296
-4
lines changed

7 files changed

+296
-4
lines changed

flight/Engine.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
* Routes a PATCH URL to a callback function.
4343
* @method Route delete(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
4444
* Routes a DELETE URL to a callback function.
45+
* @method void resource(string $pattern, string $controllerClass, array $methods = [])
46+
* Adds standardized RESTful routes for a controller.
4547
* @method Router router() Gets router
4648
* @method string getUrl(string $alias) Gets a url from an alias
4749
*
@@ -77,7 +79,7 @@ class Engine
7779
private const MAPPABLE_METHODS = [
7880
'start', 'stop', 'route', 'halt', 'error', 'notFound',
7981
'render', 'redirect', 'etag', 'lastModified', 'json', 'jsonHalt', 'jsonp',
80-
'post', 'put', 'patch', 'delete', 'group', 'getUrl', 'download'
82+
'post', 'put', 'patch', 'delete', 'group', 'getUrl', 'download', 'resource'
8183
];
8284

8385
/** @var array<string, mixed> Stored variables. */
@@ -598,8 +600,7 @@ public function _start(): void
598600
*/
599601
public function _error(Throwable $e): void
600602
{
601-
$msg = sprintf(
602-
<<<HTML
603+
$msg = sprintf(<<<HTML
603604
<h1>500 Internal Server Error</h1>
604605
<h3>%s (%s)</h3>
605606
<pre>%s</pre>
@@ -729,6 +730,20 @@ public function _delete(string $pattern, $callback, bool $pass_route = false, st
729730
return $this->router()->map('DELETE ' . $pattern, $callback, $pass_route, $route_alias);
730731
}
731732

733+
/**
734+
* Create a resource controller customizing the methods names mapping.
735+
*
736+
* @param class-string $controllerClass
737+
* @param array<string, string|array<string>> $options
738+
*/
739+
public function _resource(
740+
string $pattern,
741+
string $controllerClass,
742+
array $options = []
743+
): void {
744+
$this->router()->mapResource($pattern, $controllerClass, $options);
745+
}
746+
732747
/**
733748
* Stops processing and returns a given response.
734749
*

flight/Flight.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
* Routes a PATCH URL to a callback function.
4343
* @method static Route delete(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
4444
* Routes a DELETE URL to a callback function.
45+
* @method static void resource(string $pattern, string $controllerClass, array $methods = [])
46+
* Adds standardized RESTful routes for a controller.
4547
* @method static Router router() Returns Router instance.
4648
* @method static string getUrl(string $alias, array<string, mixed> $params = []) Gets a url from an alias
4749
*

flight/net/Router.php

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public function clear(): void
8787
public function map(string $pattern, $callback, bool $pass_route = false, string $route_alias = ''): Route
8888
{
8989

90-
// This means that the route ies defined in a group, but the defined route is the base
90+
// This means that the route is defined in a group, but the defined route is the base
9191
// url path. Note the '' in route()
9292
// Ex: Flight::group('/api', function() {
9393
// Flight::route('', function() {});
@@ -276,6 +276,69 @@ public function getUrlByAlias(string $alias, array $params = []): string
276276
throw new Exception($exception_message);
277277
}
278278

279+
/**
280+
* Create a resource controller customizing the methods names mapping.
281+
*
282+
* @param class-string $controllerClass
283+
* @param array<string, string|array<string>> $options
284+
*/
285+
public function mapResource(
286+
string $pattern,
287+
string $controllerClass,
288+
array $options = []
289+
): void {
290+
291+
$defaultMapping = [
292+
'index' => 'GET ',
293+
'create' => 'GET /create',
294+
'store' => 'POST ',
295+
'show' => 'GET /@id',
296+
'edit' => 'GET /@id/edit',
297+
'update' => 'PUT /@id',
298+
'destroy' => 'DELETE /@id'
299+
];
300+
301+
// Create a custom alias base
302+
$aliasBase = trim(basename($pattern), '/');
303+
if (isset($options['alias_base']) === true) {
304+
$aliasBase = $options['alias_base'];
305+
}
306+
307+
// Only use these controller methods
308+
if (isset($options['only']) === true) {
309+
$only = $options['only'];
310+
$defaultMapping = array_filter($defaultMapping, function ($key) use ($only) {
311+
return in_array($key, $only, true) === true;
312+
}, ARRAY_FILTER_USE_KEY);
313+
314+
// Exclude these controller methods
315+
} elseif (isset($options['except']) === true) {
316+
$except = $options['except'];
317+
$defaultMapping = array_filter($defaultMapping, function ($key) use ($except) {
318+
return in_array($key, $except, true) === false;
319+
}, ARRAY_FILTER_USE_KEY);
320+
}
321+
322+
// Add group middleware
323+
$middleware = [];
324+
if (isset($options['middleware']) === true) {
325+
$middleware = $options['middleware'];
326+
}
327+
328+
$this->group(
329+
$pattern,
330+
function (Router $router) use ($controllerClass, $defaultMapping, $aliasBase): void {
331+
foreach ($defaultMapping as $controllerMethod => $methodPattern) {
332+
$router->map(
333+
$methodPattern,
334+
[ $controllerClass, $controllerMethod ]
335+
)->setAlias($aliasBase . '.' . $controllerMethod);
336+
}
337+
},
338+
$middleware
339+
);
340+
}
341+
279342
/**
280343
* Rewinds the current route index.
281344
*/
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PHPUnit\Framework\TestCase;
6+
use tests\groupcompactsyntax\PostsController;
7+
use tests\groupcompactsyntax\TodosController;
8+
use tests\groupcompactsyntax\UsersController;
9+
10+
require_once __DIR__ . '/UsersController.php';
11+
require_once __DIR__ . '/PostsController.php';
12+
13+
final class FlightRouteCompactSyntaxTest extends TestCase
14+
{
15+
public function setUp(): void
16+
{
17+
Flight::router()->clear();
18+
}
19+
20+
public function testCanMapMethodsWithVerboseSyntax(): void
21+
{
22+
Flight::route('GET /users', [UsersController::class, 'index']);
23+
Flight::route('DELETE /users/@id', [UsersController::class, 'destroy']);
24+
25+
$routes = Flight::router()->getRoutes();
26+
27+
$this->assertCount(2, $routes);
28+
29+
$this->assertSame('/users', $routes[0]->pattern);
30+
$this->assertSame([UsersController::class, 'index'], $routes[0]->callback);
31+
$this->assertSame('GET', $routes[0]->methods[0]);
32+
33+
$this->assertSame('/users/@id', $routes[1]->pattern);
34+
$this->assertSame([UsersController::class, 'destroy'], $routes[1]->callback);
35+
$this->assertSame('DELETE', $routes[1]->methods[0]);
36+
}
37+
38+
public function testOptionsOnly(): void
39+
{
40+
Flight::resource('/users', UsersController::class, [
41+
'only' => [ 'index', 'destroy' ]
42+
]);
43+
44+
$routes = Flight::router()->getRoutes();
45+
46+
$this->assertCount(2, $routes);
47+
48+
$this->assertSame('/users', $routes[0]->pattern);
49+
$this->assertSame('GET', $routes[0]->methods[0]);
50+
$this->assertSame([UsersController::class, 'index'], $routes[0]->callback);
51+
52+
$this->assertSame('/users/@id', $routes[1]->pattern);
53+
$this->assertSame('DELETE', $routes[1]->methods[0]);
54+
$this->assertSame([UsersController::class, 'destroy'], $routes[1]->callback);
55+
}
56+
57+
public function testDefaultMethods(): void
58+
{
59+
Flight::resource('/posts', PostsController::class);
60+
61+
$routes = Flight::router()->getRoutes();
62+
$this->assertCount(7, $routes);
63+
64+
$this->assertSame('/posts', $routes[0]->pattern);
65+
$this->assertSame('GET', $routes[0]->methods[0]);
66+
$this->assertSame([PostsController::class, 'index'], $routes[0]->callback);
67+
$this->assertSame('posts.index', $routes[0]->alias);
68+
69+
$this->assertSame('/posts/create', $routes[1]->pattern);
70+
$this->assertSame('GET', $routes[1]->methods[0]);
71+
$this->assertSame([PostsController::class, 'create'], $routes[1]->callback);
72+
$this->assertSame('posts.create', $routes[1]->alias);
73+
74+
$this->assertSame('/posts', $routes[2]->pattern);
75+
$this->assertSame('POST', $routes[2]->methods[0]);
76+
$this->assertSame([PostsController::class, 'store'], $routes[2]->callback);
77+
$this->assertSame('posts.store', $routes[2]->alias);
78+
79+
$this->assertSame('/posts/@id', $routes[3]->pattern);
80+
$this->assertSame('GET', $routes[3]->methods[0]);
81+
$this->assertSame([PostsController::class, 'show'], $routes[3]->callback);
82+
$this->assertSame('posts.show', $routes[3]->alias);
83+
84+
$this->assertSame('/posts/@id/edit', $routes[4]->pattern);
85+
$this->assertSame('GET', $routes[4]->methods[0]);
86+
$this->assertSame([PostsController::class, 'edit'], $routes[4]->callback);
87+
$this->assertSame('posts.edit', $routes[4]->alias);
88+
89+
$this->assertSame('/posts/@id', $routes[5]->pattern);
90+
$this->assertSame('PUT', $routes[5]->methods[0]);
91+
$this->assertSame([PostsController::class, 'update'], $routes[5]->callback);
92+
$this->assertSame('posts.update', $routes[5]->alias);
93+
94+
$this->assertSame('/posts/@id', $routes[6]->pattern);
95+
$this->assertSame('DELETE', $routes[6]->methods[0]);
96+
$this->assertSame([PostsController::class, 'destroy'], $routes[6]->callback);
97+
$this->assertSame('posts.destroy', $routes[6]->alias);
98+
}
99+
100+
public function testOptionsExcept(): void
101+
{
102+
Flight::resource('/todos', TodosController::class, [
103+
'except' => [ 'create', 'store', 'update', 'destroy', 'edit' ]
104+
]);
105+
106+
$routes = Flight::router()->getRoutes();
107+
108+
$this->assertCount(2, $routes);
109+
110+
$this->assertSame('/todos', $routes[0]->pattern);
111+
$this->assertSame('GET', $routes[0]->methods[0]);
112+
$this->assertSame([TodosController::class, 'index'], $routes[0]->callback);
113+
114+
$this->assertSame('/todos/@id', $routes[1]->pattern);
115+
$this->assertSame('GET', $routes[1]->methods[0]);
116+
$this->assertSame([TodosController::class, 'show'], $routes[1]->callback);
117+
}
118+
119+
public function testOptionsMiddlewareAndAliasBase(): void
120+
{
121+
Flight::resource('/todos', TodosController::class, [
122+
'middleware' => [ 'auth' ],
123+
'alias_base' => 'nothanks'
124+
]);
125+
126+
$routes = Flight::router()->getRoutes();
127+
128+
$this->assertCount(7, $routes);
129+
130+
$this->assertSame('/todos', $routes[0]->pattern);
131+
$this->assertSame('GET', $routes[0]->methods[0]);
132+
$this->assertSame([TodosController::class, 'index'], $routes[0]->callback);
133+
$this->assertSame('auth', $routes[0]->middleware[0]);
134+
$this->assertSame('nothanks.index', $routes[0]->alias);
135+
136+
$this->assertSame('/todos/create', $routes[1]->pattern);
137+
$this->assertSame('GET', $routes[1]->methods[0]);
138+
$this->assertSame([TodosController::class, 'create'], $routes[1]->callback);
139+
$this->assertSame('auth', $routes[1]->middleware[0]);
140+
$this->assertSame('nothanks.create', $routes[1]->alias);
141+
}
142+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace tests\groupcompactsyntax;
6+
7+
final class PostsController
8+
{
9+
public function index(): void
10+
{
11+
}
12+
13+
public function show(string $id): void
14+
{
15+
}
16+
17+
public function create(): void
18+
{
19+
}
20+
21+
public function store(): void
22+
{
23+
}
24+
25+
public function edit(string $id): void
26+
{
27+
}
28+
29+
public function update(string $id): void
30+
{
31+
}
32+
33+
public function destroy(string $id): void
34+
{
35+
}
36+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace tests\groupcompactsyntax;
6+
7+
final class TodosController
8+
{
9+
public function index(): void
10+
{
11+
}
12+
13+
public function show(string $id): void
14+
{
15+
}
16+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace tests\groupcompactsyntax;
6+
7+
final class UsersController
8+
{
9+
public function index(): void
10+
{
11+
echo __METHOD__;
12+
}
13+
14+
public function destroy(): void
15+
{
16+
echo __METHOD__;
17+
}
18+
}

0 commit comments

Comments
 (0)