Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
ControllerContainerListener
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
3 / 3
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
 onKernelController
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 requiresAdminAuthentication
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3/**
4 * Controller container injection listener
5 *
6 * Injects the shared DI container into controllers that extend AbstractController.
7 * This replaces the per-controller container creation.
8 *
9 * This Source Code Form is subject to the terms of the Mozilla Public License,
10 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
11 * obtain one at https://mozilla.org/MPL/2.0/.
12 *
13 * @package   phpMyFAQ
14 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
15 * @copyright 2026 phpMyFAQ Team
16 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
17 * @link      https://www.phpmyfaq.de
18 * @since     2026-02-15
19 */
20
21declare(strict_types=1);
22
23namespace phpMyFAQ\EventListener;
24
25use phpMyFAQ\Controller\AbstractController;
26use phpMyFAQ\Controller\Administration\SkipsAuthenticationCheck;
27use Symfony\Component\DependencyInjection\ContainerInterface;
28use Symfony\Component\HttpKernel\Event\ControllerEvent;
29
30readonly class ControllerContainerListener
31{
32    private const string ADMIN_NAMESPACE_PREFIX = 'phpMyFAQ\\Controller\\Administration\\';
33
34    public function __construct(
35        private ContainerInterface $container,
36    ) {
37    }
38
39    public function onKernelController(ControllerEvent $event): void
40    {
41        $controller = $event->getController();
42
43        if (is_array($controller)) {
44            $controller = $controller[0];
45        }
46
47        if (!$controller instanceof AbstractController) {
48            return;
49        }
50
51        $controller->setContainer($this->container);
52
53        if ($this->requiresAdminAuthentication($controller)) {
54            $controller->userIsAuthenticated();
55        }
56    }
57
58    /**
59     * Defense-in-depth: every controller in the Administration namespace must
60     * have an authenticated user, except controllers that explicitly opt out by
61     * implementing SkipsAuthenticationCheck (e.g. AuthenticationController,
62     * which handles login/logout/token endpoints).
63     */
64    private function requiresAdminAuthentication(AbstractController $controller): bool
65    {
66        if ($controller instanceof SkipsAuthenticationCheck) {
67            return false;
68        }
69
70        return str_starts_with($controller::class, self::ADMIN_NAMESPACE_PREFIX);
71    }
72}