*/ class DoctrineCarrierRepository extends ServiceEntityRepository implements CarrierRepositoryInterface { public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Carrier::class); } public function findById(int $id): ?Carrier { return $this->find($id); } public function save(Carrier $carrier): void { $this->getEntityManager()->persist($carrier); $this->getEntityManager()->flush(); } public function createListQueryBuilder( bool $includeArchived = false, ?string $search = null, array $certificationTypes = [], ): QueryBuilder { // Fetch-join de la SEULE relation ManyToOne qualimatCarrier (sur, pas de // cartesien) pour exposer statut/date de validite QUALIMAT en liste sans // N+1 (§ 2.11). Aucune sous-collection (addresses/contacts/prices) jointe // en liste : elles ne sont embarquees qu'au detail (carrier:item:read). $qb = $this->createQueryBuilder('c') ->leftJoin('c.qualimatCarrier', 'q')->addSelect('q') ->andWhere('c.deletedAt IS NULL') ->orderBy('c.name', 'ASC') ; // Pas de cloisonnement par site (§ 2.3) : referentiel global. if (!$includeArchived) { $qb->andWhere('c.isArchived = false'); } $this->applySearch($qb, $search); $this->applyCertificationTypes($qb, $certificationTypes); return $qb; } /** * Recherche fuzzy insensible a la casse sur le nom du transporteur (§ 4.1). * Metacaracteres LIKE (%, _, \) echappes pour rester litteraux. */ private function applySearch(QueryBuilder $qb, ?string $search): void { if (null === $search || '' === trim($search)) { return; } $escaped = str_replace(['\\', '%', '_'], ['\\\\', '\%', '\_'], trim($search)); $pattern = '%'.mb_strtolower($escaped, 'UTF-8').'%'; $qb->andWhere('LOWER(c.name) LIKE :search') ->setParameter('search', $pattern) ; } /** * Restreint aux transporteurs dont la certification figure dans la liste (OR). * Alimente le filtre « Certification » de la liste (§ 4.1). * * @param list $certificationTypes */ private function applyCertificationTypes(QueryBuilder $qb, array $certificationTypes): void { $codes = []; foreach ($certificationTypes as $code) { if (is_string($code) && '' !== trim($code)) { $codes[] = trim($code); } } if ([] === $codes) { return; } $qb->andWhere('c.certificationType IN (:certificationTypes)') ->setParameter('certificationTypes', $codes) ; } }