Files
2026-05-11 11:29:41 +09:00

12 KiB

훅 패턴 검증 (validate-hook)

훅 시스템 및 작업계획서 내 훅 관련 코드 블록이 그누보드7 규정을 준수하는지 검증합니다.

0단계: 검증 대상 유형 판별 (CRITICAL)

⚠️ CRITICAL: 검증 대상이 소스코드인지 작업계획서인지 먼저 판별

판별 기준

파일 확장자 유형 검증 방식
*.php 소스코드 파일 전체를 훅/리스너 코드로 검증
*.md 작업계획서 마크다운 내 PHP 코드 블록 추출 후 검증

작업계획서 검증 시 추가 단계

작업계획서(.md) 파일인 경우:

  1. 코드 블록 추출: 마크다운에서 ```php 코드 블록 추출
  2. 훅 관련 코드 식별: HookManager, doAction, applyFilters, Listener 등의 키워드로 식별
  3. 규정 적용: hooks.md의 훅 시스템 규칙 적용
⚠️ 작업계획서 검증 시 주의사항:
- 코드 블록이 예시/설명 목적인지, 실제 구현 계획인지 구분
- "예시:", "Example:", "// 예시" 등이 포함된 코드는 참고용으로 처리
- 실제 구현 계획 코드만 엄격하게 검증

1단계: 규정 문서 읽기

다음 규정 문서를 읽어 최신 규칙을 확인합니다:

  • docs/extension/hooks.md - 훅 시스템 규칙
  • docs/backend/service-repository.md - Service에서의 훅 실행
  • docs/extension/plugin-development.md - 플러그인 격리 원칙

2단계: 검증 대상 파일 읽기

$ARGUMENTS 경로의 파일을 읽습니다.

경로가 지정되지 않은 경우, 다음 파일들을 대상으로 합니다:

  • **/Listeners/**/*.php - 훅 리스너
  • app/Services/**/*.php - 훅 실행 서비스
  • modules/_bundled/**/src/Services/**/*.php - _bundled 모듈 서비스
  • modules/_bundled/**/src/Listeners/**/*.php - _bundled 모듈 리스너
  • plugins/_bundled/**/src/Listeners/**/*.php - _bundled 플러그인 리스너

3단계: 규정 기반 자동 검증 (CRITICAL)

⚠️ CRITICAL: 1단계에서 읽은 규정 문서의 모든 규칙을 검증 항목으로 사용합니다.
스킬에 하드코딩된 검증 항목이 아닌, 규정 문서가 Single Source of Truth입니다.

3.1 규정에서 검증 패턴 추출

1단계에서 읽은 모든 규정 문서에서 다음 패턴을 추출합니다:

추출 대상 검증 유형
❌ 또는 잘못된 키워드가 포함된 코드 블록 금지 패턴 - 발견 시 에러
✅ 또는 올바른 키워드가 포함된 코드 블록 필수 패턴 - 누락 시 경고
⚠️ 절대 금지, CRITICAL 등 강조된 규칙 필수 검증 항목
TL;DR 섹션의 핵심 포인트 우선 검증 항목

3.2 검증 방법

금지 패턴 검증:

# 규정 문서의 ❌ 예시에서 추출한 패턴을 grep으로 검사
grep -rn "[금지 패턴]" [대상 파일/디렉토리]

발견 시: 에러로 보고하고 규정 문서의 해당 섹션 참조 안내

필수 패턴 검증:

  • 해당 컨텍스트에서 필수 패턴이 사용되어야 하는 경우, 누락 여부 확인
  • 누락 시: 경고로 보고하고 규정 문서의 올바른 예시 안내

3.3 훅 특화 검증

규정 문서의 훅 관련 섹션에서 추출한 규칙으로 다음을 검증합니다:

  1. 훅 네이밍: [vendor-module].[entity].[action]_[timing] 패턴 준수 여부
  2. 훅 실행 순서 (Service): before → filter → action → after 순서
  3. Listener 인터페이스: HookListenerInterface 구현 여부
  4. Filter 훅 type 필드: 'type' => 'filter' 명시 여부
  5. Filter 훅 리턴 값: 값 반환 여부

3.4 파일 유형별 규정 매핑

검증 대상 파일의 경로에 따라 해당하는 규정 문서를 우선적으로 적용합니다:

파일 경로 패턴 우선 적용 규정
**/Listeners/** hooks.md
**/Services/** service-repository.md, hooks.md

3.5 핵심 규칙 검증

규정 문서에 명시된 절대 규칙을 검증합니다.

3.5.1 _bundled 디렉토리 작업 원칙

⚠️ CRITICAL: 확장 수정/개발은 반드시 _bundled 디렉토리에서만 작업
→ 활성 디렉토리 직접 수정 절대 금지

검증 항목:

  • 수정 대상 파일이 _bundled/ 하위에 있는지 확인
  • 활성 디렉토리(modules/vendor-xxx/, plugins/vendor-xxx/)의 파일 수정 시 경고

3.5.2 플러그인 격리 원칙

⚠️ CRITICAL: 플러그인 간 직접 의존 금지 → 모듈에만 의존, 플러그인끼리는 API로만 통신
❌ 금지 ✅ 올바른 사용
플러그인 A → 플러그인 B 직접 참조 플러그인 A → 모듈 API → 플러그인 B
플러그인에서 다른 플러그인 클래스 import 훅/이벤트 또는 API 통신

검증 항목:

  • 플러그인 코드에서 다른 플러그인의 네임스페이스(Plugins\) 참조 여부 확인
  • 플러그인에서 모듈(Modules\) 의존만 허용

4단계: 결과 보고

검증 결과를 다음 형식으로 보고합니다:

## 훅 패턴 검증 결과

### 검증 파일
- [파일 경로]

### [규정 문서명] - [섹션명] 검증 결과

#### 금지 패턴 검사
- ❌ 금지 패턴 발견: [패턴] (파일:라인)
  - 규정: [규정 문서명] > [섹션명]
  - 수정 방법: [올바른 패턴으로 변경]
- ✅ 금지 패턴 없음

#### 필수 패턴 검사
- ❌ 필수 패턴 누락: [컨텍스트]에서 [패턴] 필요
  - 규정: [규정 문서명] > [섹션명]
- ✅ 필수 패턴 준수

#### 훅 네이밍 검증
- ✅ 올바름: [훅 이름]
- ❌ 잘못됨: [훅 이름] → [수정 제안]

#### Filter 훅 type 필드 검증
- ✅ type 명시됨: [훅 이름]
- ❌ type 누락: [훅 이름] → 'type' => 'filter' 추가 필요

5단계: 작업계획서 전용 검증 (마크다운 파일인 경우)

검증 대상이 작업계획서(.md)인 경우, 다음 추가 검증을 수행합니다.

5.1 훅 관련 코드 블록 식별

다음 키워드가 포함된 코드 블록을 훅 관련 코드로 식별:

키워드 용도
HookManager::doAction Action Hook 실행
HookManager::applyFilters Filter Hook 실행
implements HookListenerInterface 리스너 클래스 정의
getHooks() 훅 등록 정의
'type' => 'filter' Filter Hook 타입 명시

5.2 훅 네이밍 검증

// ✅ 올바른 패턴
HookManager::doAction('sirsoft-ecommerce.product.before_create', $data);
HookManager::doAction('sirsoft-ecommerce.product.after_create', $result);
$data = HookManager::applyFilters('sirsoft-ecommerce.product.filter_create_data', $data);

// ❌ 잘못된 패턴 - 네이밍 규칙 위반
HookManager::doAction('product.create', $data);  // ❌ vendor-module 누락
HookManager::doAction('beforeCreateProduct', $data);  // ❌ 잘못된 형식

네이밍 규칙: [vendor-module].[entity].[action]_[timing]

검증 항목:

  • ❌ vendor-module 접두사 누락
  • ❌ entity 누락
  • ❌ action_timing 형식 위반
  • ✅ 올바른 네이밍 패턴

5.3 훅 실행 순서 검증 (Service 코드)

// ✅ 올바른 순서: before → filter → action → after
public function create(array $data): Product
{
    // 1. before
    HookManager::doAction('sirsoft-ecommerce.product.before_create', $data);

    // 2. filter
    $data = HookManager::applyFilters('sirsoft-ecommerce.product.filter_create_data', $data);

    // 3. action
    $result = $this->productRepository->create($data);

    // 4. after
    HookManager::doAction('sirsoft-ecommerce.product.after_create', $result);

    return $result;
}

// ❌ 잘못된 순서
public function create(array $data): Product
{
    $result = $this->productRepository->create($data);  // ❌ 훅 없이 바로 실행
    HookManager::doAction('sirsoft-ecommerce.product.before_create', $data);  // ❌ 순서 잘못됨
    return $result;
}

5.4 Listener 정의 검증

// ✅ 올바른 패턴
class ProductCacheListener implements HookListenerInterface
{
    public function getHooks(): array
    {
        return [
            [
                'hook' => 'sirsoft-ecommerce.product.after_create',
                'method' => 'clearProductCache',
                'priority' => 10,
            ],
            [
                'hook' => 'sirsoft-ecommerce.product.filter_list_data',
                'method' => 'addCategoryInfo',
                'priority' => 10,
                'type' => 'filter',  // ✅ Filter Hook은 type 명시 필수
            ],
        ];
    }

    public function addCategoryInfo(array $data): array
    {
        // ... 데이터 변형
        return $data;  // ✅ Filter Hook은 반드시 값 반환
    }
}

// ❌ 잘못된 패턴 - Filter Hook인데 type 누락
[
    'hook' => 'sirsoft-ecommerce.product.filter_list_data',
    'method' => 'addCategoryInfo',
    'priority' => 10,
    // ❌ 'type' => 'filter' 누락
],

// ❌ 잘못된 패턴 - Filter Hook인데 반환값 없음
public function addCategoryInfo(array $data): void  // ❌ void 반환
{
    // ...
}

검증 항목:

  • ❌ HookListenerInterface 미구현
  • ❌ Filter Hook에 'type' => 'filter' 누락
  • ❌ Filter Hook 메서드가 값을 반환하지 않음
  • ✅ 올바른 리스너 구조

5.5 Filter Hook 반환값 검증

// ✅ 올바른 패턴
public function filterProductData(array $data): array
{
    $data['calculated_price'] = $data['price'] * 1.1;
    return $data;  // ✅ 반드시 반환
}

// ❌ 잘못된 패턴
public function filterProductData(array $data): void  // ❌ void
{
    $data['calculated_price'] = $data['price'] * 1.1;
    // 반환값 없음
}

5.6 작업계획서 검증 결과 보고 형식

## 작업계획서 훅 패턴 검증 결과

### 검증 파일
- [마크다운 파일 경로]

### 훅 코드 블록 요약
- 훅 실행 코드 블록: X개
- 리스너 정의 블록: Y개
- 총 검증 대상: Z개

### 훅 네이밍 검증
- ✅ 올바른 네이밍: [훅 이름]
- ❌ 잘못된 네이밍: [훅 이름] (코드 블록 위치)
  - 규칙: [vendor-module].[entity].[action]_[timing]
  - 수정 제안: [올바른 이름]

### 훅 실행 순서 검증
- ✅ before → filter → action → after 순서 준수
- ❌ 순서 위반: [위치] - [문제 설명]

### Listener 정의 검증
- ✅ HookListenerInterface 구현
- ❌ Interface 미구현: [위치]

### Filter Hook type 필드 검증
- ✅ type 명시됨: [훅 이름]
- ❌ type 누락: [훅 이름] (코드 블록 위치)
  - 수정: 'type' => 'filter' 추가 필요

### Filter Hook 반환값 검증
- ✅ 반환값 있음: [메서드명]
- ❌ 반환값 없음: [메서드명] (코드 블록 위치)
  - void 대신 array/mixed 반환 필요

핵심 원칙

⚠️ CRITICAL:
- 규정 문서가 Single Source of Truth - 스킬에 검증 항목 하드코딩 금지
- 규정 문서의 ❌/✅ 예시를 자동으로 검증 패턴으로 사용
- 규정이 변경되면 검증도 자동으로 변경됨
- 파일 유형에 따라 해당 규정 문서 우선 적용
- 작업계획서(.md) 검증 시 코드 블록 추출 후 동일한 규정 적용
- 모든 확장 작업은 _bundled 디렉토리에서만 수행 (활성 디렉토리 직접 수정 금지)
- 플러그인 간 직접 의존 금지 → 모듈에만 의존