4min.

A Good Naming Convention for Routes, Controllers and Templates?

Cet article est aussi disponible en 🇫🇷 Français : Une bonne convention de nommage pour les routes, les contrôleurs et les templates ?.

I’ve had my fair share of web projects during my career, and with experience building up, I’m increasingly pushing for coding standards, conventions, and naming precision in my code reviews.

A badly named service, a variable giving no clue, a namespace that makes no sense, multiple names for the same concept: it all makes reading code harder and demands a greater cognitive effort than I can afford 🤣 That’s called visual debt.

Today, I want to write about the naming involved in a standard route/controller/template web framework (examples will be Symfony but this applies to Laravel, Django, etc).

Consider this:

  1. We need to display a list of products on /products;
  2. We need to display one product on /product/{sku};
  3. We use an MVC framework.

What names should we use for:

  • the route name (the string we can use to generate URL to our pages);
  • the controller and action classes and methods;
  • the template (or view) files.

Frameworks let us chose, and when we leave something to appreciation, it can become messy very fast.

Section intitulée the-mess-we-are-allowed-to-makeThe Mess we are Allowed to Make 💩

I’ve seen everything, let’s look at one BAD example:

<?php

class AppController extends AbstractController
{
    #[Route('/products', name: 'all_products')]
    public function listProducts(): Response
    {
        return $this->render('product/list.html.twig', [
            'products' => [],
        ]);
    }
}

The App controller has a all_products route, attached to a listProducts action, rendering a product/list.html.twig template – my eyes are bleeding.

Another naming I see really often is to have one template folder per action, like this: listProducts/index.html.twig. We have so many index.html.twig that we never search our templates by names anymore, and if multiple controllers have the same actions names… well… You can also struggle in your code editor with all the opened files having the same name (thanks to PHPStorm for being clever about that, it adds the folder name to the tab if you open two index.html.twig).

Section intitulée a-better-namingA Better Naming 🙏

What I suggest now in my projects is to have a consistent naming like this:

  • The controller is named according to the business, it can be namespaced, and generally only consist of singular nouns and is PascalCase 🐫;
  • The action is named according to what it do, a verb in camelCase 🐫 – we also try to use consistent names across different controllers;
  • The route name is a concatenation of the controller name and the action name, transformed in snake_case 🐍;
  • The templates folder is the controller name in snake_case 🐍;
  • The template name is the action name in snake_case​ 🐍.
<?php

class ProductController extends AbstractController
{
    #[Route('/products', name: 'product_list')]
    public function list(): Response
    {
        return $this->render('product/list.html.twig', [
            'products' => [],
        ]);
    }

    #[Route('/product/{sku}', name: 'product_show')]
    public function show(string $sku): Response
    {
        return $this->render('product/show.html.twig', [
            'product' => $product,
        ]);
    }

    #[Route('/product/{sku}/add', name: 'product_add_to_cart')]
    public function addToCart(string $sku): Response
    {
        return $this->render('product/add_to_cart.html.twig', [
            'product' => $product,
        ]);
    }
}

That way, when I’m looking at templates, I know in which controller they are used. Also, when I’m looking at a route name (from APM or logs, or even in a template), I know instantly what controller they invoke.

Section intitulée using-the-action-name-as-template-nameUsing the Action Name as Template Name?

I’m using snake_case 🐍 for my templates to follow Symfony recommendations but the more I think about it, the more I want to get rid of these transformations between the camelCase 🐫 of the action and the snake_case 🐍 of the template. So this can be simpler / easier to use:

#[Route('/product/{sku}/add', name: 'product_add_to_cart')]
public function addToCart(string $sku): Response
{
    return $this->render('product/addToCart.html.twig', [
        'product' => $product,
    ]);
}

What do you think?

Section intitulée fancy-route-namesFancy Route Names?

Another transformation we do is on the route name. From Product and addToCart, we create product_add_to_cart.

But most frameworks allow any string as route name. Maybe we could use the Product_addToCart?

I don’t like it visually but hey, maybe it’s simpler to write!

Section intitulée using-invoke-adr-patternUsing __invoke() / ADR pattern

In the Symfony community, this pattern is quite common. And a fun side effect is that the route name can be self::class directly (the fully qualified class name of the controller):

<?php

#[Route('/product/{sku}', name: self::class)]
class ProductShow
{
    public function __invoke(string $sku, Environment $twig): Response
    {
        return new Response(
            $twig->render('productShow/index.html.twig', ['product' => $product,])
        );
    }
}

This allows to generate urls like this, which is quite nice:

$url = $this->urlGenerator->generate(ProductShow::class);

Section intitulée what-about-youWhat about you?

I need to know if we can improve / find a good convention. I’m not saying this one is the perfect one, any convention is good if it’s applied everywhere and enforced.

But let’s say one convention makes sense to developers, we could even add it to static analysis tools – an extension for phpstan or Psalm for example, could look at your route names / actions / templates and yell at you if you are mistaken 😍

Please let me know, do you have a convention? What are you using? Are you happy with it? Let me know!

Next time should we talk about Command aliases versus class name and namespace convention? 😀

Commentaires et discussions