Note: Want to understand more about routing? Check out the "why a framework?" page for a more in-depth explanation.
Basic routing in Flight is done by matching a URL pattern with a callback function or an array of a class and method.
Flight::route('/', function(){ echo 'hello world!'; });
Routes are matched in the order they are defined. The first route to match a request will be invoked.
The callback can be any object that is callable. So you can use a regular function:
function hello() { echo 'hello world!'; } Flight::route('/', 'hello');
You can use a static method of a class as well:
class Greeting { public static function hello() { echo 'hello world!'; } } Flight::route('/', [ 'Greeting','hello' ]);
Or by creating an object first and then calling the method:
// Greeting.php class Greeting { public function __construct() { $this->name = 'John Doe'; } public function hello() { echo "Hello, {$this->name}!"; } } // index.php $greeting = new Greeting(); Flight::route('/', [ $greeting, 'hello' ]); // You also can do this without creating the object first // Note: No args will be injected into the constructor Flight::route('/', [ 'Greeting', 'hello' ]); // Additionally you can use this shorter syntax Flight::route('/', 'Greeting->hello'); // or Flight::route('/', Greeting::class.'->hello');
If you want to use dependency injection via a container (PSR-11, PHP-DI, Dice, etc), the only type of routes where that is available is either directly creating the object yourself and using the container to create your object or you can use strings to defined the class and method to call. You can go to the Dependency Injection page for more information.
Here's a quick example:
use flight\database\PdoWrapper; // Greeting.php class Greeting { protected PdoWrapper $pdoWrapper; public function __construct(PdoWrapper $pdoWrapper) { $this->pdoWrapper = $pdoWrapper; } public function hello(int $id) { // do something with $this->pdoWrapper $name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]); echo "Hello, world! My name is {$name}!"; } } // index.php // Setup the container with whatever params you need // See the Dependency Injection page for more information on PSR-11 $dice = new \Dice\Dice(); // Don't forget to reassign the variable with '$dice = '!!!!! $dice = $dice->addRule('flight\database\PdoWrapper', [ 'shared' => true, 'constructParams' => [ 'mysql:host=localhost;dbname=test', 'root', 'password' ] ]); // Register the container handler Flight::registerContainerHandler(function($class, $params) use ($dice) { return $dice->create($class, $params); }); // Routes like normal Flight::route('/hello/@id', [ 'Greeting', 'hello' ]); // or Flight::route('/hello/@id', 'Greeting->hello'); // or Flight::route('/hello/@id', 'Greeting::hello'); Flight::start();
By default, route patterns are matched against all request methods. You can respond to specific methods by placing an identifier before the URL.
Flight::route('GET /', function () { echo 'I received a GET request.'; }); Flight::route('POST /', function () { echo 'I received a POST request.'; }); // You cannot use Flight::get() for routes as that is a method // to get variables, not create a route. // Flight::post('/', function() { /* code */ }); // Flight::patch('/', function() { /* code */ }); // Flight::put('/', function() { /* code */ }); // Flight::delete('/', function() { /* code */ });
You can also map multiple methods to a single callback by using a | delimiter:
|
Flight::route('GET|POST /', function () { echo 'I received either a GET or a POST request.'; });
Additionally you can grab the Router object which has some helper methods for you to use:
$router = Flight::router(); // maps all methods $router->map('/', function() { echo 'hello world!'; }); // GET request $router->get('/users', function() { echo 'users'; }); // $router->post(); // $router->put(); // $router->delete(); // $router->patch();
You can use regular expressions in your routes:
Flight::route('/user/[0-9]+', function () { // This will match /user/1234 });
Although this method is available, it is recommended to use named parameters, or named parameters with regular expressions, as they are more readable and easier to maintain.
You can specify named parameters in your routes which will be passed along to your callback function.
Flight::route('/@name/@id', function (string $name, string $id) { echo "hello, $name ($id)!"; });
You can also include regular expressions with your named parameters by using the : delimiter:
:
Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) { // This will match /bob/123 // But will not match /bob/12345 });
Note: Matching regex groups () with named parameters isn't supported. :'(
()
You can specify named parameters that are optional for matching by wrapping segments in parentheses.
Flight::route( '/blog(/@year(/@month(/@day)))', function(?string $year, ?string $month, ?string $day) { // This will match the following URLS: // /blog/2012/12/10 // /blog/2012/12 // /blog/2012 // /blog } );
Any optional parameters that are not matched will be passed in as NULL.
NULL
Matching is only done on individual URL segments. If you want to match multiple segments you can use the * wildcard.
*
Flight::route('/blog/*', function () { // This will match /blog/2000/02/01 });
To route all requests to a single callback, you can do:
Flight::route('*', function () { // Do something });
You can pass execution on to the next matching route by returning true from your callback function.
true
Flight::route('/user/@name', function (string $name) { // Check some condition if ($name !== "Bob") { // Continue to next route return true; } }); Flight::route('/user/*', function () { // This will get called });
You can assign an alias to a route, so that the URL can dynamically be generated later in your code (like a template for instance).
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view'); // later in code somewhere Flight::getUrl('user_view', [ 'id' => 5 ]); // will return '/users/5'
This is especially helpful if your URL happens to change. In the above example, lets say that users was moved to /admin/users/@id instead. With aliasing in place, you don't have to change anywhere you reference the alias because the alias will now return /admin/users/5 like in the example above.
/admin/users/@id
/admin/users/5
Route aliasing still works in groups as well:
Flight::group('/users', function() { Flight::route('/@id', function($id) { echo 'user:'.$id; }, false, 'user_view'); }); // later in code somewhere Flight::getUrl('user_view', [ 'id' => 5 ]); // will return '/users/5'
If you want to inspect the matching route information, you can request for the route object to be passed to your callback by passing in true as the third parameter in the route method. The route object will always be the last parameter passed to your callback function.
Flight::route('/', function(\flight\net\Route $route) { // Array of HTTP methods matched against $route->methods; // Array of named parameters $route->params; // Matching regular expression $route->regex; // Contains the contents of any '*' used in the URL pattern $route->splat; // Shows the url path....if you really need it $route->pattern; // Shows what middleware is assigned to this $route->middleware; // Shows the alias assigned to this route $route->alias; }, true);
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:
/api/v1
group
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:
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 }); }); });
You can still use route grouping with the Engine object in the following way:
Engine
$app = new \flight\Engine(); $app->group('/api/v1', function (Router $router) { // user the $router variable $router->get('/users', function () { // Matches GET /api/v1/users }); $router->post('/posts', function () { // Matches POST /api/v1/posts }); });
You can create a set of routes for a resource using the resource method. This will create a set of routes for a resource that follows the RESTful conventions.
resource
To create a resource, do the following:
Flight::resource('/users', UsersController::class);
And what will happen in the background is it will create the following routes:
[ 'index' => 'GET ', 'create' => 'GET /create', 'store' => 'POST ', 'show' => 'GET /@id', 'edit' => 'GET /@id/edit', 'update' => 'PUT /@id', 'destroy' => 'DELETE /@id' ]
And your controller will look like this:
class UsersController { public function index(): void { } public function show(string $id): void { } public function create(): void { } public function store(): void { } public function edit(string $id): void { } public function update(string $id): void { } public function destroy(string $id): void { } }
Note: You can view the newly added routes with runway by running php runway routes.
runway
php runway routes
There are a few options to configure the resource routes.
You can configure the aliasBase. By default the alias is the last part of the URL specified. For example /users/ would result in an aliasBase of users. When these routes are created, the aliases are users.index, users.create, etc. If you want to change the alias, set the aliasBase to the value you want.
aliasBase
/users/
users
users.index
users.create
Flight::resource('/users', UsersController::class, [ 'aliasBase' => 'user' ]);
You can also specify which routes you want to create by using the only and except options.
only
except
Flight::resource('/users', UsersController::class, [ 'only' => [ 'index', 'show' ] ]);
Flight::resource('/users', UsersController::class, [ 'except' => [ 'create', 'store', 'edit', 'update', 'destroy' ] ]);
These are basically whitelisting and blacklisting options so you can specify which routes you want to create.
You can also specify middleware to be run on each of the routes created by the resource method.
Flight::resource('/users', UsersController::class, [ 'middleware' => [ MyAuthMiddleware::class ] ]);
You can now stream responses to the client using the streamWithHeaders() method. This is useful for sending large files, long running processes, or generating large responses. Streaming a route is handled a little differently than a regular route.
streamWithHeaders()
Note: Streaming responses is only available if you have flight.v2.output_buffering set to false.
flight.v2.output_buffering
You can stream a response to the client by using the stream() method on a route. If you do this, you must set all the methods by hand before you output anything to the client. This is done with the header() php function or the Flight::response()->setRealHeader() method.
stream()
header()
Flight::response()->setRealHeader()
Flight::route('/@filename', function($filename) { // obviously you would sanitize the path and whatnot. $fileNameSafe = basename($filename); // If you have additional headers to set here after the route has executed // you must define them before anything is echoed out. // They must all be a raw call to the header() function or // a call to Flight::response()->setRealHeader() header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"'); // or Flight::response()->setRealHeader('Content-Disposition', 'attachment; filename="'.$fileNameSafe.'"'); $fileData = file_get_contents('/some/path/to/files/'.$fileNameSafe); // Error catching and whatnot if(empty($fileData)) { Flight::halt(404, 'File not found'); } // manually set the content length if you'd like header('Content-Length: '.filesize($filename)); // Stream the data to the client echo $fileData; // This is the magic line here })->stream();
You can also use the streamWithHeaders() method to set the headers before you start streaming.
Flight::route('/stream-users', function() { // you can add any additional headers you want here // you just must use header() or Flight::response()->setRealHeader() // however you pull your data, just as an example... $users_stmt = Flight::db()->query("SELECT id, first_name, last_name FROM users"); echo '{'; $user_count = count($users); while($user = $users_stmt->fetch(PDO::FETCH_ASSOC)) { echo json_encode($user); if(--$user_count > 0) { echo ','; } // This is required to send the data to the client ob_flush(); } echo '}'; // This is how you'll set the headers before you start streaming. })->streamWithHeaders([ 'Content-Type' => 'application/json', 'Content-Disposition' => 'attachment; filename="users.json"', // optional status code, defaults to 200 'status' => 200 ]);