context = []; } /** * 전역 변수 설정 */ public function setVariables(array $variables): void { $this->context['variables'] = $variables; } /** * 컨텍스트에 변수 추가 */ public function setVariable(string $key, mixed $value): void { data_set($this->context, $key, $value); } /** * 단계 결과 저장 */ public function setStepResult(string $stepId, array $extracted, array $fullResponse): void { $this->context['steps'][$stepId] = [ 'extracted' => $extracted, 'response' => $fullResponse, ]; // 편의를 위해 extracted 값을 stepId.key 형태로도 접근 가능하게 foreach ($extracted as $key => $value) { $this->context['steps'][$stepId][$key] = $value; } } /** * 문자열/배열 내 모든 변수 치환 */ public function bind(mixed $input): mixed { if (is_string($input)) { return $this->bindString($input); } if (is_array($input)) { return array_map(fn ($v) => $this->bind($v), $input); } return $input; } /** * 문자열 내 변수 패턴 치환 */ private function bindString(string $input): string { // 내장 변수 처리 $input = $this->resolveBuiltins($input); // 컨텍스트 변수 처리 return preg_replace_callback( '/\{\{([^}]+)\}\}/', fn ($matches) => $this->resolveReference($matches[1]), $input ); } /** * 내장 변수 처리 ($timestamp, $uuid, $random:N, $env.XXX, $auth.token) */ private function resolveBuiltins(string $input): string { // {{$timestamp}} → 현재 타임스탬프 $input = str_replace('{{$timestamp}}', (string) time(), $input); // {{$uuid}} → 랜덤 UUID $input = str_replace('{{$uuid}}', (string) Str::uuid(), $input); // {{$random:N}} → N자리 랜덤 숫자 $input = preg_replace_callback( '/\{\{\$random:(\d+)\}\}/', function ($m) { $digits = (int) $m[1]; $max = (int) pow(10, $digits) - 1; return str_pad((string) random_int(0, $max), $digits, '0', STR_PAD_LEFT); }, $input ); // {{$date}} → 현재 날짜 (Y-m-d) $input = str_replace('{{$date}}', date('Y-m-d'), $input); // {{$datetime}} → 현재 날짜시간 (Y-m-d H:i:s) $input = str_replace('{{$datetime}}', date('Y-m-d H:i:s'), $input); // {{$env.VAR_NAME}} → 환경변수에서 읽기 $input = preg_replace_callback( '/\{\{\$env\.([A-Z_][A-Z0-9_]*)\}\}/i', fn ($m) => env($m[1], ''), $input ); // {{$auth.token}} → 현재 로그인 사용자의 API 토큰 if (str_contains($input, '{{$auth.token}}')) { $token = $this->getAuthToken(); $input = str_replace('{{$auth.token}}', $token, $input); } // {{$auth.apiKey}} → .env의 API Key $input = str_replace('{{$auth.apiKey}}', env('FLOW_TESTER_API_KEY', ''), $input); return $input; } /** * 현재 로그인 사용자의 API 토큰 조회 */ private function getAuthToken(): string { $user = auth()->user(); if (! $user) { return env('FLOW_TESTER_API_TOKEN', ''); } // 사용자에게 저장된 API 토큰이 있으면 사용 if (! empty($user->api_token)) { return $user->api_token; } // 없으면 .env의 기본 토큰 사용 return env('FLOW_TESTER_API_TOKEN', ''); } /** * 참조 경로 해석 (step1.pageId 또는 page_create_1.pageId → 실제 값) */ private function resolveReference(string $path): string { $path = trim($path); // variables.xxx → $this->context['variables']['xxx'] if (str_starts_with($path, 'variables.')) { $value = data_get($this->context, $path, ''); return $this->valueToString($value); } // stepId.xxx 패턴 감지 (stepId는 등록된 step의 ID) // 첫 번째 점(.) 기준으로 분리 $dotPos = strpos($path, '.'); if ($dotPos !== false) { $potentialStepId = substr($path, 0, $dotPos); $subPath = substr($path, $dotPos + 1); // 등록된 step인지 확인 if (isset($this->context['steps'][$potentialStepId])) { // stepId.response.xxx → 전체 응답에서 추출 if (str_starts_with($subPath, 'response.')) { $responsePath = substr($subPath, 9); // "response." 제거 $value = data_get($this->context['steps'][$potentialStepId]['response'] ?? [], $responsePath, ''); return $this->valueToString($value); } // stepId.xxx → extracted 또는 직접 접근 $value = data_get($this->context['steps'][$potentialStepId] ?? [], $subPath, ''); return $this->valueToString($value); } } // 기타 경로는 context에서 직접 조회 $value = data_get($this->context, $path, ''); return $this->valueToString($value); } /** * 값을 문자열로 변환 (배열/객체는 JSON) */ private function valueToString(mixed $value): string { if (is_null($value)) { return ''; } if (is_bool($value)) { return $value ? 'true' : 'false'; } if (is_array($value) || is_object($value)) { return json_encode($value, JSON_UNESCAPED_UNICODE); } return (string) $value; } /** * 현재 컨텍스트 반환 (디버깅용) */ public function getContext(): array { return $this->context; } }