Repository: symfony/symfony
File: src/Symfony/Bundle/FrameworkBundle/Routing/AttributeRouteControllerLoader.php (PHP)

Code before:
namespace Symfony\Bundle\FrameworkBundle\Routing;

use Symfony\Component\Routing\Loader\AttributeClassLoader;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

/**
 * AttributeRouteControllerLoader is an implementation of AttributeClassLoader
 * that sets the '_controller' default based on the class and method names.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Alexandre Daubois <alex.daubois@gmail.com>
 */
class AttributeRouteControllerLoader extends AttributeClassLoader
{
    /**
     * @param class-string[] $taggedClasses
     */
    public function __construct(
        ?string $env = null,
        private array $taggedClasses = [],
    ) {
        parent::__construct($env);
    }

    public function load(mixed $class, ?string $type = null): RouteCollection
    {
        if (['tag' => 'routing.controller'] !== $class) {
            return parent::load($class, $type);
        }

        $collection = new RouteCollection();

        foreach ($this->taggedClasses as $class) {
            $collection->addCollection(parent::load($class, $type));
        }

        return $collection;
    }

    public function supports(mixed $resource, ?string $type = null): bool
    {
        return parent::supports($resource, $type) || 'attribute' === $type && ['tag' => 'routing.controller'] === $resource;
    }

    /**
     * Configures the _controller default parameter of a given Route instance.
     */
    protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $attr): void
    {
        if ('__invoke' === $method->getName()) {

Review comment (suggestion):
Instead of overriding the load method for a different kind of supported resource that needs to call the parent method after that, I would suggest creating a separate loader to load the tagged class (supporting _only_ the new kind of resource), which would then delegate the loading to the AttributeRouteControllerLoader (thanks to `$this->import()`).

Relying on composition rather than inheritance will give us more flexibility for the future. And it will also keep the code more understandable.

Code after:
namespace Symfony\Bundle\FrameworkBundle\Routing;

use Symfony\Component\Routing\Loader\AttributeClassLoader;
use Symfony\Component\Routing\Route;

/**
 * AttributeRouteControllerLoader is an implementation of AttributeClassLoader
 * that sets the '_controller' default based on the class and method names.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Alexandre Daubois <alex.daubois@gmail.com>
 */
class AttributeRouteControllerLoader extends AttributeClassLoader
{
    /**
     * Configures the _controller default parameter of a given Route instance.
     */
    protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $attr): void
    {
        if ('__invoke' === $method->getName()) {
            $route->setDefault('_controller', $class->getName());
        } else {
            $route->setDefault('_controller', $class->getName().'::'.$method->getName());
        }
    }

    /**
     * Makes the default route name more sane by removing common keywords.
     */
    protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method): string
    {
        $name = preg_replace('/(bundle|controller)_/', '_', parent::getDefaultRouteName($class, $method));

        if (str_ends_with($method->name, 'Action') || str_ends_with($method->name, '_action')) {
            $name = preg_replace('/action(_\d+)?$/', '\\1', $name);
        }

        return str_replace('__', '_', $name);
    }
}