Routing
In Lithe, you can define routes to control how your application responds to client requests using URIs (paths) and HTTP methods such as GET, POST, among others. Lithe offers flexibility and simplicity for defining and organizing your routes. Easily customize your application's behavior by connecting routes to specific functions or controllers to handle requests efficiently.
Defining Routes
In Lithe, you define how the application responds to requests for endpoints using URIs and HTTP methods like GET and POST. Methods such as get()
, post()
, and use()
are used to handle these requests and apply middleware. Each route can have one or more callback functions (handlers), which are executed when the route is matched. To pass control between functions, you need to use $next()
, allowing multiple handlers to be used in sequence.
To define routes in Lithe, use the following structure:
$app->METHOD($PATH, $HANDLER);
Where:
$app
is an instance of theLithe\App
class.METHOD
is the HTTP method (in lowercase) such asget
,post
, etc.$PATH
is the URL path that triggers the function.$HANDLER
is the function executed when the route is matched.
Here is a simple example of a route in Lithe:
$app = new \Lithe\App;
$app->get('/', function ($req, $res) {
return $res->send('Hello World!');
});
In this example, we define a route that responds with "Hello World!" when a GET request is made to the home page.
Route Methods
The route methods in Lithe are derived from HTTP methods and are attached to the instance of the App
class. You can define routes for different HTTP methods such as GET, POST, PUT, and DELETE. Here’s an example of how to define routes at the root of the application:
$app->get('/', function ($req, $res) {
return $res->send('Hello World!');
});
$app->post('/', function ($req, $res) {
return $res->send('Got a POST request');
});
$app->put('/user', function ($req, $res) {
return $res->send('Got a PUT request at /user');
});
$app->delete('/user', function ($req, $res) {
return $res->send('Got a DELETE request at /user');
});
Lithe allows the registration of routes for any HTTP verb:
$app->get($path, $handler);
$app->post($path, $handler);
$app->put($path, $handler);
$app->delete($path, $handler);
$app->patch($path, $handler);
$app->options($path, $handler);
$app->head($path, $handler);
If you need to register a route that responds to multiple HTTP verbs, use the match
method or the any
method to register a route that responds to all verbs.
$app->match(['get', 'post'], '/', function ($req, $res) {
// ...
});
$app->any('/', function ($req, $res) {
// ...
});
Defining Parameters in Routes
Route parameters are parts of the URL with specific names and are used to capture values. These captured values are placed in the params
property of a Request
instance, where each route parameter name becomes a key with its respective value.
Route path: /users/:userId/books/:bookId
Request URL: http://localhost:8000/users/34/books/8989
$req->params: { "userId": "34", "bookId": "8989" }
Simple Parameter
To define routes with route parameters, simply specify the route parameters in the route path as shown below.
$app->get('/users/:userId/books/:bookId', function ($req, $res) {
$res->send($req->params);
});
The names of route parameters should consist of “word characters” ([A-Za-z0-9_]).
Typed Parameters
Defining Typed Parameters
You can define typed parameters to ensure that values meet specific criteria.
$app->get('/user/:id=int', function ($req, $res) {
$res->send($req->param('id'));
});
In the example above, the id
parameter must be an integer (int
). If the received request does not match the route pattern constraints, an HTTP 404 response will be returned.
Parameter data types are automatically converted behind the scenes, ensuring that the parameter value is of the specified type.
|
Operator for Optional Types
Using the You can also use the |
operator to define optional types in route parameters, allowing for different types or formats.
$app->get('/user/:identifier=int|username', function ($req, $res) {
$res->send($req->param('identifier'));
});
In the example above, :identifier
can be an integer (int
) or a username that matches ([a-zA-Z0-9_]{3,16}
).
Parameters with Regular Expressions
To have more control over the exact string that can be matched by a route parameter, you can use regular expressions with the generic regex
type.
$app->get('/user/:name=regex<[a-z]+>', function ($req, $res) {
$res->send($req->param('name'));
});
In this example, the name
parameter must meet the regular expression [a-z]+
, which matches a sequence of lowercase letters.
This approach provides greater flexibility and control in defining routes, ensuring that parameters meet specific criteria.
Parameter Types
Here is the equivalence of parameter types listed with their corresponding regular expressions:
- int:
[0-9]+
(one or more numeric digits) - string:
[^/]+
(one or more characters that are not a slash/
) - uuid:
[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}
(UUID in standard format) - date:
\d{4}\-\d{1,2}\-\d{1,2}
(date in YYYY-MM-DD format) - email:
[^\s@]+@[^\s@]+\.[^\s@]+
(valid email address) - bool:
(false|true|0|1)
(boolean values: false, true, 0, or 1) - float:
[-+]?[0-9]*\.?[0-9]+
(floating-point number) - slug:
[a-z0-9]+(?:-[a-z0-9]+)*
(valid slug, used in URLs) - username:
[a-zA-Z0-9_]{3,16}
(username with letters, numbers, and_
, between 3 and 16 characters) - tel:
\+?[\d\-\(\)]+
(phone number, optionally starting with+
and containing digits, dashes, and parentheses) - alphanumeric:
[a-zA-Z0-9]+
(alphanumeric string, letters and numbers) - regex<Pattern>: Any valid regular expression you want to apply as a filter for the parameter. For example,
regex<(true|false)>
to accept onlytrue
orfalse
.
These regular expressions are used to validate and capture parameters in routes, ensuring that they match the expected criteria. If a parameter does not match the specified pattern for its type, it may result in a 404 error.
Route Handlers
You can provide multiple callback functions that act as middleware to handle a request. The only exception is that these callbacks can invoke $next()
to call the remaining route callbacks. You can use this mechanism to enforce preconditions on a route and then pass control to subsequent routes if there is no reason to continue with the current route.
Route handlers can be in the form of a function, a callable array, or combinations of both, as shown in the following examples.
A single callback function can handle a route. For example:
$app->get('/example/a', function ($req, $res) {
$res->send('Hello from A!');
});
More than one callback function can handle a route (be sure to specify $next
as the third parameter and call it to invoke the next function in the pipeline of middleware or controller). For example:
$app->get('/example/b', function ($req, $res, $next) {
error_log('The response will be sent by the next function ...');
$next();
}, function ($req, $res) {
$res->send('Hello from B!');
});
A callable array can handle a route. For example:
class UserController
{
public static function index($req, $res) {
return $res->view('users.index');
}
}
$app->get('/user', [UserController::class, 'index']);
A combination of independent functions and callable arrays can handle a route. For example:
$app->get('/user', function ($req, $res, $next) {
error_log('The response will be sent by the next function ...');
$next();
}, [UserController::class, 'index']);
Chainable Route Handlers
You can also create chainable route handlers for a route path using the route
method. Since the path is specified in a single place, creating modular routes is useful for reducing redundancies and typos. For more information on routes, refer to the Router class documentation.
Here is an example of chainable route handlers defined using the route
method:
$app->route('/book')
->get(function ($req, $res) {
$res->send('Get a random book');
})
->post(function ($req, $res) {
$res->send('Add a book');
})
->put(function ($req, $res) {
$res->send('Update the book');
});
Creating Modular Routers
The Router
class in Lithe allows you to create modular and mountable route handlers. This provides a complete middleware and routing system, often referred to as a "mini-application." Let’s explore how to build and integrate a router into your application in a clear and organized manner.
Router Structure
In this example, you will learn how to create a router as a separate module, configure middleware, define routes, and finally integrate the router module into your main application. This not only improves code organization but also facilitates maintenance and scalability.
One way to structure your router is by using a functional approach, which can be more intuitive in some situations.
- Each file is treated as a separate instance of the router, allowing you to organize your routes more intuitively.
- With modularity, it's easier to add new routers or modify existing ones without affecting other parts of the application.
- Functions like
get
andpost
are specific to that file instance, which helps in reading and maintaining the code.
Creating the Router File
Create a file named birds.php
in your application directory. This file will be responsible for defining routes related to birds.
<?php
use function Lithe\Orbis\Http\Router\{get, apply};
// Middleware specific to this router
$timeLog = function ($req, $res, $next) {
echo 'Time: ' . date('Y-m-d');
$next(); // Calls the next middleware or route
};
apply($timeLog); // Applies the middleware to the router
// Define the home route
get('/', function ($req, $res) {
$res->send('Birds homepage');
});
// Define the "About" route
get('/about', function ($req, $res) {
$res->send('About the birds');
});
- Here, we use
apply($timeLog)
to apply the middleware, keeping the syntax simple and clear.
Integrating the Router in the Main Application
To integrate the router into your application, use the router
function, which converts single files into routers ready to manage your routes simply and efficiently! Now, let's load the router module into the main application. Open the src/App.php
file and add the following code:
// src/App.php
use function Lithe\Orbis\Http\Router\{router};
// Load the router from the 'birds.php' file
$birds = router(__DIR__ . '/birds');
// Mount the router in the application at the '/birds' route
$app->use('/birds', $birds);
Now, your application will be ready to handle requests to /birds
and /birds/about
, in addition to invoking the $timeLog
middleware, which is specific to the router. A router can contain other routers, allowing for a hierarchical structure that better organizes the routes and functionalities of the application. Just like in the main application, routers are mounted in the same way, facilitating modularization and code maintenance. This promotes better scalability and clarity in organizing routes.
Organizing Your Routes in a Modular and Automatic Way
The set('routes', ...)
method simplifies route registration, making the code cleaner and more organized. With it, you only need to structure your route folders and files, and the system will automatically build them for you.
How It Works
When you use set('routes', ...)
, the system automatically locates and loads all PHP files within the specified folder (and subfolders). The routes are built based on the folder structure, and each file represents a route with its own path:
- If you have a file
cart.php
, it will create the route/cart
. - The file
admin/dashboard.php
will create the route/admin/dashboard
.
Within the routes directory, the index.php
file is always interpreted as the main route of a folder. For example, if you have an index.php
file in the routes directory, it will be mapped to the route /
, representing the root of the application.
However, when using subfolders like panel/index.php
, the system will not map it to the route /panel
, but rather to /panel/index
. To ensure that the route is correctly mapped to /panel
, you should name the file as panel.php
(without index.php
), like this:
index.php
→ maps to the route/
panel.php
→ maps to the route/panel
This approach helps avoid route conflicts and makes the file structure clearer and more intuitive.
Configuration
Directory Structure:
/routes
cart.php
checkout.php
/admin
dashboard.php
users.php
Defining Routes in the Files:
In each file, you can define your routes using the code style you prefer, either the functional syntax or the classic syntax.
File cart.php
:
get('/', function ($req, $res) {
$res->send('Cart');
});
File admin/dashboard.php
:
$router->get('/', function ($req, $res) {
$res->send('Admin Dashboard');
});
Application Configuration
To define the path for your routes and enable automatic loading, just add the following configuration in your application:
$app->set('routes', __DIR__ . '/routes'); // Defines the path and automatically loads routes
The directory structure makes it easier to visualize and maintain routes. Just organize your folders and files, and let the system manage the paths.