Hi!
Not so long ago, Alexander B.K., a reader and author of dev.to, asked me to describe the design and creation of a PHP router. I decided that it would be interesting for me, especially since I already made my own implementation in php7+ and posted it on github. It is my reasoning and the code that I want to describe in the next a few articles. This is inspired by routers in popular frameworks, but nothing more.
I would like to have a router that will allow me to add paths and the data about the controller and action necessary to process them, as in the example below.
$router
->get('/api/users', UsersController::class, 'actionGetAll', 'json')
->get('/api/users/{id:int}', UsersController::class, 'actionGet', 'json');
First of all, let's design the interface of the future router in order to understand what end result we need to get. It will also help us write code knowing exactly what is required.
We need support for the main http types of requests, I propose to focus on the four most popular web applications and APIs when working: GET, POST, PUT, DELETE. We implement them in the form of four methods for each and a fifth for those requests that can work with any of them. Each of the methods will need information about the path that it will expect and process, the class and its action that the route will run, and also let's add the ability to set a specific format for the returned data.
So that the routes can be chained, as well as conveniently complete the processing of the request and return the data in response, I propose to make one more public output method, which should always be closing. Other approaches are possible, without this method, but I decided to focus on it, since this option seems to me the easiest to implement and understand.
interface RouterInterface
{
public function get(string $path, string $class, string $action, string $type): RouterInterface;
public function post(string $path, string $class, string $action, string $type): RouterInterface;
public function put(string $path, string $class, string $action, string $type): RouterInterface;
public function delete(string $path, string $class, string $action, string $type): RouterInterface;
public function all(string $path, string $class, string $action, string $type): RouterInterface;
public function output(): never;
}
It turned out quite a lot of incoming parameters for each method, besides, they are all identical. I propose to make a separate class that will control the route parameters. And thus, each method of our interface will only wait for one instance of this Request class.
class Request
{
public function __construct(
public readonly string $path,
public readonly ?Closure $func = null,
public readonly ?string $class = null,
public readonly ?string $action = null,
public readonly string $type = 'json',
public array $methods = []
) {}
}
interface RouterInterface
{
public function get(Request $request): RouterInterface;
public function post(Request $request): RouterInterface;
public function put(Request $request): RouterInterface;
public function delete(Request $request): RouterInterface;
public function all(Request $request): RouterInterface;
public function output(): never;
}
In my opinion, now it looks pretty cute and interesting. We have a convenient interface for the future router, as well as the Request class in which we can later add methods for working with input data.
Photo by Annie Spratt on Unsplash
Top comments (3)
I would strongly recommend having a look at PSR-15 HTTP Handlers and PSR-7 HTTP Message Interface for compatibility with foreign code such as other frameworks, rather than implementing an independent Request interface.
Thank you for your recommendation.
Great article! Thanks. Did you ever work with gRPC?