$params */ public function handle(Request $request, array $params): Response { $id = (string) ($params['id'] ?? ''); if (preg_match(self::ID_PATTERN, $id) !== 1) { return Response::json(404, ['error' => 'not found']); } $manifestPath = $this->scenariosDir . '/manifest.json'; $manifest = []; if (is_file($manifestPath)) { $decoded = json_decode((string) file_get_contents($manifestPath), true); if (is_array($decoded)) { $manifest = $decoded; } } if (!isset($manifest[$id])) { return Response::json(404, ['error' => 'not found']); } $fixturePath = $this->scenariosDir . '/' . $id . '.json'; $real = realpath($fixturePath); $realDir = realpath($this->scenariosDir); $insideBase = $real !== false && $realDir !== false && str_starts_with($real, $realDir . DIRECTORY_SEPARATOR); if ($insideBase === false || !is_file($real)) { return Response::json(404, ['error' => 'not found']); } $data = json_decode((string) file_get_contents($real), true, flags: JSON_THROW_ON_ERROR); if (!is_array($data)) { return Response::json(500, ['error' => 'fixture invalid', 'details' => ['Fixture JSON is not an object.']]); } try { $scenario = ScenarioSerializer::scenarioFromArray($data); } catch (InvalidArgumentException $exception) { return Response::json(500, ['error' => 'fixture invalid', 'details' => [$exception->getMessage()]]); } $errors = ScenarioValidator::validate($scenario); if ($errors !== []) { return Response::json(500, ['error' => 'fixture invalid', 'details' => $errors]); } return Response::json(200, ScenarioSerializer::scenarioToArray($scenario)); } }