- VariableBinder: 변수 바인딩 엔진 ({{...}} 패턴 처리)
- DependencyResolver: 의존성 정렬 (Topological Sort)
- ResponseValidator: HTTP 응답 검증 (JSONPath, 연산자)
- HttpClient: Laravel HTTP Client 래퍼
- FlowExecutor: 플로우 실행 엔진
193 lines
5.1 KiB
PHP
193 lines
5.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services\FlowTester;
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
/**
|
|
* 변수 바인딩 엔진
|
|
*
|
|
* {{...}} 패턴을 감지하고 실제 값으로 치환합니다.
|
|
* - {{variables.xxx}} - 전역 변수
|
|
* - {{stepN.xxx}} - 이전 단계 추출값
|
|
* - {{stepN.response.xxx}} - 이전 단계 전체 응답
|
|
* - {{$timestamp}} - 현재 타임스탬프
|
|
* - {{$uuid}} - 랜덤 UUID
|
|
* - {{$random:N}} - N자리 랜덤 숫자
|
|
*/
|
|
class VariableBinder
|
|
{
|
|
private array $context = [];
|
|
|
|
/**
|
|
* 컨텍스트 초기화
|
|
*/
|
|
public function reset(): void
|
|
{
|
|
$this->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)
|
|
*/
|
|
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);
|
|
|
|
return $input;
|
|
}
|
|
|
|
/**
|
|
* 참조 경로 해석 (step1.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);
|
|
}
|
|
|
|
// stepN.xxx → $this->context['steps']['stepN']['xxx']
|
|
if (preg_match('/^(step\w+)\.(.+)$/', $path, $m)) {
|
|
$stepId = $m[1];
|
|
$subPath = $m[2];
|
|
|
|
// stepN.response.xxx → 전체 응답에서 추출
|
|
if (str_starts_with($subPath, 'response.')) {
|
|
$responsePath = substr($subPath, 9); // "response." 제거
|
|
$value = data_get($this->context['steps'][$stepId]['response'] ?? [], $responsePath, '');
|
|
|
|
return $this->valueToString($value);
|
|
}
|
|
|
|
// stepN.xxx → extracted 또는 직접 접근
|
|
$value = data_get($this->context['steps'][$stepId] ?? [], $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;
|
|
}
|
|
}
|