DEV Community

Cover image for I made a php router with attributes
Simone Gentili
Simone Gentili

Posted on

I made a php router with attributes

Hi folks!!! I always ear php haters talk about a language I do not know. They do not know the language they are talking about. And to show them a little example of what php8 is able to do, I've create a simple exercice: a php router with php attributes.

Create public folder. I mean, .. a folder called "public". Then create an index.php file inside. And follow the steps in this article.

The attribute

First of all, ... you can create a php class Route. the attributes is simple the #[Attribute]. Attribute syntax starts from #[ and finisci with ]. You can "mark" a class, a method, a property, .... with attributes.

 #[Attribute]
 class Route
 {
     public function __construct(
         private string $method = 'GET',
         private string $path = '/',
     ) { }
 }
Enter fullscreen mode Exit fullscreen mode

A Response class

This is useless for the purpose of this post, but I've made a little video (in italian). And I've just copied and pasted the code here. This object aims to decorate the json for a generic response, with some links. It make easy website navigation at the end of the post.

class Response
{
    public function __construct(private array $json = []) { }

    public function getContent()
    {
        return json_encode(
            array_merge(
                $this->json,
                [
                    '@link' => [
                        'http://localhost:8888/',
                        'http://localhost:8888/info',
                        'http://localhost:8888/conclusions',
                    ]
                ]
            )
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Handler interface

In some framework we call them controllers. Here I just named them Handler. The point is that each route will call its own handler.

interface Handler {
    public function handle(): Response;
}
Enter fullscreen mode Exit fullscreen mode

The concrete handlers

Now you can create some handlers, and att an attribute to them. Note that someone known annotations. Annotations was just a DocBlock comment. PHP attributes are structured and readable by the interpreter. You can obtain a class attribute using reflection.

#[Route('GET', '/')]
class HomeContoller implements Handler
{
    public function handle(): Response
    {
        return new Response([
            'page' => 'home'
        ]);
    }
}

#[Route('GET', '/info')]
class InfoController implements Handler
{
    public function handle(): Response
    {
        return new Response([
            'page' => 'info'
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Configuration

I've also made a little configuration file: a list of handler, .. controller, .. call them as you like. Doesnt matter.

$routes = [
    HomeContoller::class,
    InfoController::class,
];
Enter fullscreen mode Exit fullscreen mode

Reading attributes

Here I've create a function. The function read an array of handlers/controllers. For each handlers/controllers get a reflection instance, get attributes from the instance. Attributes (Route) indicate the http method and the path. If the route corresponds to the handler one, then handle the handler.

function router(array $routes): Response {
    foreach($routes as $handler) {
        $reflection = new ReflectionClass($handler);
        $attributes = $reflection->getAttributes(Route::class);
        foreach($attributes as $attribute) {
            if ($attribute->getArguments()[0] === $_SERVER['REQUEST_METHOD']) {
                if ($attribute->getArguments()[1] === $_SERVER['REQUEST_URI']) {
                    return (new $handler)->handle();
                }
            }
        }
    }
}

$response = router($routes);

echo $response->getContent();
Enter fullscreen mode Exit fullscreen mode

Run built-in serve

Now You have your amazing router. You can try it. Just run

php -S localhost:8888 -t public

And see it working.

Conclusions

The conclusion is that php is amazing. A lot of people do not know it. all of them know php3, maybe. Once I heard someone say: "wow, .. php have also classes". So, .. LOL.

Well, .. add a new controller:

#[Route('GET', '/conclusions')]
class ConclusionsController implements Handler
{
    public function handle(): Response
    {
        return new Response([
            'message' => 'If you like this post, ... leave a like',
            'and' => 'and follow me for more post like this',
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Then update also the configuration:

$routes = [
    HomeContoller::class,
    InfoController::class,
    ConclusionsController::class,
];
Enter fullscreen mode Exit fullscreen mode

And if you speak italian you can see my video on youtub

Top comments (4)

Collapse
 
matheuseduardo profile image
Matheus Eduardo Silva Guimarães

Did you have a github repository with this sample?

Collapse
 
sensorario profile image
Simone Gentili • Edited

Yes!! You can see the code here. Is not on packagist. I've made it at exercice to start to show the way I like to apply tests in a legacy code. Actually the code you can see could be quite different because even if the related video is scheduled, .. the generated code is visible on github right now. In next videos I want also to show how to do a pull request and/or make issues. Feel free to contribute, .. or not ^_^

Collapse
 
yura712 profile image
Yura

Great article! Thank for pushing PHP forward!

Collapse
 
sensorario profile image
Simone Gentili

Have you seen my automatic dependency injection's article?