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

Hook 개념

D DX
2026.04.21 00:58(수정됨) 133 0

1. Hook 시스템이란?

Hook(훅) 시스템은 CMS 핵심 코드를 수정하지 않고도 동작에 끼어들거나 값을 변형할 수 있게 해주는 확장 메커니즘입니다. DXCMS는 WordPress의 Action/Filter 개념을 PHP 5.6+ 호환으로 경량화하여 내장하고 있습니다.

핵심 개념 한 줄 요약
  • 훅 포인트 = CMS가 "여기서 끼어들어도 돼" 하고 열어 둔 자리
  • 콜백 등록 = 개발자가 "나 여기서 이 코드 실행할게" 하고 예약하는 것
  • 훅 실행 = CMS가 그 자리에 도달하면 등록된 콜백을 순서대로 호출하는 것


1.1 Hook의 두 가지 종류

종류 설명
Action Hook (dx_run_hook) CMS의 특정 시점에 코드를 실행합니다. 반환값 없음. "이 시점에 내 코드를 실행하라"는 명령입니다.
Filter Hook (dx_apply_filter) 값이 출력되기 전 가로채어 변형합니다. 반환값이 있음. "이 값을 내가 원하는 형태로 바꿔라"는 명령입니다.


1.2 Hook vs extend/ 폴더 비교

DXCMS에는 Hook 외에 extend/ 폴더라는 또 다른 확장 수단이 있습니다. 두 방식의 차이를 명확히 이해해야 합니다.
 
구분 Hook (dx_add_hook) extend/ 폴더
등록 방법 코드에서 함수 호출로 등록 파일을 폴더에 넣으면 자동 실행
실행 시점 훅 이름으로 정밀 제어 top / middle / bottom 3단계만
우선 순위 priority 숫자로 세밀 조정 파일명 오름차순으로만 제어
대상 개발자 (플러그인 제작자) 누구나 (설정 파일, 간단한 코드)
격리 try-catch로 자체 관리 필요 파일별 자동 에러 격리


2. HookManager 클래스 구조

HookManager는 싱글턴(Singleton) 패턴으로 구현되어 있으며 core/hook/HookManager.php에 위치합니다. 개발자는 직접 클래스를 사용하지 않고 전역 헬퍼 함수를 사용합니다.


2.1 핵심 메서드

메서드 / 함수 설명
dx_add_hook($name, $callback, $priority) 훅 포인트에 콜백 등록. priority가 낮을수록 먼저 실행 (기본값 10)
dx_run_hook($name, $args) Action 훅 실행. 등록된 콜백을 순서대로 호출. 반환값 없음
dx_apply_filter($name, $value, $args) Filter 훅 실행. 값을 콜백 체인에 통과시켜 변형된 값 반환
dx_add_filter($name, $callback, $priority) dx_add_hook의 별칭. Filter 의미를 명확히 하기 위해 사용
dx_remove_hook($name, $callback) 등록된 특정 콜백 제거. callback 생략 시 해당 훅 전체 제거
dx_has_hook($name) 해당 훅에 콜백이 등록되어 있는지 확인. boolean 반환


2.2 priority(우선순위) 동작 방식

priority는 정수값으로, 숫자가 작을수록 먼저 실행됩니다. 같은 priority면 등록 순서대로 실행됩니다.
 
// priority 1 → 가장 먼저 실행
dx_add_hook('dx_head', function() { echo '<!-- 먼저 -->'; }, 1);
 
// priority 10 → 기본값, 두 번째 실행
dx_add_hook('dx_head', function() { echo '<!-- 기본 -->'; });
 
// priority 999 → 가장 나중에 실행
dx_add_hook('dx_head', function() { echo '<!-- 마지막 -->'; }, 999);


3. 표준 훅 포인트 전체 목록

DXCMS v8.1.0은 아래 훅 포인트를 기본 제공합니다. 플러그인 개발 시 이 목록에서 필요한 훅을 선택해 사용합니다.


3.1 레이아웃 훅 (Layout Hooks)

테마 main.php에서 실행되는 훅입니다. HTML 출력에 직접 개입합니다.
 
훅 이름 실행 위치 / 용도
dx_head <head> 태그 내부. CSS•JS 링크, 메타 태그 추가
dx_top <body> 시작 직후. 전역 배너, 공지, 분석 스크립트
dx_middle 콘텐츠 영역 내부. 페이지별 맞춤 콘텐츠 삽입
dx_bottom </body> 직전. 하단 공통 위젯, 팝업
dx_footer_scripts body 닫기 직전 스크립트 영역. JS 초기화 코드
dx_body_bottom 최종 출력 직전. 전역 JS 실행 (priority 1 = 최우선)

페이지 타입별 세분화 훅도 자동 생성됩니다. context의 type 값에 따라 다음과 같이 추가 실행됩니다.
 
// type='board' 게시판 페이지라면 아래 훅들이 추가로 실행됩니다:
dx_board_top     // dx_{type}_top
dx_board_middle  // dx_{type}_middle
dx_board_bottom  // dx_{type}_bottom
 
// slug='notice' 페이지라면:
dx_page_notice_top
dx_page_notice_middle
dx_page_notice_bottom


3.2 인증 훅 (Auth Hooks)

훅 이름 전달 인자 / 용도
dx_after_login array('user' => $user) — 로그인 성공 후. 포인트 지급, 알림 발송
dx_after_logout array('user' => $user) — 로그아웃 후. 세션 정리, 로그 기록
dx_after_register array('user_id' => $id, 'data' => ...) — 회원가입 완료 후. 환영 메일 발송


3.3 게시판 훅 (Board Hooks)

훅 이름 전달 인자 / 용도
dx_board_before board, action, skin, id — 게시판 처리 시작 전. 접근 제어
dx_board_before_save data(참조), board, action — 저장 직전 데이터 변형 가능
dx_board_after_save post_id, board, redirect(참조) — 저장 완료 후. 알림, 포인트
dx_after_write post_id, board, data — 글 작성 완료 후. 전역 처리
dx_board_before_delete post, board_key — 삭제 직전. 관련 파일 정리
dx_board_after_delete post_id, board_key — 삭제 완료 후
dx_board_list_context context(참조), board — 목록 컨텍스트 변형
dx_board_view_context context(참조), board, post — 상세 컨텍스트 변형


3.4 커뮤니티 활동 훅

훅 이름 전달 인자 / 용도
dx_after_comment post_id, board_key, comment_id, member_id — 댓글 작성 후
dx_after_like post_id, owner_id — 좋아요 클릭 후. 포인트, 알림
dx_add_friend member_id, target_id — 친구 추가 완료 후
dx_after_point member_id, type, point, balance — 포인트 변동 후
dx_levelup member_id, old_level, new_level — 레벨 상승 시


3.5 플러그인•드라이버 훅

훅 이름 용도
dx_editor_render 에디터 HTML 렌더링. CKEditor4 등 커스텀 에디터 적용
dx_editor_init 에디터 초기화 JS 코드 삽입
dx_payment_request 결제 처리. 토스•카카오•이니시스 등 PG사 플러그인이 등록
dx_shop_after_purchase 구매 완료 후. 디지털 상품 배포, 쿠폰 발급
dx_mailer_drivers 메일 드라이버 등록. SMTP•SendGrid 등 커스텀 드라이버
dx_sms_drivers SMS 드라이버 등록. CoolSMS•NCP 등 커스텀 드라이버
dx_captcha_drivers Captcha 드라이버 등록. hCaptcha•Turnstile 등


3.6 관리자 훅

훅 이름 용도
dx_admin_top 관리자 패널 상단. 공지, 긴급 알림
dx_admin_bottom 관리자 패널 하단
dx_admin_dashboard_widgets 대시보드 위젯 추가 (dx-socket 플러그인 등에서 활용)


4. Action Hook 실전 예제


4.1 페이지 하단에 성능 표시 (디버그)

example-plugin/plugin.php 참고. 모든 페이지 우측 하단에 실행 시간과 DB 쿼리 수를 출력합니다.
 
if (defined('DX_DEBUG') && DX_DEBUG) {
    dx_add_hook('dx_bottom', function($context) {
        $time = round((microtime(true) - DX_START_TIME) * 1000, 2);
        $db   = Database::getInstance();
        echo '<div style="position:fixed;bottom:10px;right:10px;
              background:rgba(0,0,0,.7);color:#fff;padding:6px 12px;
              border-radius:8px;font-size:11px;z-index:9999">';
        echo '⚡ ' . $time . 'ms | DB: ' . $db->getQueryCount() . '쿼리';
        echo '</div>';
    }, 999);  // priority 999 = 가장 마지막에 실행
}


4.2 로그인 후 환영 메시지 표시

dx_add_hook('dx_after_login', function($args) {
    $user = $args['user'];
    $_SESSION['welcome_msg'] = $user['nickname'] . '님, 환영합니다!';
});
 
// 이후 dx_top 훅에서 세션 메시지를 화면에 표시
dx_add_hook('dx_top', function() {
    if (!empty($_SESSION['welcome_msg'])) {
        echo '<div class="dx-alert dx-alert-success">'
           . htmlspecialchars($_SESSION['welcome_msg'])
           . '</div>';
        unset($_SESSION['welcome_msg']);
    }
});


4.3 게시글 저장 전 데이터 보정

dx_board_before_save 훅의 data 인자는 참조(&)로 전달됩니다. 값을 수정하면 실제 저장 데이터에 반영됩니다.
 
dx_add_hook('dx_board_before_save', function($args) {
    $data  = &$args['data'];   // 참조 전달 — 수정하면 저장에 반영됨
    $board = $args['board'];
 
    // 제목이 비어 있으면 내용 앞 20자를 자동 제목으로
    if (empty($data['title']) && !empty($data['content'])) {
        $data['title'] = mb_substr(strip_tags($data['content']), 0, 20) . '...';
    }
 
    // 특정 게시판에서 금지어 필터
    if ($board['board_key'] === 'free') {
        $data['content'] = str_replace(['광고', '스팸'], '***', $data['content']);
    }
});


4.4 회원가입 후 환영 메일 발송

dx_add_hook('dx_after_register', function($args) {
    $userId = $args['user_id'];
    $data   = $args['data'];
 
    try {
        $mailer = DxMailer::getInstance();
        $mailer->send(
            $data['email'],
            '회원가입을 환영합니다!',
            '<h2>안녕하세요 ' . htmlspecialchars($data['nickname']) . '님</h2>'
            . '<p>DXCMS에 가입해 주셔서 감사합니다.</p>'
        );
    } catch (Exception $e) {
        dx_log('[welcome-mail] ' . $e->getMessage(), 'warning');
    }
});


4.5 결제 플러그인 연동 (dx_payment_request)

결제 플러그인들은 dx_payment_request 훅에 자신을 등록합니다. 실제 tosspay-payment/plugin.php 구조입니다.
 
// plugins/my-payment/plugin.php
dx_add_hook('dx_payment_request', function($data) {
    // $data['payment'] 에 결제 수단 ID가 담겨 있음
    if ($data['payment'] !== 'my_payment') return;  // 내 플러그인만 처리
 
    // 결제 처리 로직
    $amount  = (int)$data['amount'];
    $orderId = $data['order_id'];
 
    // 결제 API 호출...
    header('Content-Type: application/json');
    echo json_encode(array('success' => true, 'redirect' => '/payment/complete'));
    exit;
});


4.6 head에 CSS•JS 삽입

dx_head 훅은 <head> 태그 내부에서 실행됩니다. 플러그인 전용 스타일시트나 스크립트를 삽입할 때 사용합니다.
 
dx_add_hook('dx_head', function() {
    $base = rtrim(dx_base_url(''), '/');
    echo '<link rel="stylesheet" href="' . $base . '/plugins/my-plugin/style.css">';
    echo '<script src="' . $base . '/plugins/my-plugin/script.js"></script>';
});
 
// body 닫기 직전에 JS 실행이 필요할 때
dx_add_hook('dx_body_bottom', function() {
    echo '<script>
        document.addEventListener("DOMContentLoaded", function() {
            MyPlugin.init();
        });
    </script>';
});


5. Filter Hook 실전 예제

Filter 훅은 값을 가로채어 변형한 뒤 돌려줍니다. 반드시 변형된 값을 return 해야 합니다. DXCMS에서는 dx_apply_filter()와 dx_add_filter()를 사용합니다.

Filter 훅 사용 원칙
콜백에서 반드시 $value를 return 해야 합니다. return을 빠뜨리면 이후 콜백에 null이 전달됩니다.
여러 콜백이 체인처럼 연결됩니다. 앞 콜백의 반환값이 다음 콜백의 $value가 됩니다.
원본 값의 타입을 유지하는 것이 좋습니다. 문자열로 받았으면 문자열을 반환하세요.


5.1 기본 Filter 훅 등록과 실행 구조

// ① CMS 내부 코드 (훅 포인트 정의)
$content = dx_apply_filter('dx_post_content', $rawContent, array('post_id' => $id));
echo $content;  // 필터링된 내용 출력
 
// ② 플러그인/확장 코드 (필터 등록)
dx_add_filter('dx_post_content', function($value, $args) {
    // URL을 자동으로 링크로 변환
    $value = preg_replace(
        '/(https?:\/\/[^\s<>"]+)/',
        '<a href="$1" target="_blank">$1</a>',
        $value
    );
    return $value;  // 반드시 return!
});


5.2 다중 Filter 체인

같은 훅에 여러 필터를 등록하면 priority 순으로 체인을 형성합니다.
 
// 필터 1: HTML 태그 허용 목록 적용 (priority 5)
dx_add_filter('dx_post_content', function($value) {
    $allowed = '<p><br><strong><em><ul><ol><li><a><img>';
    return strip_tags($value, $allowed);
}, 5);
 
// 필터 2: 이모지 변환 (priority 10 = 기본, 1 다음에 실행)
dx_add_filter('dx_post_content', function($value) {
    return str_replace(':)', '😊', $value);
}, 10);
 
// 필터 3: 광고 문구 차단 (priority 20, 가장 나중)
dx_add_filter('dx_post_content', function($value) {
    return preg_replace('/광고[^\n]*/u', '[광고 차단]', $value);
}, 20);


6. 플러그인에서 Hook 등록하기

DXCMS 플러그인은 plugins/{plugin-name}/plugin.php 파일에 훅을 등록합니다. 이 파일은 CMS 초기화 완료 후 자동으로 include됩니다.


6.1 플러그인 파일 기본 구조

<?php
/**
 * My Plugin - 플러그인 설명
 */
if (!defined('DX_CMS')) exit('Direct access not allowed.');
 
// ① 페이지 상단에 안내 배너 표시
dx_add_hook('dx_top', function($context) {
    if (!isset($context['type'])) return;
    echo '<div class="my-notice">공지: 서비스 점검 예정 안내</div>';
}, 1);
 
// ② 레벨업 시 알림 발송
dx_add_hook('dx_levelup', function($args) {
    $memberId = $args['member_id'];
    $newLevel = $args['new_level'];
    // 알림 DB 저장 등 처리
    DxNotification::send($memberId, '레벨이 ' . $newLevel . '로 상승했습니다!');
});
 
// ③ 관리자 대시보드 위젯 추가
dx_add_hook('dx_admin_dashboard_widgets', function() {
    echo '<div class="dx-card"><h3>My Plugin 통계</h3>.....</div>';
});


6.2 훅 제거 (우선순위로 교체)

기존에 등록된 훅을 제거하거나 새 콜백으로 교체할 수 있습니다.
 
// 특정 콜백 제거
$myCallback = function($args) { /* ... */ };
dx_add_hook('dx_top', $myCallback);
 
// 나중에 제거
dx_remove_hook('dx_top', $myCallback);
 
// 해당 훅 전체 콜백 제거 (callback 생략)
dx_remove_hook('dx_top');

6.3 훅 등록 여부 확인

if (dx_has_hook('dx_payment_request')) {
    // 결제 플러그인이 설치되어 있을 때만 UI 표시
    echo '<button>결제하기</button>';
} else {
    echo '<p>결제 플러그인을 설치해 주세요.</p>';
}


7. context 인자 활용

dx_hook_top / dx_hook_middle / dx_hook_bottom으로 실행되는 레이아웃 훅에는 context 배열이 전달됩니다. 이 값으로 현재 페이지 종류를 판단할 수 있습니다.


7.1 context 주요 키

설명 / 예시
type 페이지 유형. 'board', 'page', 'auth', 'admin', 'search' 등
slug 페이지 슬러그. board_key 또는 page slug. 예: 'free', 'notice'
action 현재 액션. 'list', 'view', 'write', 'edit' 등
board_key 게시판 키값 (type=board일 때). 예: 'free'


7.2 특정 페이지에서만 실행

dx_add_hook('dx_bottom', function($context) {
 
    // 게시판 목록 페이지에서만 실행
    if (($context['type'] ?? '') !== 'board') return;
    if (($context['action'] ?? '') !== 'list') return;
 
    echo '<script>console.log("게시판 목록 페이지");</script>';
});
 
dx_add_hook('dx_middle', function($context) {
 
    // 'free' 게시판에서만 사이드 위젯 표시
    if (($context['board_key'] ?? '') !== 'free') return;
 
    echo '<aside class="free-board-widget">.....</aside>';
});


8. 커스텀 훅 포인트 만들기

플러그인이나 테마에서 직접 훅 포인트를 정의하면, 다른 플러그인이 해당 시점에 끼어들 수 있습니다. 오픈 구조를 만들어 확장성을 높이는 방법입니다.

8.1 Action 훅 포인트 정의

// 내 플러그인 A: 처리 완료 후 훅 포인트 제공
function my_plugin_process($data) {
    // 처리 로직...
    $result = do_something($data);
 
    // 다른 플러그인이 끼어들 수 있는 포인트 제공
    dx_run_hook('my_plugin_after_process', array(
        'data'   => $data,
        'result' => $result,
    ));
 
    return $result;
}
 
// 플러그인 B: A의 훅 포인트를 활용
dx_add_hook('my_plugin_after_process', function($args) {
    dx_log('[플러그인B] 처리 완료: ' . json_encode($args['result']), 'info');
});


8.2 Filter 훅 포인트 정의

// 플러그인 A: 출력 전 필터 포인트 제공
function my_plugin_render($html) {
    // 다른 플러그인이 HTML을 변형할 수 있게 허용
    $html = dx_apply_filter('my_plugin_render', $html);
    echo $html;
}
 
// 플러그인 C: A의 HTML에 광고 삽입
dx_add_filter('my_plugin_render', function($html) {
    return $html . '<div class="ad">광고</div>';
});


9. 디버그 & 트러블슈팅


9.1 실행된 훅 목록 확인

HookManager::getExecuted()로 현재 요청에서 실행된 훅 목록을 확인할 수 있습니다.
 
if (defined('DX_DEBUG') && DX_DEBUG) {
    dx_add_hook('dx_bottom', function() {
        $executed = HookManager::getInstance()->getExecuted();
        echo '<pre style="font-size:11px;background:#f5f5f5;padding:10px">';
        echo '실행된 훅 (' . count($executed) . '개):\n';
        print_r($executed);
        echo '</pre>';
    }, 9999);
}


9.2 등록된 훅 전체 목록 확인

$allHooks = HookManager::getInstance()->getAll();
// 반환 예시:
// ['dx_head', 'dx_body_bottom', 'dx_after_login', 'dx_payment_request', ...]
 
// 특정 훅에 몇 개의 콜백이 등록되어 있는지 확인
$cnt = HookManager::getInstance()->count('dx_payment_request');
// 예: 2 (tosspay + kakaopay 두 개 플러그인이 등록된 경우)


9.3 자주 발생하는 실수

실수 올바른 방법
Filter 훅에서 return을 빠뜨림 콜백 내에서 반드시 return $value; 작성
참조 인자를 복사로 받음 data(참조)는 &$args['data']로 받아야 수정 반영됨
훅 이름 오타 dx_has_hook()으로 훅 이름 존재 여부 먼저 확인
훅 등록 위치가 늦음 플러그인 파일 최상단에서 등록해야 실행 전에 예약됨
priority 충돌 코어 훅은 1~99, 플러그인은 100+, 디버그는 999로 규칙 통일


10. 훅 개발 가이드라인


10.1 priority 사용 규칙 권장

범위 용도
1 ~ 9 최우선 실행 필요 (보안 체크, 인증 확인, 필수 데이터 주입)
10 (기본) 일반 플러그인 로직 (기본값 유지 권장)
11 ~ 99 코어에 의존하는 로직 (코어 콜백 실행 후 처리)
100 ~ 998 서드파티 플러그인 간 충돌 방지용 범위
999 디버그, 로깅, 정리 작업 (가장 마지막에 실행)


10.2 안전한 훅 콜백 작성

dx_add_hook('dx_after_write', function($args) {
    // ① 필수 인자 확인
    if (empty($args['post_id'])) return;
 
    // ② try-catch로 에러 격리 (훅 실패가 CMS 전체를 멈추면 안 됨)
    try {
        $postId = (int)$args['post_id'];
        // 처리 로직...
    } catch (Exception $e) {
        dx_log('[my-plugin][dx_after_write] ' . $e->getMessage(), 'error');
    }
});


10.3 훅 이름 네이밍 규칙

  • DXCMS 내장 훅: dx_ 접두사 (예: dx_after_login)
  • 플러그인 자체 훅: {plugin-name}_ 접두사 (예: tosspay_after_payment)
  • 테마 훅: theme_ 접두사 (예: theme_header_end)
  • 충돌 방지를 위해 고유한 접두사를 반드시 사용하세요.

최종 정리
Hook은 CMS 코드를 수정하지 않고 동작을 확장하는 핵심 메커니즘입니다.
Action 훅(dx_run_hook)은 실행, Filter 훅(dx_apply_filter)은 값 변형에 사용합니다.
priority로 실행 순서를 제어하며, 낮은 숫자가 먼저 실행됩니다.
모든 콜백은 try-catch로 에러를 격리하여 CMS 안정성을 보장하세요.
커스텀 훅 포인트를 정의하면 내 플러그인도 다른 플러그인이 확장할 수 있습니다.

 

댓글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일 이내
최신글
최신댓글
목록