Middleware
Middlewares provide a convenient mechanism to inspect and filter incoming HTTP requests to your application. For example, Lithe includes a middleware that checks if the user of your application is authenticated. If the user is not authenticated, the middleware will redirect the user to the login screen of your application. However, if the user is authenticated, the middleware will allow the request to proceed further into the application.
Functioning
In Lithe, Middleware are functions that have access to the request object ($req
), response object ($res
), and the $next
function in the application's request-response cycle. The $next
function is a function in Lithe's router that, when invoked, executes the next middleware in line after the current middleware.
Middleware functions provide a convenient mechanism to inspect, filter, and manipulate incoming HTTP requests to your application.
Middleware functions can perform the following tasks:
- Execute any code.
- Make changes to the request and response objects.
- End the request-response cycle.
- Call the next middleware in the stack.
If the current middleware function does not end the request-response cycle, it must call $next()
to pass control to the next middleware function. Otherwise, the request will remain pending.
Elements of a Middleware Function
The following code shows the elements of a middleware function call:
$app->use(function ($req, $res, $next) {
$next();
});
Where:
$req
: HTTP request argument for the middleware function, conventionally called "$req".$res
: HTTP response argument for the middleware function, conventionally called "$res".$next
: Callback argument for the middleware function, conventionally called "$next".
Defining Middleware
Let's start with a simple example of middleware called myLogger
. This middleware prints the message LOGGED
every time a request passes through it. The middleware is defined as a function assigned to a variable called myLogger
:
$myLogger = function ($req, $res, $next) {
echo 'LOGGED';
$next();
};
Note the call to $next()
above. Calling this function invokes the next middleware function in the application. The $next()
function is not part of PHP or Lithe but is the third argument passed to the middleware function. The $next()
function could have any name, but by convention, it is always called "next". To avoid confusion, always use this convention.
Creating Middleware via Command Line
You can also create a new middleware using the make:middleware
command:
php line make:middleware EnsureTokenIsValid
This command will place a new class EnsureTokenIsValid
within your application's src/http/middleware
directory. In this middleware, we will only allow access to the route if the provided token matches a specified value. Otherwise, we will redirect users back to the initial URI:
use Lithe\Http\Request;
use Lithe\Http\Response;
class EnsureTokenIsValid {
public function __invoke(Request $req, Response $res, callable $next) {
$token = $req->input('token');
if ($token !== 'my-secret-token') {
return $res->redirect('home');
}
return $next();
}
}
As you can see, if the provided token
does not match our secret token, the middleware will return an HTTP redirect to the client; otherwise, the request will proceed further into the application. To pass the request deeper into the application (allowing the middleware to "pass"), you must call the callback $next
.
It is best to think of middleware as a series of "layers" that HTTP requests must traverse before reaching your application. Each layer can inspect the request and even reject it completely.
Loading Middleware
To load a middleware function, you can call the use()
method of the \Lithe\App
class, specifying the desired middleware function. For example, the following code loads the myLogger
middleware function before executing the route for the root path (/
):
$myLogger = function ($req, $res, $next) {
echo 'LOGGED';
$next();
};
$app->use($myLogger);
$app->get('/', function ($req, $res, $next) {
$res->send('Hello World!');
});
Whenever the application receives a request, it prints the message "LOGGED".
The order of loading middleware is important: middleware functions that are loaded first are also executed first.
The myLogger
middleware function simply prints a message and then passes the request to the next middleware function in the stack by calling the $next()
function.
Using Middleware
A Lithe application can use the following types of middleware:
Application-level Middleware
Bind application-level middleware to an instance of the application object using the use()
and METHOD()
methods, where METHOD
is the HTTP method of the request that the middleware function handles (such as GET, PUT, or POST) in lowercase.
The following example shows a middleware function without a mount path. The function is executed every time the application receives a request.
$app->use(function ($req, $res, $next) {
echo 'Hello World!';
$next();
});
The example below shows a middleware that handles GET requests at the path /user/:id
.
$app->get('/user/:id', function ($req, $res, $next) {
// If the user ID is '0', pass to the next middleware
if ($req->params->id === '0') {
return $next();
}
// Otherwise, send a specific response
$res->send('ID is not 0');
}, function ($req, $res) {
// Send a response when the ID is '0'
$res->send('regular');
});
Router-level Middleware
Router-level middleware works the same way as application-level middleware, except it is bound to an instance of \Lithe\Http\Router
.
$router = new \Lithe\Http\Router;
Load router-level middleware using the use
and METHOD
functions.
The following example code shows a middleware system using router-level middleware:
$router = new \Lithe\Http\Router();
// Router-level middleware for all routes in the router
$router->use(function ($req, $res, $next) {
echo 'Time: ', Date('H:i:s'), '<br>';
$next();
});
$router->get('/user/:id', function ($req, $res, $next) {
// Check if the user ID is '0'; if so, redirect to /
if ($req->params->id === '0') {
$res->redirect('/');
}
// Otherwise, pass control to the next middleware in the stack
$next();
}, function ($req, $res, $next) {
echo $req->params->id;
$res->render('special');
});
// Mount the router to the application
$app->use('/api', $router);
Third-party Middleware
Use third-party middleware to add functionalities to Lithe applications.
Install the necessary PHP module for the desired functionality and then load it into your application at the application or router level.
The following example illustrates loading session middleware, the function \Lithe\Middleware\Session\session
.
use function Lithe\Middleware\Session\session;
$app = new \Lithe\App;
$app->use(session([
'secure' => true
]));
For a partial list of commonly used third-party middleware functions with Lithe, see: Third-party Middleware.
Configurable Middleware
If you need your middleware to be configurable, export a function that accepts an array of options or other parameters, and then return the middleware implementation based on the input parameters.
function my_middleware($options) {
return function ($req, $res, $next) use ($options) {
// Middleware implementation based on the options array
// Call the next middleware
$next();
};
};
Now, the middleware can be used as shown below.
$app->use(my_middleware(['option1' => '1', 'option2' => '2']));