<?php
namespace App\EventSubscriber;
use App\Module\Attribute\RequiresModule;
use App\Service\ModuleAccessService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Egzekwuje wyłączanie modułów po stronie serwera.
*/
final readonly class ModuleAccessSubscriber implements EventSubscriberInterface
{
/**
* EasyAdmin podmienia wykonywany kontroler w AdminRouterSubscriber::onKernelController
* z priorytetem 128, przy wyższym priorytecie moduły nie działałyby
* dla żadnego okna EasyAdmina.
*/
public const PRIORITY = 0;
public function __construct(private ModuleAccessService $moduleAccess)
{
}
/**
* @return array<string, array{0: string, 1: int}>
*/
public static function getSubscribedEvents(): array
{
return [ControllerEvent::class => ['onKernelController', self::PRIORITY]];
}
public function onKernelController(ControllerEvent $event): void
{
$module = $this->resolveModule($event->getController());
if ($module !== null && !$this->moduleAccess->isEnabled($module)) {
throw new NotFoundHttpException(\sprintf('Moduł "%s" jest wyłączony na tej instalacji.', $module));
}
}
/**
* Atrybut z metody ma pierwszeństwo przed atrybutem z klasy.
*/
private function resolveModule(mixed $controller): ?string
{
if (\is_array($controller)) {
[$object, $method] = $controller;
} elseif (\is_object($controller) && \method_exists($controller, '__invoke')) {
$object = $controller;
$method = '__invoke';
} else {
return null;
}
if (!\is_object($object)) {
return null;
}
$reflection = new \ReflectionObject($object);
if ($reflection->hasMethod($method)) {
$attributes = $reflection->getMethod($method)->getAttributes(RequiresModule::class);
if ($attributes !== []) {
return $attributes[0]->newInstance()->module;
}
}
$attributes = $reflection->getAttributes(RequiresModule::class);
return $attributes === [] ? null : $attributes[0]->newInstance()->module;
}
}