$degree) { if ($degree === 0) { $queue[] = $id; } } $sorted = []; while (! empty($queue)) { $current = array_shift($queue); $sorted[] = $current; foreach ($graph[$current] as $neighbor) { $inDegree[$neighbor]--; if ($inDegree[$neighbor] === 0) { $queue[] = $neighbor; } } } if (count($sorted) !== count($steps)) { // 순환 의존성 발견 - 어떤 스텝들이 문제인지 파악 $remaining = array_diff(array_keys($graph), $sorted); throw new Exception('Circular dependency detected in steps: '.implode(', ', $remaining)); } return $sorted; } /** * 의존성 그래프 시각화 (디버깅용) * * @param array $steps 스텝 정의 배열 * @return array 의존성 정보 */ public function visualize(array $steps): array { $result = []; foreach ($steps as $step) { $id = $step['id']; $deps = $step['dependsOn'] ?? []; $result[$id] = [ 'name' => $step['name'] ?? $id, 'depends_on' => $deps, 'depended_by' => [], ]; } // 역방향 의존성 추가 foreach ($steps as $step) { $id = $step['id']; $deps = $step['dependsOn'] ?? []; foreach ($deps as $dep) { if (isset($result[$dep])) { $result[$dep]['depended_by'][] = $id; } } } return $result; } /** * 의존성 유효성 검사 * * @param array $steps 스텝 정의 배열 * @return array ['valid' => bool, 'errors' => array] */ public function validate(array $steps): array { $errors = []; $stepIds = array_column($steps, 'id'); // 중복 ID 체크 $duplicates = array_diff_assoc($stepIds, array_unique($stepIds)); if (! empty($duplicates)) { $errors[] = 'Duplicate step IDs found: '.implode(', ', array_unique($duplicates)); } // 존재하지 않는 의존성 체크 foreach ($steps as $step) { $id = $step['id']; $deps = $step['dependsOn'] ?? []; foreach ($deps as $dep) { if (! in_array($dep, $stepIds)) { $errors[] = "Step '{$id}' depends on unknown step '{$dep}'"; } // 자기 참조 체크 if ($dep === $id) { $errors[] = "Step '{$id}' cannot depend on itself"; } } } // 순환 의존성 체크 try { $this->resolve($steps); } catch (Exception $e) { $errors[] = $e->getMessage(); } return [ 'valid' => empty($errors), 'errors' => $errors, ]; } }