참조된 커스텀 키 목록 (중복 제거, 예: ['custom.home.1']) */ public function collectReferencedKeys(mixed $content): array { $keys = []; $this->walk($content, $keys); return array_values(array_unique($keys)); } /** * 레이아웃 이름을 키 네임스페이스로 정규화합니다 (커스텀 키 생성/매칭 SSoT). * * `board/list` → `board_list`. 슬래시/공백을 언더스코어로 치환하고 * 키에 안전한 문자만 남깁니다. 키 저장(TemplateCustomTranslationService::createKey)과 * 스캐너의 layoutKey 추출이 동일 정규화를 공유하도록 단일 SSoT 로 둡니다. * * @param string $layoutName 레이아웃 이름 * @return string 정규화된 키 네임스페이스 */ public static function normalizeLayoutKey(string $layoutName): string { $normalized = preg_replace('/[\/\s]+/', '_', trim($layoutName)); $normalized = preg_replace('/[^A-Za-z0-9_\-]/', '', (string) $normalized); return $normalized === '' ? 'layout' : $normalized; } /** * 값을 깊이 우선 순회하며 문자열에서 커스텀 키를 누적합니다. * * @param mixed $value 순회 대상 값 * @param array $keys 누적 키 (참조 전달) */ private function walk(mixed $value, array &$keys): void { if (is_string($value)) { if (str_contains($value, '$t:custom.') && preg_match_all(self::CUSTOM_KEY_RE, $value, $matches)) { foreach ($matches[1] as $key) { $keys[] = $key; } } return; } if (is_array($value)) { foreach ($value as $item) { $this->walk($item, $keys); } return; } if (is_object($value)) { foreach (get_object_vars($value) as $item) { $this->walk($item, $keys); } } } }