회원가입 | 고객센터 |
DESIGNONEX
dxcms.kr
로그인 회원가입
고객센터
3.7 Hook 시스템

실행 타이밍

D DX
2026.04.21 00:59(수정됨) 135 0

1. 전체 실행 흐름 개요

DXCMS의 모든 요청은 index.php 단일 진입점을 통해 처리됩니다. 요청이 들어온 순간부터 응답이 완료될 때까지 5단계를 거치며, 각 단계마다 훅 포인트가 배치되어 있습니다.

핵심 원칙

  • 훅은 "CMS가 특정 작업을 완료한 직후" 또는 "특정 작업을 시작하기 직전"에 실행됩니다.
  • 훅 등록(dx_add_hook)은 STEP 4 load_plugins() 시점에 완료되어야 합니다.
  • 훅 실행(dx_run_hook)은 반드시 등록 이후에 이루어집니다. 등록 전 실행은 무시됩니다.


1.1 5단계 실행 구조 (index.php 기준)


1. STEP 1 — 클래스•함수 로드

functions.php, DxCache, Secure, Database, HookManager, PluginRegistry, Auth, DxExtend, Dispatcher 등 모든 클래스 파일을 require_once로 로드합니다. 이 단계는 정의만 하며 실행은 없습니다.


2. STEP 2 — 보안 초기화

세션 설정 → 세션 시작 → 보안 헤더 발행 → CSRF 토큰 발급 순서로 실행됩니다. 훅 없음.


3. STEP 3 — DB 연결 + 설정 로드

data/config.php를 실행해 DB 연결, dx_config 설정, DX_SECRET_KEY 주입을 완료합니다. 훅 없음.


4. STEP 4 — 초기화 + 훅 등록

HookManager, PluginRegistry, load_plugins(), DxSite, DxTheme, Auth 순서로 초기화. 플러그인이 여기서 dx_add_hook을 실행합니다.
dx_body_bottom (팝업)  dx_after_login (회원모니터)  dx_after_logout (회원모니터)


5. STEP 5 — 라우팅 + 디스패치 + 렌더링

routes/ 로드 → DxRouter → Dispatcher::dispatch() 순서. extend/top → 라우팅 → extend/middle → 핸들러 → 렌더링 → extend/bottom.


1.2 extend/ 폴더와 Hook의 실행 시점 비교

실행 방식 실행 시점 (index.php 기준) 주요 용도
extend/top/ STEP 4 완료 직후 (플러그인•Auth•DxSite•DxTheme 모두 완료) 점검 모드, IP 차단, 전역 변수 주입
extend/middle/ Dispatcher::dispatch() 내부 라우트 확정 직후, 핸들러 실행 전    방문자 로그(01_visit_tracker.php), 접근 제어
extend/bottom/ Dispatcher 완료 직후 ob_end_flush() 직전 캐시 저장, 성능 로그, 정리 작업
dx_head 훅 테마 main.php의 </head> 바로 앞 CSS•JS•메타태그 삽입
dx_top 훅 테마 main.php의 <body> 시작 직후 헤더 네비게이션 아래 공지 배너, 팝업 트리거
dx_middle 훅 테마 main.php의 <main> 태그 안 실제 콘텐츠 바로 앞 사이드 위젯, 보조 콘텐츠
dx_bottom 훅 테마 main.php의 </footer> 직후 스크립트 블록 앞 플로팅 버튼, 하단 위젯
dx_footer_scripts 훅 테마 main.php의 모든 JS 다음 추가 스크립트 태그 삽입
dx_body_bottom 훅 맨 마지막 (dx_footer_scripts 바로 다음) 팝업 렌더링, 전역 JS 실행


2. index.php 단계별 실행 타이밍


2.1 STEP 4 — 초기화 완료 후 훅 등록

STEP 4는 훅 시스템이 활성화되는 핵심 단계입니다. load_plugins() 호출 시 plugins/ 폴더의 plugin.php 파일들이 실행되며, 각 플러그인이 dx_add_hook으로 콜백을 등록합니다.
 
 
HookManager::getInstance();     // 훅 매니저 싱글턴 생성
PluginRegistry::getInstance();  // 플러그인 레지스트리 생성
load_plugins();                 // ← plugins/*/plugin.php 실행
                                //   이 시점에 dx_add_hook이 모두 등록됨
_dx_register_point_hooks();    // 포인트 시스템 훅 등록
 
DxSite::getInstance();          // 멀티사이트 도메인 설정 적용
DxTheme::getInstance();         // 테마 결정 (DxSite 이후)
Auth::getInstance();            // 세션 기반 인증 초기화
DxContainer::getInstance()->registerCoreServices();
 
// CMS가 직접 등록하는 훅들
dx_add_hook('dx_body_bottom', function() {
    DxPopup::render();           // 팝업 자동 출력 (priority 50)
}, 50);
 
dx_add_hook('dx_after_login', function($args) {
    DxMemberMonitor::onLogin((int)$args["user"]["id"]);
}, 20);
 
// extend/top/ 실행 — 모든 초기화 완료 직후
DxExtend::getInstance()->runTop(array(
    'version' => DX_VERSION,
    'path'    => dx_request_uri(),
));

주의: 훅 등록 가능 최초 시점
dx_add_hook()은 HookManager가 생성된 이후부터 호출 가능합니다.
STEP 1의 함수 파일 로드 시점에는 훅을 등록할 수 없습니다 (HookManager 미생성).
플러그인의 plugin.php는 load_plugins() 내부에서 include 되므로 STEP 4가 안전한 등록 시점입니다.


2.2 STEP 5 — 라우팅과 extend/middle 실행

// index.php STEP 5 — Dispatcher 실행 전후
 
// ① routes/ 폴더 자동 로드 (DxRouter 기반 라우트)
$_dxRouteFiles = glob(DX_ROOT . "/routes/*.php");
foreach ($_dxRouteFiles as $f) require_once $f;
 
// ② 라우팅 + 디스패치
if (!DxRouter::dispatch()) {
    $dispatcher = new Dispatcher(new Router());
    $dispatcher->dispatch();
    // dispatch() 내부에서:
    //   1) Router::resolve() — 라우트 확정
    //   2) extend/middle/ 실행  ← 이 시점에 $GLOBALS["dx_route"] 사용 가능
    //   3) switch(type) — 핸들러 실행
}
 
// ③ 렌더링 완료 후 extend/bottom/ 실행
DxExtend::getInstance()->runBottom(array(
    'elapsed' => round((microtime(true) - DX_START) * 1000, 2),
));
 
// ④ 출력 버퍼 flush
if (ob_get_level() > 0) ob_end_flush();


3. 테마 레이아웃 훅 실행 타이밍

테마의 themes/default/layout/main.php가 렌더링될 때 6개의 훅이 HTML 구조 내 특정 위치에서 실행됩니다. 각 훅이 HTML의 어느 위치에 있는지 정확히 파악해야 올바른 훅을 선택할 수 있습니다.


3.1 main.php HTML 구조와 훅 위치

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>...</title>
  <style>...</style>   <!-- CMS 기본 스타일 -->
 
  ★ [훅 1] dx_head  ← 289번 줄 (</head> 바로 앞)
  <!-- Google Analytics, 웹마스터 메타태그 추가 위치 -->
  <script src="jquery.min.js"></script>
</head>
 
<body>
  <!-- 헤더 네비게이션, 드로어 메뉴 -->
 
  ★ [훅 2] dx_top  ← 649번 줄 (헤더 아래, 상단 유틸바 위)
 
  <div class="dx-topbar">  <!-- 상단 유틸바 --></div>
  <header>...</header>     <!-- GNB 메뉴 --></header>
 
  <main id="dx-main">
    ★ [훅 3] dx_middle  ← 1135번 줄 (콘텐츠 바로 앞)
 
    <!-- 실제 페이지 콘텐츠 ($dx_content) -->
    <?php echo $dx_content; ?>
  </main>
 
  <footer>...</footer>
 
  ★ [훅 4] dx_bottom  ← 1854번 줄 (</footer> 직후)
 
  <script>/* CMS 기본 JS 코드 */</script>
 
  ★ [훅 5] dx_footer_scripts  ← 2129번 줄
  ★ [훅 6] dx_body_bottom     ← 2130번 줄
</body>
</html>


3.2 레이아웃 훅 상세 설명

훅 이름 HTML 문맥 주요 용도
dx_head <head> 내부 </head> 바로 앞 외부 CSS 파일 링크 구글 폰트 로드 favicon 추가 커스텀 메타태그
dx_top <body> 내부 헤더 메뉴 아래 전체 페이지 공지 배너 점검 안내 띠 A/B 테스트 overlay
dx_middle <main> 내부 $dx_content 앞 콘텐츠 앞 보조 안내 사이드 패널 맞춤형 위젯
dx_bottom </footer> 직후 <script> 블록 앞 플로팅 버튼(채팅, 상단이동) 하단 위젯 쿠키 동의 배너
dx_footer_scripts CMS JS 다음 </body> 직전 추가 <script> 태그 외부 라이브러리 로드
dx_body_bottom 맨 마지막 (dx_footer_scripts 다음) 팝업 렌더링(priority 50) 전역 JS 초기화 디버그 도구


3.3 dx_top의 세분화 — 페이지 타입별 자동 훅

dx_hook_top(), dx_hook_middle(), dx_hook_bottom() 함수는 context의 type과 slug 값에 따라 추가 훅을 자동으로 실행합니다. 이를 통해 특정 페이지 타입에서만 실행되는 훅을 만들 수 있습니다.
 
// HookManager.php의 dx_hook_top() 내부 구현
function dx_hook_top($context = array()) {
    dx_run_hook('dx_top', $context);            // ① 전체 페이지 공통
 
    if (isset($context['type'])) {
        dx_run_hook('dx_' . $context['type'] . '_top', $context);
        // 예: type='board' → dx_board_top
        // 예: type='page' → dx_page_top
        // 예: type='admin' → dx_admin_top  ← 관리자 전용!
    }
 
    if (isset($context['slug'])) {
        dx_run_hook('dx_page_' . $context['slug'] . '_top', $context);
        // 예: slug='notice' → dx_page_notice_top
        // 예: slug='free'   → dx_page_free_top
    }
}
 
// ─────────────────────────────────────────────
// 실제 활용 예시: 게시판에서만 실행
dx_add_hook('dx_board_top', function($context) {
    echo '<div class="board-notice">게시판 이용 규칙...</div>';
});
 
// notice 게시판 목록에서만 실행
dx_add_hook('dx_page_notice_top', function($context) {
    echo '<div class="notice-banner">공지사항 배너</div>';
});


4. 게시판 핸들러 훅 실행 타이밍

게시판 요청(boards/handler.php)은 자체적인 훅 포인트를 가집니다. 핸들러 진입부터 렌더링 완료까지 총 8개의 훅 포인트가 배치되어 있습니다.


4.1 boards/handler.php 실행 흐름

// boards/handler.php 실행 순서 (실제 소스 기반)
 
// ─── 공통 진입 훅 ─────────────────────────────────
dx_run_hook('dx_board_before', array(
    'board'  => $board,
    'action' => $action,   // list | view | write | edit | reply | delete
    'skin'   => $boardSkin,
    'id'     => $id,
));
 
// ─── 액션별 분기 (switch) ─────────────────────────
switch ($action) {
 
  case 'list':
    // 목록 컨텍스트 준비 후:
    dx_run_hook('dx_board_list_context',
        array('context' => &$ctx, 'board' => $board));  // 참조 전달
    // 렌더링...
    break;
 
  case 'view':
    // 상세 컨텍스트 준비 후:
    dx_run_hook('dx_board_view_context',
        array('context' => &$ctx, 'board' => $board, 'post' => $post));
    // 렌더링...
    break;
 
  case 'write':
  case 'edit':
    dx_run_hook('dx_board_write_context',
        array('context' => &$ctx, 'board' => $board));
    // 저장 처리 시:
    dx_run_hook('dx_board_before_save',
        array('data' => &$data, 'board' => $board, 'action' => $action));
    // DB 저장 완료 후:
    dx_run_hook('dx_after_write',
        array('post_id' => $postId, 'board' => $board, 'data' => $data));
    dx_run_hook('dx_board_after_save',
        array('post_id' => $postId, 'board' => $board, 'redirect' => &$redirect));
    break;
 
  case 'delete':
    dx_run_hook('dx_board_before_delete',
        array('post' => $post, 'board' => $board));
    // 삭제 처리...
    dx_run_hook('dx_board_after_delete',
        array('post_id' => $id, 'board' => $board));
    break;
}
 
// ─── 공통 종료 훅 ─────────────────────────────────
dx_run_hook('dx_board_after', array(
    'board'  => $board,
    'action' => $action,
    'skin'   => $boardSkin,
));


4.2 게시판 훅 타이밍 표

훅 이름 실행 시점 인자 특이사항
dx_board_before 핸들러 진입 즉시 모든 처리 전 일반 전달. 여기서 exit()하면 게시판 전체 차단 가능
dx_board_list_context 목록 데이터 조회 완료 렌더링 직전 context를 참조(&)로 전달. 콘텐츠를 변형하면 목록에 반영됨
dx_board_view_context 게시글 조회 완료 렌더링 직전 context, post 참조 전달
dx_board_write_context 작성 폼 렌더링 직전 context 참조 전달
dx_board_before_save DB 저장 직전 (write•edit•reply 공통) data를 참조(&)로 전달. 값을 수정하면 실제 저장 데이터에 반영됨
dx_after_write DB 저장 완료 직후 포인트, 알림, 통계 처리에 사용
dx_board_after_save dx_after_write 바로 다음 redirect를 참조로 전달. 저장 후 이동 URL 변경 가능
dx_board_before_delete 삭제 처리 직전 연관 파일•댓글 정리에 사용
dx_board_after_delete 삭제 완료 직후 캐시 무효화, 통계 갱신에 사용
dx_board_after 모든 처리 완료 handler.php 맨 끝 로그, 정리 작업


5. 인증•커뮤니티 훅 실행 타이밍


5.1 로그인 관련 훅

// core/auth/Auth.php 내부 — login() 메서드
 
// DB에서 사용자 조회 → 비밀번호 검증 → 세션 저장 완료 후:
dx_run_hook('dx_after_login', array('user' => $user));
// ↑ 이 훅이 실행되는 시점에는 세션에 사용자 정보가 이미 저장되어 있음
// ↑ Auth::getInstance()->isLoggedIn() === true
 
// core/auth/Auth.php 내부 — logout() 메서드
// 세션에서 사용자 정보를 제거하기 전:
dx_run_hook('dx_after_logout', array('user' => $user));
// ↑ 이 훅 실행 시점에는 아직 세션이 살아있음
 
// core/auth/Auth.php 내부 — register() 메서드
// DB에 회원 INSERT 완료 후:
dx_run_hook('dx_after_register', array(
    'user_id' => $id,
    'data'    => $safeData,  // 저장된 회원 데이터
));


5.2 커뮤니티 활동 훅 타이밍

훅 이름 위치 파일 실행 시점
dx_after_comment core/api/comment.php DB에 댓글 INSERT 완료 직후 알림 발송, 포인트 지급에 활용
dx_after_like core/api/like.php 좋아요 DB 처리 완료 직후 게시물 소유자에게 알림 발송
dx_add_friend core/DxFriend.php 친구 관계 DB 저장 완료 직후
dx_after_point core/DxPoint.php 포인트 지급/차감 DB 저장 후 레벨업 체크 전
dx_levelup core/DxPoint.php 레벨업 조건 충족 확인 후 DB 레벨 업데이트 완료 직후
dx_shop_after_purchase core/DxShop.php 결제 완료 + 주문 DB 저장 후 디지털 상품 배포에 활용


6. 훅 등록과 실행의 순서 관계


6.1 등록 → 실행 타이밍 원칙

핵심 규칙
dx_add_hook()은 반드시 dx_run_hook() 호출 이전에 실행되어야 합니다.
STEP 4의 load_plugins()에서 plugin.php가 실행되므로, 플러그인의 dx_add_hook은 STEP 5보다 항상 앞에 있습니다.
예외: extend/top/ 파일에서 dx_add_hook을 등록하면, 해당 파일은 load_plugins() 이후에 실행되므로 테마 훅(dx_head 등)에 등록 가능합니다.


6.2 훅 등록 가능 위치 정리

등록 위치 실행 시점 등록 가능한 훅 범위
plugins/*/plugin.php STEP 4 — load_plugins() 모든 훅 (가장 권장)
extend/top/*.php STEP 4 완료 직후 테마 훅(dx_head, dx_top ...) 포함 모든 훅
extend/middle/*.php Dispatcher::dispatch() 내부 dx_middle, dx_bottom 등 렌더링 훅만
extend/bottom/*.php 렌더링 완료 후 훅 등록 불가 (이미 모든 훅 실행 완료)
data/config.php STEP 3 HookManager 미생성 → 훅 등록 불가


6.3 등록 타이밍 오류 예시

// ❌ 잘못된 예: extend/bottom에서 dx_top 훅 등록
// extend/bottom/bad_hook.php
dx_add_hook('dx_top', function($ctx) {
    echo '<div>이 코드는 절대 실행되지 않습니다</div>';
});
// → dx_top은 렌더링 중(STEP 5)에 이미 실행됨
// → extend/bottom은 렌더링 완료 후 실행됨
// → 등록 시점이 실행 시점보다 늦어 콜백이 무시됨
 
// ✅ 올바른 예: plugin.php에서 dx_top 훅 등록
// plugins/my-plugin/plugin.php
dx_add_hook('dx_top', function($ctx) {
    echo '<div>이 코드는 정상 실행됩니다</div>';
});
// → plugin.php는 STEP 4에서 실행됨
// → dx_top은 STEP 5 렌더링 중에 실행됨
// → 등록이 실행보다 먼저 완료됨 → 정상 작동


7. 전체 훅 실행 순서 다이어그램

아래는 HTTP 요청이 들어온 순간부터 응답이 완료될 때까지 모든 훅이 실행되는 순서를 나타낸 다이어그램입니다.
 
HTTP 요청 수신
  ↓
STEP 1: 클래스•함수 로드 (실행 없음)
functions.php → DxCache → Secure → Database →
HookManager → PluginRegistry → Auth → DxExtend → ...
  ↓
STEP 2: 보안 초기화 (세션, 보안 헤더, CSRF)
  ↓
STEP 3: DB 연결 + data/config.php 실행
  ↓
STEP 4: 초기화 + 훅 등록
load_plugins()  ← 플러그인 dx_add_hook 등록 완료
DxSite → DxTheme → Auth
CMS 내부 dx_add_hook 등록
★ extend/top/ 실행  ← 최초 extend 실행 지점
  ↓
STEP 5: 라우팅 + 디스패치

Router::resolve() → 라우트 확정
★ extend/middle/ 실행  ← dx_route 확정 직후

switch(type) → 핸들러 실행
├── 게시판: dx_board_before
│           dx_board_{action}_context
│           dx_board_before_save (저장 시)
│           dx_after_write / dx_board_after_save
│           dx_board_after
└── 기타: 페이지, 관리자, API 처리...

테마 layout/main.php 렌더링
├── <head> 안  → ★ dx_head
├── 헤더 아래  → ★ dx_top (+ dx_{type}_top)
├── <main> 안  → ★ dx_middle (+ dx_{type}_middle)
├── 푸터 아래  → ★ dx_bottom (+ dx_{type}_bottom)
├── 스크립트 후 → ★ dx_footer_scripts
└── 맨 마지막  → ★ dx_body_bottom (팝업 포함)

★ extend/bottom/ 실행  ← 렌더링 완료 후
  ↓
ob_end_flush() → HTTP 응답 전송 완료
(register_shutdown_function 콜백 실행)
★ visit_logs INSERT ← 응답 후 비동기 처리


8. 실전 타이밍 선택 가이드

목적에 맞는 훅을 빠르게 선택하기 위한 의사결정 가이드입니다.


8.1 목적별 훅 선택

하고 싶은 것 선택할 훅 이유
모든 페이지 <head>에 CSS 추가 dx_head HTML head 내부에서 실행됨
모든 페이지 상단에 배너 표시 dx_top 헤더 아래, 콘텐츠 위에서 실행됨
게시판 목록 상단에만 안내 추가 dx_board_top type=board일 때만 실행됨
notice 게시판에만 추가 UI dx_page_notice_top slug=notice일 때만 실행됨
페이지 하단 플로팅 버튼 dx_bottom 푸터 아래, 스크립트 앞에서 실행됨
외부 JS 라이브러리 추가 dx_footer_scripts 모든 CMS JS 로드 후 실행됨
팝업 / 전역 초기화 JS dx_body_bottom 맨 마지막 실행 (DOM 완성 후)
로그인 후 포인트 지급 dx_after_login 세션 저장 완료 후 실행됨
글 저장 전 내용 검사•수정 dx_board_before_save data 참조(&)로 전달, 수정 반영됨
글 저장 후 알림 발송 dx_after_write DB 저장 완료 후 실행됨
점검 모드 구현 extend/top/ 모든 초기화 완료 후 가장 먼저 실행
방문자 IP 로깅 extend/middle/ 라우트 확정 후, 응답 후 비동기 처리


8.2 타이밍 오류 체크리스트

  • 훅이 실행되지 않는다면: plugin.php에서 dx_add_hook을 등록했는지 확인 (config.php나 extend/bottom은 불가)
  • 레이아웃 훅(dx_head 등)이 실행되지 않는다면: API, 단독 페이지(is_standalone) 요청인지 확인 — 이 경우 main.php가 로드되지 않음
  • dx_board_before_save에서 데이터 수정이 반영 안 된다면: &$args['data'] 참조로 받아야 함
  • dx_after_login 훅이 실행 안 된다면: 소셜 로그인 콜백(_callback) 파일에서 Auth::login()이 호출되는지 확인
  • extend/middle에서 등록한 훅이 실행 안 된다면: 이미 지나간 훅에 등록한 것 — plugin.php로 이동 필요


최종 요약

훅 등록은 STEP 4 load_plugins()에서 완료되어야 합니다. plugin.php가 가장 안전합니다.
레이아웃 훅(dx_head~dx_body_bottom)은 테마 main.php 렌더링 중에 실행됩니다.
extend/top/은 모든 초기화 완료 후 라우팅 전, extend/middle/은 라우트 확정 후, extend/bottom/은 렌더링 완료 후입니다.
게시판 훅은 boards/handler.php 내에서, 인증 훅은 Auth.php 내에서 실행됩니다.
dx_body_bottom은 테마 렌더링의 맨 마지막이며, 팝업(priority 50)보다 작은 priority를 주면 팝업보다 먼저 실행됩니다.

 

댓글0

로그인 후 댓글을 작성할 수 있습니다.
1. DX 철학 / 개념 왜 DXCMS를 만들었는가 2026.04.20 1. DX 철학 / 개념 DXCMS란 무엇인가 2026.04.20 DXCMS 활용 (CMS) DXCMS 날코딩•막코딩 완전 허용 2026.04.12
31
전체 회원
503
전체 게시글
770
전체 댓글
441
오늘 방문
33,173
전체 방문
2
현재 접속
인기글 7일 이내
최신글
최신댓글
목록