Logo

dev-resources.site

for different kinds of informations.

Decorate the Symfony router to add a trailing slash to all URLs

Published at
7/5/2024
Categories
symfony
routing
php
webdev
Author
chrisshennan
Categories
4 categories in total
symfony
open
routing
open
php
open
webdev
open
Author
12 person written this
chrisshennan
open
Decorate the Symfony router to add a trailing slash to all URLs

I recently noticed an issue between the links that Symfony generated for Password Angel and the actual links that are in use. When Symfony builds the URL there are no trailing slashes i.e. /terms, however, as Password Angel is hosted in an S3 bucket as a static site a trailing slash is part of the live URL i.e. /terms/. This causes 2 problems:-

  • Unnecessary redirections - All links in the page will refer to the link version without the trailing slash and then the user will need to be redirected to the version with the trailing slash.
  • The canonical URLs are invalid - As I'm using Symfony to generate the canonical URL for each page, it generated the link version without the trailing slash. This may cause SEO issues as search engines will

    • visit /terms
    • be redirected to /terms/
    • be informed the original page is at /terms
    • ... go to step 1 - infinite loop ...

Solution - Decorate the Symfony Router

To resolve this I created a decorator for the Symfony default router and have overridden the generate method to add a slash to the end of the URL. It also checks for the presence of ? which would indicate there are query string parameters and in this situation, I am inserting the / before the ? as we want /terms/?utm_campaign=... and not /terms?utm_campaign=.../.

<?php

declare(strict_types=1);

namespace App\Service;

use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Router;
use Symfony\Component\Routing\RouterInterface;

#[AsDecorator('router.default')]
class TrailingSlashUrlGenerator implements RouterInterface, WarmableInterface
{
    public function __construct(
        private readonly Router $urlGenerator,
    ) {}

    public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH): string
    {
        // Original URL
        $url = $this->urlGenerator->generate($name, $parameters, $referenceType);

        // Add the slash before any query string parameters
        $pos = strpos($url, '?');
        if ($pos !== false) {
            $parts = explode('?', $url, 2);
            if (str_ends_with($parts[0], '/') === false) {
                $parts[0] .= '/';
                return implode('?', $parts);
            }
        }

        // Add the slash at the end of the URL
        if (str_ends_with($url, '/') === false) {
            $url .= '/';
        }

        return $url;
    }

    public function match(string $pathinfo): array
    {
        return $this->urlGenerator->match($pathinfo);
    }

    public function getRouteCollection(): RouteCollection
    {
        return $this->urlGenerator->getRouteCollection();
    }

    public function setContext(RequestContext $context): void
    {
        $this->urlGenerator->setContext($context);
    }

    public function getContext(): RequestContext
    {
        return $this->urlGenerator->getContext();
    }

    public function warmUp(string $cacheDir, ?string $buildDir = null): array
    {
        return [];
    }
}
Enter fullscreen mode Exit fullscreen mode

Note: To host Password Angel as a static site on S3, I have written a Symfony command to generate static versions of all the pages (all 4 of them) and these are uploaded to S3. Let me know if you're interested and I'll post up how the Symfony command works.


Originally published at https://chrisshennan.com/blog/decorate-the-symfony-router-to-add-a-trailing-slash-to-all-urls

routing Article's
30 articles in total
Favicon
Mastering Nested Navigation in Flutter with `go_router` and a Bottom Nav Bar
Favicon
Understanding ShellRoute in go_router: Managing Shared Layouts Effectively
Favicon
How the Web Evolved: The Rise of Single Page Applications
Favicon
Introducing Humiris MoAI Basic : A New Way to Build Hybrid AI Models
Favicon
TanStack Router: The Future of React Routing in 2025
Favicon
Next.js: Dynamic Routing with API Integration.
Favicon
Learning Next.js 13 App Router: A Comprehensive Guide 🚀
Favicon
Organizing Your Routes Modularly and Automatically in Lithe
Favicon
Organizando Suas Rotas de Forma Modular e Automática no Lithe
Favicon
🗺️ Peta Jalan Laravel: Menjelajah Routing, Middleware, dan Controller (Indonesian Version)
Favicon
Working On a React / Python Flask Back End Web App.
Favicon
Vue.js for Beginners 2024 #VueJs Part 5 : A Complete Guide to Routing with Vue Router
Favicon
Mastering Routing Protocols with Cisco Packet Tracer: A Learning Experience
Favicon
Restful Routing - A Flask API Example
Favicon
Routing in React vs. Next.js: Extra Work or Easy Wins?
Favicon
Browser refresh on click of Home button using href
Favicon
Decorate the Symfony router to add a trailing slash to all URLs
Favicon
Routing in Umbraco Part 2: Content Finders
Favicon
Routing in Umbraco Part 1: URL segments
Favicon
Simplifying Network Administration: BGP Route Servers' Function in the Internet Ecosystem
Favicon
createBrowserRouter A step up from Switch
Favicon
Mastering Angular 17 Routing
Favicon
Snag the Current URL in SvelteKit (2024 Edition)
Favicon
Ensuring Type Safety in Next.js Routing
Favicon
Laravel 11: Health Check Routing Example
Favicon
Switch'in L2 mi L3 mü Olduğunun Öğrenilmesi
Favicon
Routing with React Router
Favicon
Routing implementation using PHP attributes
Favicon
What is Vinxi, and how does it compare to Vike?
Favicon
Fallback Routing with Axum

Featured ones: