Skip to content

[Routing] Add RoutableInterface allowing objects to provide routing parameters #61038

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 7.4
Choose a base branch
from

Conversation

tjveldhuizen
Copy link
Contributor

Q A
Branch? 7.4
Bug fix? no
New feature? yes
Deprecations? no
Issues
License MIT

🚀 New Feature: Simplify Router Parameter Generation with Routable Objects

This pull request introduces the ability to pass objects directly as parameters to the Symfony Router, significantly simplifying the manual provision of complex parameter structures for routes. This improves code readability and reduces the chance of errors when generating URLs, especially with deeply nested or lengthy URL patterns.


💡 The Problem This Solves

In many Symfony applications, URL generation is a common task. For complex route patterns, such as /season/{season}/competition/{competition}/pools/{pool}/matches/{match}, developers currently have to manually collect all necessary parameters and pass them to the router. For example:

$this->router->generate('match_details', [
    'season' => $match->getPool()->getCompetition()->getSeason()->getSlug(),
    'competition' => $match->getPool()->getCompetition()->getSlug(),
    'pool' => $match->getPool()->getSlug(),
    'match' => $match->getSlug(),
]);

This quickly becomes cumbersome, error-prone, and reduces code readability, especially when routes require many parameters or when the objects containing the data are deeply nested.


✨ The Proposed Solution

This PR introduces a new concept where objects can define their own "router parameters" by implementing a specific interface. This allows the router to automatically extract the necessary parameters from the object, rather than requiring them to be manually specified.

Core Concepts:

  • RoutableInterface: A new interface that objects can implement. This interface defines a method (e.g., getRouterParameters()) that returns a RouterParameters object.
  • RouterParameters Value Object: A new value object that can hold a structured collection of parameters, including the ability to nest other RoutableInterface implementations.

Example Usage:

Let's say we have Pool and Match classes that implement the RoutableInterface:

class Pool implements RoutableInterface
{
    public function __construct(public readonly string $slug) {}

    public function getRouterParameters(): RouterParameters
    {
        return new RouterParameters(['pool' => $this->slug]);
    }
}

class Match implements RoutableInterface
{
    public function __construct(
        public readonly Pool $pool,
        public readonly string $slug,
    ) {}

    public function getRouterParameters(): RouterParameters
    {
        return (new RouterParameters([$this->pool, 'match' => $this->slug]))
            ->add('pool_details', [$this->pool]);
    }
}

With this implementation, URLs can be generated much more intuitively:

$match = new Match(new Pool('volley-championship'), 'final-match');

// Generate the URLs for the match: /pools/volley-championship/matches/final-match
$this->router->generate('match_details', [$match]);
$this->router->generate('match_edit', [$match]);

// Generate the URL for the related pool object: /pools/volley-championship
$this->router->generate('pool_details', [$match->pool]);
// Or directly via the match object:
$this->router->generate('pool_details', [$match]);

🌟 Benefits

  • Improved Code Readability: Router calls become shorter and clearer.
  • Less Error-Prone: Reduces the chance of typos or forgetting to provide parameters.
  • Better Maintainability: If the URL structure changes, you only need to adjust the getRouterParameters() method in the respective objects, instead of modifying all generate() calls.
  • Consistent URL Generation: Ensures that URLs for specific objects are always generated in the same way, regardless of the context.

🙏 Feedback Welcome!

This is my first feature pull request for Symfony, and I'm very eager to receive feedback. Any suggestions to improve the implementation or the proposal are greatly appreciated! I hope this feature can be valuable to the Symfony community.

@tjveldhuizen
Copy link
Contributor Author

Regarding Object Hydration for Router Parameters

One potential consideration with this approach is the hydration of related objects within Doctrine entities. When generating a route, the getRouterParameters() method on a RoutableInterface entity will likely cause any related objects needed for that route's parameters to be hydrated.

For simple cases, this might not be an issue. However, in scenarios with many configured routes or complex object graphs, this could potentially lead to unnecessary object hydration, impacting performance.

I've considered using closures or callables instead of direct parameter arrays to prevent this, for example:

// Instead of:
return new RouterParameters([$this->getPool(), 'match' => $this->getSlug()]);

// Something like:
return new RouterParameters(fn() => [$this->getPool(), 'match' => $this->getSlug()]);

While this could offer lazy loading, it does make the implementation of getRouterParameters() less straightforward and might introduce a less "clean" API from a user perspective.

If anyone has a more elegant or built-in Symfony/Doctrine-friendly way to prevent or mitigate this eager hydration, I'd be very grateful for your suggestions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy