How about Slim 4 route definitions as annotations?

Dear reader

Would it make sense to define the routes as annotations directly at the route callables? I am wondering what you think are the pros and cons to do that.

Because today I started to develop such a feature. Currently I can annotate a class as a route group using @RouteGroup and a method of the class as a route using @Route. Note that closures are not supported. So for example

/**
 * @RouteGroup("/user")
 */
class UserController
{
    /**
     * @Route("/{id}", name="userView")
     *
     * @param ServerRequestInterface $request
     * @param ResponseInterface $response
     * @param array $args
     * @return ResponseInterface
     */
    public function view(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
    {
        $response->getBody()->write('VIEW '.$args['id']);

        return $response;
    }

    /**
     * @Route(pattern="/add", methods={"POST"})
     *
     * @param ServerRequestInterface $request
     * @param ResponseInterface $response
     * @param array $args
     * @return ResponseInterface
     */
    public function add(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
    {
        $response->getBody()->write('Add User Called');

        return $response;
    }

    /**
     * @Route(pattern="/delete/{id}", methods={"delete"})
     *
     * @param ServerRequestInterface $request
     * @param ResponseInterface $response
     * @param array $args
     *
     * @return ResponseInterface
     */
    public function delete(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
    {
        $response->getBody()->write('DELETE');

        return $response;
    }
}

So this class actually defines a route group with the pattern /user containig three different routes.

It is even possible to define a route name as well as route middleware (and route group middleware as well), for example

    /**
     * @Route(
     *     "/",
     *     name="home",
     *     middleware={"App\TestRouteMiddleware"},
     * )
     */

Again, closures as middleware are not supported.

The tool provides a composer binary that can be run in order to “compile” those annotations into a proper json-file. The tool has also a route loader that can be used to load this json-file and add the routes and route groups to the app, for example

$routeLoader = new RouteLoader($app);
$routeLoader->loadFromJson(__DIR__.'/../config/routes.json');

The tool can be configured with mappings of namespaces to route groups. So it is possible for example to define the namespace App\Controllers\Api to be a route group with the pattern /api and all annotated routes that are in classes that are in that namespace would be part of that route group.

It is also possible to define a parent to any route group. The parent has to be another route group, that is either a class annotated as route group or a namespace defined as route group.

So, thanks to the parent property of the @RouteGroup annotation, the annotated routes in the following class would be part of the namespace route group App\Controllers\Api that had been configured to have the pattern /api.

/**
 * @RouteGroup("/article", parent="App\Controllers\Api")
 *
 * @package App\Controllers
 */
class ArticleController
{
    /**
     * @Route("/read", name="readArticle")
     *
     * @param ServerRequestInterface $serverRequest
     * @param ResponseInterface $response
     * @param array $args
     * @return ResponseInterface
     */
    public function read(
        ServerRequestInterface $serverRequest,
        ResponseInterface $response,
        array $args
    ): ResponseInterface {
        $response->getBody()->write('Read Article');

        return $response;
    }
}

Hence the path for the route readArticle would be /api/article/read.

What are your thoughts about that?

Oh, please no! I detest annotations for application frameworks. I like to keep my route definitions in one place, and for my actions to be framework agnostic. You need support from IDE extensions etc… I have never enjoyed working with annotations in my apps. A hard no from me

1 Like

Thanks for your feedback.

It might look like good idea - and certainly it is for fast prototyping

but things starts to be more complicated when you want to use real-world limitations:

  • allow only some methods per route (GET, POST,…)
  • adding different middlewares per route
  • grouping routes is really tricky with this
  • if you require “translated” variations for routes…

all that are common use-cases which are really difficult to solve with annotations
of course you can define somehow all of this in annotation’s - I can see how this might be done and how to use this

but you don’t want to define this things with annotations for application with more than few routes :slight_smile:

Hi @adriansuter :slight_smile:

IMHO: Code is code and comments are comments. This means: Annotations (DocBlocks) must never control the behavior of the application. There are of course some exceptions for unit tests, but test code is not part of the core application itself. Breaking a DocBlock could break a url (Tests would be helpful as well).

We need full support for static code analysis. We also need IDE and refactoring support (without additional plug-in and so on).

DocBlocks must be parsed (needs time) and maybe cached for better performance. I don’t like caching and routing issues just because of DocBlocks.

1 Like

Thanks. It is interesting - I thought exactly the opposite. For fast prototyping it is better not to have annotations and for large projects it would be better to leave the route definitions at the callables.

Because if you want to remove a route, for example, then without annotations you would need to remove the controller class (and maybe you need to remove the controller class also from the container definitions) and remove the entry wherever you add the route to the app.

1 Like

Hi @odan

Thanks for your valuable feedback. You are absolutely right!

1 Like

annotations cant engage logic to include/exclude routes, you are left with removing files or unloading etc
its way easier to just turn routes on and off

and odan is right, code is code and comments are comments they shouldnt mix