aucun cloisonnement * (no-op, aligne site-aware.md § 5). */ final class ProviderSiteScopeChecker { public function __construct( private readonly Security $security, private readonly CurrentSiteProviderInterface $currentSiteProvider, ) {} /** * Site de cloisonnement a appliquer, ou null si aucun cloisonnement * (`bypass_scope`, ou pas de site courant resolu). */ public function siteScopeOrNull(): ?SiteInterface { if ($this->security->isGranted('sites.bypass_scope')) { return null; } return $this->currentSiteProvider->get(); } /** * Vrai si le prestataire est dans le perimetre site de l'user courant — ou si * aucun cloisonnement ne s'applique. */ public function isInScope(Provider $provider): bool { $scopeSite = $this->siteScopeOrNull(); if (null === $scopeSite) { return true; } return $this->providerHasSite($provider, (int) $scopeSite->getId()); } /** * Leve un 404 si le prestataire est hors perimetre (anti-enumeration : ne pas * reveler l'existence d'une ligne hors site). No-op si dans le perimetre. */ public function assertInScope(Provider $provider): void { if (!$this->isInScope($provider)) { throw new NotFoundHttpException('Prestataire introuvable.'); } } /** * Vrai si le prestataire est rattache (relation directe provider.sites) au site * d'id donne. Comparaison en memoire sur l'entite deja chargee. */ private function providerHasSite(Provider $provider, int $siteId): bool { foreach ($provider->getSites() as $site) { if ($site instanceof SiteInterface && $site->getId() === $siteId) { return true; } } return false; } }