<?php
namespace App\Services;
use App\Entity\AccountingFirm;
use App\Repository\AccountingFirmRepository;
use App\Repository\AliasRepository;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Service centralisé pour résoudre l'AccountingFirm à partir du host
* Évite la duplication des requêtes entre Voter et Controller
* Utilise un cache en mémoire pour la durée de la requête
*/
class AccountingFirmResolver
{
private ?AccountingFirm $cachedFirm = null;
private bool $resolved = false;
private ?string $cachedHost = null;
public function __construct(
private RequestStack $requestStack,
private AccountingFirmRepository $accountingFirmRepository,
private AliasRepository $aliasRepository
) {
}
/**
* Résout l'AccountingFirm à partir du host de la requête courante
* Utilise un cache en mémoire - ne fait les requêtes qu'une seule fois
*
* @return AccountingFirm|null
*/
public function resolve(): ?AccountingFirm
{
// Si déjà résolu pour ce host, retourner le cache
$currentHost = $this->getCurrentHost();
if ($this->resolved && $this->cachedHost === $currentHost) {
return $this->cachedFirm;
}
// Résolution avec EAGER loading de Parameters
$this->cachedHost = $currentHost;
$this->cachedFirm = $this->resolveAccountingFirm($currentHost);
$this->resolved = true;
return $this->cachedFirm;
}
/**
* Récupère le host depuis la requête courante
* Gère les routes de preview
*
* @return string
*/
private function getCurrentHost(): string
{
$request = $this->requestStack->getCurrentRequest();
if (!$request) {
return '';
}
$host = $request->getHost();
$host = str_replace('www.', '', $host);
// Gestion des routes de preview
if (preg_match('/preview/', $request->get('_route') ?? '')) {
$previewHost = $request->attributes->get('host');
if ($previewHost) {
$host = $previewHost;
}
}
return $host;
}
/**
* Résout l'AccountingFirm avec EAGER loading de Parameters
* Gère les alias si nécessaire
*
* @param string $host
* @return AccountingFirm|null
*/
private function resolveAccountingFirm(string $host): ?AccountingFirm
{
// Tentative de résolution directe avec EAGER loading de Parameters
$qb = $this->accountingFirmRepository->createQueryBuilder('a')
->select('a, p') // EAGER loading de Parameters
->leftJoin('a.parameters', 'p')
->where('a.host = :host')
->setParameter('host', $host);
$accountingFirm = $qb->getQuery()->getOneOrNullResult();
// Si trouvé, retourner directement
if ($accountingFirm !== null) {
return $accountingFirm;
}
// Sinon, chercher via un alias
$alias = $this->aliasRepository->findOneBy(['name' => $host]);
if ($alias === null) {
return null;
}
// Récupérer l'AccountingFirm via l'alias avec EAGER loading
$acId = $alias->getParameter()->getAccountingFirm()->getId();
$qb = $this->accountingFirmRepository->createQueryBuilder('a')
->select('a, p') // EAGER loading de Parameters
->leftJoin('a.parameters', 'p')
->where('a.id = :id')
->setParameter('id', $acId);
return $qb->getQuery()->getOneOrNullResult();
}
/**
* Retourne le host actuellement résolu (pour debug)
*
* @return string|null
*/
public function getCurrentResolvedHost(): ?string
{
return $this->cachedHost;
}
/**
* Force un reset du cache (utile pour les tests)
*/
public function reset(): void
{
$this->cachedFirm = null;
$this->resolved = false;
$this->cachedHost = null;
}
}