Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
ContainerControllerResolver
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
2 / 2
7
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getController
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2
3/**
4 * Container-aware Controller Resolver
5 *
6 * Extends Symfony's ControllerResolver to check if a controller class is registered
7 * as a service in the DI container. If yes, returns the pre-configured instance with
8 * constructor dependencies resolved. If not, falls back to instantiating with `new`.
9 *
10 * This Source Code Form is subject to the terms of the Mozilla Public License,
11 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
12 * obtain one at https://mozilla.org/MPL/2.0/.
13 *
14 * @package   phpMyFAQ
15 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
16 * @copyright 2026 phpMyFAQ Team
17 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
18 * @link      https://www.phpmyfaq.de
19 * @since     2026-02-16
20 */
21
22declare(strict_types=1);
23
24namespace phpMyFAQ\Controller;
25
26use Override;
27use Symfony\Component\DependencyInjection\ContainerInterface;
28use Symfony\Component\HttpFoundation\Request;
29use Symfony\Component\HttpKernel\Controller\ControllerResolver;
30
31class ContainerControllerResolver extends ControllerResolver
32{
33    public function __construct(
34        private readonly ContainerInterface $container,
35    ) {
36        parent::__construct();
37    }
38
39    #[Override]
40    public function getController(Request $request): callable|false
41    {
42        $controllerAttr = $request->attributes->get('_controller');
43
44        // If the controller is in ClassName::method format and registered in the container,
45        // resolve it from the container BEFORE the parent tries to instantiate with `new`.
46        if (is_string($controllerAttr) && str_contains($controllerAttr, '::')) {
47            [$class, $method] = explode('::', $controllerAttr, limit: 2);
48            if (class_exists($class) && $this->container->has($class)) {
49                $instance = $this->container->get($class);
50                $controllerCallable = [$instance, $method];
51                if (is_callable($controllerCallable)) {
52                    /* @mago-expect analysis:less-specific-nested-return-statement - is_callable proves the array is a valid callable */
53                    return $controllerCallable;
54                }
55            }
56        }
57
58        // Fall back to default resolution for unregistered controllers
59        return parent::getController($request);
60    }
61}