Linux SRV-16 6.18.44-paas #1 SMP PREEMPT_DYNAMIC Thu Aug 13 10:08:12 UTC 2026 x86_64
/
proc
/
212
/
root
/
proc
/
212
/
task
/
212
/
root
/
proc
/
70528
/
cwd
/
src
/
Service
/
//proc/212/root/proc/212/task/212/root/proc/70528/cwd/src/Service/MockupStateStorageService.php
<?php namespace App\Service; use Symfony\Component\HttpFoundation\File\File; /** * Conserve, pour chaque maquette générée, la description JSON utilisée et une * copie durable de ses fichiers sources (visuel du client, images générées par * l'IA). Une demande d'affinage repart ainsi de la maquette précédente au lieu * d'en produire une nouvelle depuis zéro. */ class MockupStateStorageService { /** Nom de fichier tel que produit par la génération : aucun chemin, aucune remontée de dossier. */ private const FILENAME_PATTERN = '/^mockup_\d+_[0-9a-f]{12}\.[a-z0-9]{2,4}$/'; public $mockupStorageDir; public function __construct(string $mockupStorageDir) { $this->mockupStorageDir = $mockupStorageDir; } /** * Enregistre l'état d'une maquette : description JSON, contexte de génération * et copie des fichiers sources, qui vivent sinon dans le dossier temporaire. */ public function save(string $mockupFilename, array $state, ?File $visualFile = null): void { $id = $this->identifier($mockupFilename); $sourceDir = $this->sourceDir($id); $this->createDirectory($sourceDir); if ($visualFile !== null) { $visualName = 'visual.'.($visualFile->guessExtension() ?: 'bin'); copy($visualFile->getPathname(), $sourceDir.'/'.$visualName); $state['visual_file'] = $visualName; } $state['layout'] = $this->storeGeneratedImages($state['layout'] ?? [], $sourceDir); file_put_contents($this->statePath($id), $this->encode($state)); } /** * Recharge l'état d'une maquette. Les chemins de fichiers sont stockés * relativement au dossier des sources et restitués en chemins absolus. * * @throws \InvalidArgumentException si la maquette n'a pas été générée par l'IA */ public function load(string $mockupFilename): array { $id = $this->identifier($mockupFilename); $statePath = $this->statePath($id); if (!is_file($statePath)) { throw new \InvalidArgumentException("Cette maquette ne peut pas être affinée : générez d'abord une maquette avec l'IA."); } $state = json_decode(file_get_contents($statePath), true); if (!is_array($state)) { throw new \RuntimeException("État de maquette illisible : ".$statePath); } $sourceDir = $this->sourceDir($id); $visualPath = isset($state['visual_file']) ? $sourceDir.'/'.$state['visual_file'] : null; $state['visual_path'] = ($visualPath !== null && is_file($visualPath)) ? $visualPath : null; $state['layout'] = $this->restoreGeneratedImages($state['layout'] ?? [], $sourceDir); return $state; } /** * Copie les images générées (fichiers temporaires) dans le dossier des * sources et remplace leur chemin absolu par un nom de fichier relatif. */ private function storeGeneratedImages(array $layout, string $sourceDir): array { foreach ($layout['pages'] ?? [] as $pageIndex => $page) { foreach ($page['elements'] ?? [] as $elementIndex => $element) { if (!is_array($element)) { continue; } $generatedPath = $element['generated_path'] ?? null; unset($layout['pages'][$pageIndex]['elements'][$elementIndex]['generated_path']); if (!$generatedPath || !is_file($generatedPath)) { continue; } $name = $this->fileName($element, $pageIndex, $elementIndex); copy($generatedPath, $sourceDir.'/'.$name); $layout['pages'][$pageIndex]['elements'][$elementIndex]['generated_file'] = $name; } } return $layout; } private function restoreGeneratedImages(array $layout, string $sourceDir): array { foreach ($layout['pages'] ?? [] as $pageIndex => $page) { foreach ($page['elements'] ?? [] as $elementIndex => $element) { if (!is_array($element) || empty($element['generated_file'])) { continue; } $path = $sourceDir.'/'.basename($element['generated_file']); if (is_file($path)) { $layout['pages'][$pageIndex]['elements'][$elementIndex]['generated_path'] = $path; } } } return $layout; } /** * Nom de fichier d'une image générée, dérivé de l'identifiant de l'élément. * L'identifiant peut provenir d'une réponse de l'IA : il est assaini. */ private function fileName(array $element, int $pageIndex, int $elementIndex): string { $id = preg_replace('/[^A-Za-z0-9_-]/', '', (string) ($element['id'] ?? '')); if ($id === '') { $id = sprintf('img_%d_%d', $pageIndex, $elementIndex); } return $id.'.png'; } /** * @throws \InvalidArgumentException si le nom de maquette ne vient pas de l'application */ private function identifier(string $mockupFilename): string { if (!preg_match(self::FILENAME_PATTERN, $mockupFilename)) { throw new \InvalidArgumentException("Maquette inconnue."); } return pathinfo($mockupFilename, PATHINFO_FILENAME); } private function statePath(string $id): string { $this->createDirectory($this->mockupStorageDir.'/state'); return $this->mockupStorageDir.'/state/'.$id.'.json'; } private function sourceDir(string $id): string { return $this->mockupStorageDir.'/sources/'.$id; } private function createDirectory(string $directory): void { if (!is_dir($directory) && !mkdir($directory, 0775, true) && !is_dir($directory)) { throw new \RuntimeException("Impossible de créer le dossier ".$directory); } } private function encode(array $state): string { return json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } }