Router
A PHP Router that uses ReactPHP/HTTP under the hood
Install / Use
/learn @cmdstr-graveyard/RouterREADME
This is now abandonware!
If you still need a router in React/Http, checkout Tnapf/Router
~~CommandString/Router~~
A router package built that uses ReactPHP/HTTP under the hood
Table of Contents
- Getting Started
- Creating routes
- Patterns
- Controllers
- Middleware
- Dev Mode
- Responses
- Advanced Usage
- Nodemon
Installation
composer require commandstring/router
Getting started
You first need to create a ReactPHP SocketServer
$socket = new \React\Socket\SocketServer("127.0.0.1:8000");
Then create a router instance
$router = new \Router\Http\Router($socket, true);
The second parameter is whether dev mode should be enabled or not you can read about dev mode here
create some routes then begin listening for requests
$router->listen();
Routing
You can then add routes by using the match method
use Router\Http\Methods;
$router->match([Methods::GET], "/", function() { /* ... */ });
You can listen for more methods by adding them to the array
$router->match([Methods::GET, Methods::POST], "/", function() { /* ... */ });
Routing Shorthands
Shorthands for single request methods are provided
$router->get('pattern', function() { /* ... */ });
$router->post('pattern', function() { /* ... */ });
$router->put('pattern', function() { /* ... */ });
$router->delete('pattern', function() { /* ... */ });
$router->options('pattern', function() { /* ... */ });
$router->patch('pattern', function() { /* ... */ });
$router->head('pattern', function() { /* ... */ });
You can use this shorthand for a route that can be accessed using any method:
$router->all('pattern', function() { /* ... */ });
Route Patterns
Route Patterns can be static or dynamic:
- Static Route Patterns contain no dynamic parts and must match exactly against the
pathpart of the current URL. - Dynamic Route Patterns contain dynamic parts that can vary per request. The varying parts are named subpatterns and are defined using either Perl-compatible regular expressions (PCRE) or by using placeholders
Static Route Patterns
A static route pattern is a regular string representing a URI. It will be compared directly against the path part of the current URL.
Examples:
/about/contact
Usage Examples:
$router->get('/about', function($req, $res) {
$res->getBody()->write("Hello World");
return $res;
});
Dynamic PCRE-based Route Patterns
This type of Route Pattern contains dynamic parts which can vary per request. The varying parts are named subpatterns and are defined using regular expressions.
Examples:
/movies/(\d+)/profile/(\w+)
Commonly used PCRE-based subpatterns within Dynamic Route Patterns are:
\d+= One or more digits (0-9)\w+= One or more word characters (a-z 0-9 _)[a-z0-9_-]+= One or more word characters (a-z 0-9 _) and the dash (-).*= Any character (including/), zero or more[^/]+= Any character but/, one or more
Note: The PHP PCRE Cheat Sheet might come in handy.
The subpatterns defined in Dynamic PCRE-based Route Patterns are converted to parameters that are passed into the route handling function. The prerequisite is that these subpatterns need to be defined as parenthesized subpatterns, which means that they should be wrapped between parenthesis:
// Bad
$router->get('/hello/\w+', function($req, $res, $name) {
$res->getBody()->write('Hello '.htmlentities($name));
return $res;
});
// Good
$router->get('/hello/(\w+)', function($req, $res, $name) {
$res->getBody()->write('Hello '.htmlentities($name));
return $res;
});
Note: The leading / at the very beginning of a route pattern is not mandatory, but is recommended.
When multiple subpatterns are defined, the resulting route handling parameters are passed into the route handling function in the order they are defined:
$router->get('/movies/(\d+)/photos/(\d+)', function($req, $res, $movieId, $photoId) {
$res->getBody()->write('Movie #'.$movieId.', photo #'.$photoId);
return $res;
});
Dynamic Placeholder-based Route Patterns
This type of Route Pattern is the same as Dynamic PCRE-based Route Patterns, but with one difference: they don't use regexes to do the pattern matching but they use the more easy placeholders instead. Placeholders are strings surrounded by curly braces, e.g. {name}. You don't need to add parens around placeholders.
Examples:
/movies/{id}/profile/{username}
Placeholders are easier to use than PRCEs, but offer you less control as they internally get translated to a PRCE that matches any character (.*).
$router->get('/movies/{movieId}/photos/{photoId}', function($req, $res, $movieId, $photoId) {
$res->getBody()->write('Movie #'.$movieId.', photo #'.$photoId);
return $res;
});
Note: the name of the placeholder does not need to match the name of the parameter that is passed into the route handling function:
$router->get('/movies/{foo}/photos/{bar}', function($req, $res, $movieId, $photoId) {
$res->getBody()->write('Movie #'.$movieId.', photo #'.$photoId);
return $res;
});
Optional Route Subpatterns
Route subpatterns can be made optional by making the subpatterns optional by adding a ? after them. Think of blog URLs in the form of /blog(/year)(/month)(/day)(/slug):
$router->get(
'/blog(/\d+)?(/\d+)?(/\d+)?(/[a-z0-9_-]+)?',
function($req, $res, $year = null, $month = null, $day = null, $slug = null) {
if (!$year) {
$res->getBody()->write("Blog Overview");
return $res;
}
if (!$month) {
$res->getBody()->write("Blog year overview");
return $res;
}
if (!$day) {
$res->getBody()->write("Blog month overview");
return $res;
}
if (!$slug) {
$res->getBody()->write("Blog day overview");
return $res;
}
$res->getBody()->write('Blogpost ' . htmlentities($slug) . ' detail');
return $res;
}
);
The code snippet above responds to the URLs /blog, /blog/year, /blog/year/month, /blog/year/month/day, and /blog/year/month/day/slug.
Note: With optional parameters, it is important that the leading / of the subpatterns is put inside the subpattern itself. Don't forget to set default values for the optional parameters.
The code snipped above unfortunately also responds to URLs like /blog/foo and states that the overview needs to be shown - which is incorrect. Optional subpatterns can be made successive by extending the parenthesized subpatterns so that they contain the other optional subpatterns: The pattern should resemble /blog(/year(/month(/day(/slug)))) instead of the previous /blog(/year)(/month)(/day)(/slug):
$router->get('/blog(/\d+(/\d+(/\d+(/[a-z0-9_-]+)?)?)?)?', function($req, $res, $year = null, $month = null, $day = null, $slug = null) {
// ...
});
Note: It is highly recommended to always define successive optional parameters.
To make things complete use quantifiers to require the correct amount of numbers in the URL:
$router->get('/blog(/\d{4}(/\d{2}(/\d{2zz}(/[a-z0-9_-]+)?)?)?)?', function($req, $res, $year = null, $month = null, $day = null, $slug = null) {
// ...
});
Controllers
When defining a route you can either pass an anonymous function or an array that contains a class along with a static method to invoke. Additionally your controller must return an implementation of the PSR7 Response Interface
Anonymous Function Controller
$router->get("/home", function ($req, $res) {
$res->getBody()->write("Welcome home!");
return $res;
});
Class Controller
I have a class with a static method, your handler MUST be a static method!
class Home {
public static function handler($req, $res) {
$res->getBody()->write("Welcome home!");
return $res;
}
}
I then replace the anonymous function with an array the first item being the class string and the second key being the name of the static method.
$router->get("/home", [Home::class, "handler"]);
404 Handler
Defining a 404 handler is required and is similar to creating a route. You can also have different 404 pages for different patterns.
To setup a 404 handler you can invoke the map404 method and insert a pattern for the first parameter and then your controller as the second.
$router->map404("/(.*)", function ($req, $res) {
$res->getBody()->write("{$req->getRequestTarget()} is not a valid route");
return $res;
});
500 handler
Defining a 500 handler is recommended and is exactly the same as mapping a 404 handler.
$router->map500("/(.*)", function ($req, $res) {
$res->getBody()->write("An error has happened internally :(");
return $res;
});
Note that when in development mode your 500 error handler will be overrode
Middleware
Middleware is software that connects the model and view in an MVC application, facilitating the communication and data flow between these two components while also providing a layer of abstraction, decoupling the model and view and allowing them to interact without needing to know the details of how the other component operates.
A good example is having before middleware that makes sure the user is an administrator before they go to a restricted page. You could do this in your routes controller for every admin page but that would be redundant. Or for after middleware, you may have a REST API that returns a JSON response. You can have after m
