*/ public readonly array $reseaux; /** @var array, preparation: ?string}> */ public readonly array $machines; /** @var array */ public readonly array $verifications; public static function depuis(string $identifiantLab): self { if (! preg_match('/^[a-z0-9][a-z0-9-]*$/', $identifiantLab)) { throw new RuntimeException("Identifiant de lab invalide : {$identifiantLab}"); } $chemin = config('labs.dossier')."/{$identifiantLab}/lab.yaml"; if (! File::exists($chemin)) { throw new RuntimeException("Lab introuvable : {$chemin}"); } return new self(Yaml::parseFile($chemin)); } /** * @return array */ public static function identifiantsDisponibles(): array { return collect(File::directories(config('labs.dossier'))) ->map(fn (string $dossier) => basename($dossier)) ->filter(fn (string $id) => File::exists(config('labs.dossier')."/{$id}/lab.yaml")) ->values() ->all(); } public function nombreMachines(): int { return count($this->machines); } /** * @param array $donnees */ private function __construct(array $donnees) { $this->identifiant = $donnees['id']; $this->titre = $donnees['titre']; $this->niveau = $donnees['niveau'] ?? null; $this->dureeMin = $donnees['duree_min'] ?? null; $this->reseaux = $donnees['reseaux']; $this->machines = array_map( fn (array $m) => [ 'nom' => $m['nom'], 'image' => $m['image'], 'flavor' => $m['flavor'], 'reseaux' => $m['reseaux'], 'preparation' => $m['preparation'] ?? null, ], $donnees['machines'], ); // Les commandes et le résultat attendu ne sont lus que par le // correcteur, sur l'hyperviseur : le portail n'en a pas besoin. $this->verifications = array_map( fn (array $v) => [ 'id' => $v['id'], 'titre' => $v['titre'], 'machine' => $v['machine'], 'points' => (int) ($v['points'] ?? 1), 'message_echec' => $v['message_echec'] ?? 'Vérification non réussie.', 'bonus' => str_starts_with($v['id'], 'bonus-'), ], $donnees['verifications'] ?? [], ); } }