Merge pull request #506 from flightphp/route-grouping

Route grouping
pull/511/head
n0nag0n 1 year ago committed by GitHub
commit 4b89e37457
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -312,6 +312,68 @@ Flight::route('/', function(\flight\net\Route $route) {
}, true);
```
## Route Grouping
There may be times when you want to group related routes together (such as `/api/v1`).
You can do this by using the `group` method:
```php
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Matches /api/v1/users
});
Flight::route('/posts', function () {
// Matches /api/v1/posts
});
});
```
You can even nest groups of groups:
```php
Flight::group('/api', function () {
Flight::group('/v1', function () {
// Flight::get() gets variables, it doesn't set a route! See object context below
Flight::route('GET /users', function () {
// Matches GET /api/v1/users
});
Flight::post('/posts', function () {
// Matches POST /api/v1/posts
});
Flight::put('/posts/1', function () {
// Matches PUT /api/v1/posts
});
});
Flight::group('/v2', function () {
// Flight::get() gets variables, it doesn't set a route! See object context below
Flight::route('GET /users', function () {
// Matches GET /api/v2/users
});
});
});
```
### Grouping with Object Context
You can still use route grouping with the `Engine` object in the following way:
```php
$app = new \flight\Engine();
$app->group('/api/v1', function (Router $router) {
$router->get('/users', function () {
// Matches GET /api/v1/users
});
$router->post('/posts', function () {
// Matches POST /api/v1/posts
});
});
```
# Extending
Flight is designed to be an extensible framework. The framework comes with a set

@ -33,7 +33,7 @@ use Throwable;
*
* Routing
* @method void route(string $pattern, callable $callback, bool $pass_route = false) Routes a URL to a callback function.
* @method void get(string $pattern, callable $callback, bool $pass_route = false) Routes a GET URL to a callback function.
* @method void group(string $pattern, callable $callback) Groups a set of routes together under a common prefix.
* @method void post(string $pattern, callable $callback, bool $pass_route = false) Routes a POST URL to a callback function.
* @method void put(string $pattern, callable $callback, bool $pass_route = false) Routes a PUT URL to a callback function.
* @method void patch(string $pattern, callable $callback, bool $pass_route = false) Routes a PATCH URL to a callback function.
@ -151,7 +151,7 @@ class Engine
$methods = [
'start', 'stop', 'route', 'halt', 'error', 'notFound',
'render', 'redirect', 'etag', 'lastModified', 'json', 'jsonp',
'post', 'put', 'patch', 'delete',
'post', 'put', 'patch', 'delete', 'group',
];
foreach ($methods as $name) {
$this->dispatcher->set($name, [$this, '_' . $name]);
@ -468,6 +468,17 @@ class Engine
$this->router()->map($pattern, $callback, $pass_route);
}
/**
* Routes a URL to a callback function.
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function that includes the Router class as first parameter
*/
public function _group(string $pattern, callable $callback): void
{
$this->router()->group($pattern, $callback);
}
/**
* Routes a URL to a callback function.
*

@ -24,6 +24,11 @@ use flight\template\View;
* @method static void halt(int $code = 200, string $message = '') Stop the framework with an optional status code and message.
*
* @method static void route(string $pattern, callable $callback, bool $pass_route = false) Maps a URL pattern to a callback.
* @method static void group(string $pattern, callable $callback) Groups a set of routes together under a common prefix.
* @method void post(string $pattern, callable $callback, bool $pass_route = false) Routes a POST URL to a callback function.
* @method void put(string $pattern, callable $callback, bool $pass_route = false) Routes a PUT URL to a callback function.
* @method void patch(string $pattern, callable $callback, bool $pass_route = false) Routes a PATCH URL to a callback function.
* @method void delete(string $pattern, callable $callback, bool $pass_route = false) Routes a DELETE URL to a callback function.
* @method static Router router() Returns Router instance.
*
* @method static void map(string $name, callable $callback) Creates a custom framework method.
@ -123,11 +128,22 @@ class Flight
if (!$initialized) {
require_once __DIR__ . '/autoload.php';
self::$engine = new Engine();
self::setEngine(new Engine());
$initialized = true;
}
return self::$engine;
}
/**
* Set the engine instance
*
* @param Engine $engine Vroom vroom!
* @return void
*/
public static function setEngine(Engine $engine): void
{
self::$engine = $engine;
}
}

@ -32,6 +32,13 @@ class Router
*/
protected int $index = 0;
/**
* When groups are used, this is mapped against all the routes
*
* @var string
*/
protected string $group_prefix = '';
/**
* Gets mapped routes.
*
@ -56,6 +63,7 @@ class Router
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback
* @return void
*/
public function map(string $pattern, callable $callback, bool $pass_route = false): void
{
@ -68,9 +76,83 @@ class Router
$methods = explode('|', $method);
}
$this->routes[] = new Route($url, $callback, $methods, $pass_route);
$this->routes[] = new Route($this->group_prefix.$url, $callback, $methods, $pass_route);
}
/**
* Creates a GET based route
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback
* @return void
*/
public function get(string $pattern, callable $callback, bool $pass_route = false): void {
$this->map('GET ' . $pattern, $callback, $pass_route);
}
/**
* Creates a POST based route
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback
* @return void
*/
public function post(string $pattern, callable $callback, bool $pass_route = false): void {
$this->map('POST ' . $pattern, $callback, $pass_route);
}
/**
* Creates a PUT based route
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback
* @return void
*/
public function put(string $pattern, callable $callback, bool $pass_route = false): void {
$this->map('PUT ' . $pattern, $callback, $pass_route);
}
/**
* Creates a PATCH based route
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback
* @return void
*/
public function patch(string $pattern, callable $callback, bool $pass_route = false): void {
$this->map('PATCH ' . $pattern, $callback, $pass_route);
}
/**
* Creates a DELETE based route
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback
* @return void
*/
public function delete(string $pattern, callable $callback, bool $pass_route = false): void {
$this->map('DELETE ' . $pattern, $callback, $pass_route);
}
/**
* Group together a set of routes
*
* @param string $group_prefix group URL prefix (such as /api/v1)
* @param callable $callback The necessary calling that holds the Router class
* @return void
*/
public function group(string $group_prefix, callable $callback): void {
$old_group_prefix = $this->group_prefix;
$this->group_prefix .= $group_prefix;
$callback($this);
$this->group_prefix = $old_group_prefix;
}
/**
* Routes the current request.
*

@ -1,5 +1,6 @@
<?php
use flight\Engine;
use flight\net\Request;
use flight\net\Response;
use flight\net\Router;
@ -18,11 +19,13 @@ class FlightTest extends PHPUnit\Framework\TestCase
$_SERVER = [];
$_REQUEST = [];
Flight::init();
Flight::setEngine(new Engine());
}
protected function tearDown(): void {
unset($_REQUEST);
unset($_SERVER);
Flight::clear();
}
// Checks that default components are loaded
@ -96,4 +99,92 @@ class FlightTest extends PHPUnit\Framework\TestCase
Flight::doesNotExist();
}
public function testStaticRoute() {
Flight::route('/test', function() {
echo 'test';
});
Flight::request()->url = '/test';
$this->expectOutputString('test');
Flight::start();
}
public function testStaticRouteGroup() {
Flight::group('/group', function() {
Flight::route('/test', function() {
echo 'test';
});
});
Flight::request()->url = '/group/test';
$this->expectOutputString('test');
Flight::start();
}
public function testStaticRouteGet() {
// can't actually get "get" because that gets a variable
Flight::route('GET /test', function() {
echo 'test get';
});
$_SERVER['REQUEST_METHOD'] = 'GET';
Flight::request()->url = '/test';
$this->expectOutputString('test get');
Flight::start();
}
public function testStaticRoutePost() {
Flight::post('/test', function() {
echo 'test post';
});
$_SERVER['REQUEST_METHOD'] = 'POST';
Flight::request()->url = '/test';
$this->expectOutputString('test post');
Flight::start();
}
public function testStaticRoutePut() {
Flight::put('/test', function() {
echo 'test put';
});
$_SERVER['REQUEST_METHOD'] = 'PUT';
Flight::request()->url = '/test';
$this->expectOutputString('test put');
Flight::start();
}
public function testStaticRoutePatch() {
Flight::patch('/test', function() {
echo 'test patch';
});
$_SERVER['REQUEST_METHOD'] = 'PATCH';
Flight::request()->url = '/test';
$this->expectOutputString('test patch');
Flight::start();
}
public function testStaticRouteDelete() {
Flight::delete('/test', function() {
echo 'test delete';
});
$_SERVER['REQUEST_METHOD'] = 'DELETE';
Flight::request()->url = '/test';
$this->expectOutputString('test delete');
Flight::start();
}
}

@ -106,7 +106,6 @@ class RouterTest extends PHPUnit\Framework\TestCase
}
// Simple path with trailing slash
// Simple path
public function testPathRouteTrailingSlash()
{
$this->router->map('/path/', [$this, 'ok']);
@ -115,6 +114,15 @@ class RouterTest extends PHPUnit\Framework\TestCase
$this->check('OK');
}
public function testGetRouteShortcut()
{
$this->router->get('/path', [$this, 'ok']);
$this->request->url = '/path';
$this->request->method = 'GET';
$this->check('OK');
}
// POST route
public function testPostRoute()
{
@ -125,6 +133,15 @@ class RouterTest extends PHPUnit\Framework\TestCase
$this->check('OK');
}
public function testPostRouteShortcut()
{
$this->router->post('/path', [$this, 'ok']);
$this->request->url = '/path';
$this->request->method = 'POST';
$this->check('OK');
}
// Either GET or POST route
public function testGetPostRoute()
{
@ -135,6 +152,30 @@ class RouterTest extends PHPUnit\Framework\TestCase
$this->check('OK');
}
public function testPutRouteShortcut() {
$this->router->put('/path', [$this, 'ok']);
$this->request->url = '/path';
$this->request->method = 'PUT';
$this->check('OK');
}
public function testPatchRouteShortcut() {
$this->router->patch('/path', [$this, 'ok']);
$this->request->url = '/path';
$this->request->method = 'PATCH';
$this->check('OK');
}
public function testDeleteRouteShortcut() {
$this->router->delete('/path', [$this, 'ok']);
$this->request->url = '/path';
$this->request->method = 'DELETE';
$this->check('OK');
}
// Test regular expression matching
public function testRegEx()
{
@ -208,6 +249,12 @@ class RouterTest extends PHPUnit\Framework\TestCase
$this->check('OK');
}
public function testWildcardDuplicate() {
$this->router->map('/account/*' , [$this, 'ok']);
$this->request->url = '/account/account/account';
$this->check('OK');
}
// Check if route object was passed
public function testRouteObjectPassing()
{
@ -352,4 +399,66 @@ class RouterTest extends PHPUnit\Framework\TestCase
$router->reset();
$this->assertEquals(0, $router->getIndex());
}
// Passing URL parameters
public function testGroupRoutes()
{
$this->router->group('/user', function(Router $router) {
$router->map('/@id', function ($id) {
echo $id;
});
$router->map('/@id/@name', function ($id, $name) {
echo $id . $name;
});
});
$this->request->url = '/user/123';
$this->check('123');
}
public function testGroupRoutesMultiParams()
{
$this->router->group('/user', function(Router $router) {
$router->map('/@id', function ($id) {
echo $id;
});
$router->map('/@id/@name', function ($id, $name) {
echo $id . $name;
});
});
$this->request->url = '/user/123/abc';
$this->check('123abc');
}
public function testGroupNestedRoutes()
{
$this->router->group('/client', function(Router $router) {
$router->group('/user', function(Router $router) {
$router->map('/@id', function ($id) {
echo $id;
});
$router->map('/@id/@name', function ($id, $name) {
echo $id . $name;
});
});
});
$this->request->url = '/client/user/123/abc';
$this->check('123abc');
}
public function testGroupNestedRoutesWithCustomMethods()
{
$this->router->group('/client', function(Router $router) {
$router->group('/user', function(Router $router) {
$router->get('/@id', function ($id) {
echo $id;
});
$router->post('/@id/@name', function ($id, $name) {
echo $id . $name;
});
});
});
$this->request->url = '/client/user/123/abc';
$this->request->method = 'POST';
$this->check('123abc');
}
}

Loading…
Cancel
Save