addArgument('file', InputArgument::OPTIONAL, 'Chemin du fichier JSON', 'customer.json') ->addOption('force', null, InputOption::VALUE_NONE, 'Écrit réellement en base (sinon dry-run)') ->addOption('limit', null, InputOption::VALUE_REQUIRED, 'Ne traiter que les N premières entrées (debug)') ; } protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $write = (bool) $input->getOption('force'); $limit = null !== $input->getOption('limit') ? max(0, (int) $input->getOption('limit')) : null; $path = (string) $input->getArgument('file'); if (!str_starts_with($path, '/')) { $path = rtrim($this->projectDir, '/').'/'.$path; } if (!is_file($path) || !is_readable($path)) { $io->error(sprintf('Fichier introuvable ou illisible : %s', $path)); return Command::FAILURE; } $raw = file_get_contents($path); $decoded = json_decode((string) $raw, true); if (!is_array($decoded) || !isset($decoded['data']) || !is_array($decoded['data'])) { $io->error('JSON invalide : la clé "data" (tableau) est attendue.'); return Command::FAILURE; } /** @var array> $rows */ $rows = $decoded['data']; if (null !== $limit) { $rows = array_slice($rows, 0, $limit); } $io->title('Import fournisseurs'); $io->writeln(sprintf('Fichier : %s', $path)); $io->writeln(sprintf('Entrées : %d', count($rows))); $io->writeln($write ? 'Mode écriture (--force)' : 'Mode dry-run — aucune écriture. Ajouter --force pour appliquer.'); $io->newLine(); // --- Chargement des référentiels existants --------------------------------- /** @var array $constructeursByName */ $constructeursByName = []; foreach ($this->em->getRepository(Constructeur::class)->findAll() as $c) { $constructeursByName[$this->normalizeKey((string) $c->getName())] = $c; } /** @var array $categoriesByName */ $categoriesByName = []; foreach ($this->em->getRepository(ConstructeurCategorie::class)->findAll() as $cat) { $categoriesByName[$this->normalizeKey((string) $cat->getName())] = $cat; } // numéros et liens catégorie déjà présents, indexés par objet Constructeur $seenNumeros = new SplObjectStorage(); // Constructeur => array (clé = numéro normalisé) $seenCatLinks = new SplObjectStorage(); // Constructeur => array (clé = nom catégorie normalisé) // pré-remplissage pour les fournisseurs existants $existingTel = $this->em->getRepository(ConstructeurTelephone::class)->findAll(); foreach ($existingTel as $tel) { $owner = $tel->getConstructeur(); if (null === $owner) { continue; } $map = $seenNumeros[$owner] ?? []; $map[$this->normalizeKey((string) $tel->getNumero())] = true; $seenNumeros[$owner] = $map; } /** @var array $catLinkPairs */ $catLinkPairs = $this->em->createQuery( 'SELECT c.name AS cname, cat.name AS catname FROM '.Constructeur::class.' c JOIN c.categories cat' )->getArrayResult(); foreach ($catLinkPairs as $pair) { $cKey = $this->normalizeKey((string) $pair['cname']); $catKey = $this->normalizeKey((string) $pair['catname']); $owner = $constructeursByName[$cKey] ?? null; if (null === $owner) { continue; } $map = $seenCatLinks[$owner] ?? []; $map[$catKey] = true; $seenCatLinks[$owner] = $map; } // --- Traitement ------------------------------------------------------------ $created = 0; $matched = 0; $phonesAdded = 0; $categoriesCreated = 0; $catLinksAdded = 0; $skippedNoName = 0; $tooLong = []; $i = 0; foreach ($rows as $row) { ++$i; $name = trim((string) ($row['name'] ?? $row['reference'] ?? '')); if ('' === $name) { ++$skippedNoName; continue; } if (mb_strlen($name) > 255) { $tooLong[] = $name; $name = mb_substr($name, 0, 255); } $key = $this->normalizeKey($name); if (isset($constructeursByName[$key])) { $constructeur = $constructeursByName[$key]; ++$matched; } else { $constructeur = new Constructeur()->setName($name); if ($write) { $this->em->persist($constructeur); } $constructeursByName[$key] = $constructeur; ++$created; } // --- téléphones --- foreach ($this->splitPhones((string) ($row['phone'] ?? '')) as $numero) { $numero = mb_substr($numero, 0, 50); $nKey = $this->normalizeKey($numero); $map = $seenNumeros[$constructeur] ?? []; if (isset($map[$nKey])) { continue; } $tel = new ConstructeurTelephone()->setNumero($numero); $constructeur->addTelephone($tel); if ($write) { $this->em->persist($tel); } $map[$nKey] = true; $seenNumeros[$constructeur] = $map; ++$phonesAdded; } // --- catégories --- foreach ($this->splitCategories((string) ($row['categoriesStr'] ?? '')) as $catName) { $catName = mb_substr($catName, 0, 255); $catKey = $this->normalizeKey($catName); if (isset($categoriesByName[$catKey])) { $categorie = $categoriesByName[$catKey]; } else { $categorie = new ConstructeurCategorie()->setName($catName); if ($write) { $this->em->persist($categorie); } $categoriesByName[$catKey] = $categorie; ++$categoriesCreated; } $linkMap = $seenCatLinks[$constructeur] ?? []; if (isset($linkMap[$catKey])) { continue; } $constructeur->addCategory($categorie); $linkMap[$catKey] = true; $seenCatLinks[$constructeur] = $linkMap; ++$catLinksAdded; } if ($write && 0 === $i % 200) { $this->em->flush(); } } if ($write) { $this->em->flush(); } // --- Rapport --------------------------------------------------------------- $io->section('Résultat'); $io->table( ['Action', 'Nombre'], [ ['Fournisseurs créés', $created], ['Fournisseurs déjà en base (complétés si besoin)', $matched], ['Téléphones ajoutés', $phonesAdded], ['Catégories créées', $categoriesCreated], ['Liens fournisseur↔catégorie ajoutés', $catLinksAdded], ['Entrées ignorées (sans nom)', $skippedNoName], ['Noms tronqués (>255)', count($tooLong)], ] ); if ($tooLong) { $io->warning(sprintf('%d nom(s) dépassaient 255 caractères et ont été tronqués.', count($tooLong))); } if ($write) { $io->success('Import terminé.'); } else { $io->note('Dry-run : rien n\'a été écrit. Relancer avec --force pour appliquer.'); } return Command::SUCCESS; } /** * @return list */ private function splitPhones(string $value): array { $parts = preg_split('#[/;\n\r]+#', $value) ?: []; $out = []; foreach ($parts as $p) { $p = trim($p); if ('' !== $p) { $out[] = $p; } } return array_values(array_unique($out)); } /** * @return list */ private function splitCategories(string $value): array { $parts = explode(',', $value); $out = []; $seen = []; foreach ($parts as $p) { $p = trim($p); if ('' === $p) { continue; } $k = $this->normalizeKey($p); if (isset($seen[$k])) { continue; } $seen[$k] = true; $out[] = $p; } return $out; } private function normalizeKey(string $value): string { return mb_strtolower(trim(preg_replace('/\s+/u', ' ', $value) ?? $value)); } }