회원가입 | 고객센터 |
DESIGNONEX
dxcms.kr
로그인 회원가입
고객센터
3.8 Extend 구조

코어 수정 없이 CMS를 확장하는 방법

D DX
2026.05.02 02:54(수정됨) 135 0

1. Extend 구조란 무엇인가?

DXCMS v8.1.0은 "코어 파일을 절대 수정하지 않아도 된다"는 철학을 중심으로 설계된 확장 아키텍처를 제공합니다. 이 구조를 Extend 구조라고 부르며, 크게 세 가지 레이어로 구성됩니다.


1.1 왜 코어를 수정하면 안 되는가?

많은 개발자가 기능 추가를 위해 CMS 코어 파일을 직접 수정하는 유혹에 빠집니다. 그러나 이 방식은 다음과 같은 치명적인 문제를 낳습니다.
  • 업데이트 불가: 코어 파일을 수정하면 DXCMS 새 버전이 나와도 덮어쓸 수 없게 됩니다.
  • 충돌 위험: 여러 개발자가 같은 파일을 수정하면 병합 충돌이 발생합니다.
  • 추적 불가: 어떤 기능이 어디서 추가됐는지 파악하기 어려워집니다.
  • 롤백 불가: 문제 발생 시 원래 상태로 되돌리는 것이 매우 어렵습니다.
  • 보안 위험: 핵심 보안 로직이 의도치 않게 변경될 수 있습니다.

DXCMS의 원칙
"코어는 건드리지 않는다. 모든 커스터마이징은 extend/, plugins/, 훅을 통해서만 한다."
이 원칙을 지키면 CMS를 업데이트해도 커스텀 기능이 그대로 유지됩니다.


1.2 Extend 3계층 구조

DXCMS의 확장 메커니즘은 사용 난이도와 목적에 따라 3계층으로 나뉩니다.
 
계층 수단 위치 대상 특징
1계층 (가장 쉬움) extend/ 폴더 extend/top•middle•bottom/ 누구나 파일을 폴더에 넣으면 자동 실행
2계층 (중간) Hook 시스템 plugins/ 또는 extend/ 개발자 특정 시점에 콜백 등록•실행
3계층 (심화) Plugin 시스템 plugins/{plugin-name}/ 플러그인 개발자 에디터•결제 등 교체 가능한 기능 모듈


1.3 실행 흐름 다이어그램

DXCMS 요청 처리의 전체 흐름에서 Extend 구조가 어느 시점에 실행되는지 확인하세요.
 
[ 브라우저 요청 ] → index.php
    │
    ├─ ① CMS 초기화 (DB • 세션 • 인증 • DxSite • DxTheme • 플러그인 로드)
    │
    ├─ ② extend/top/ 실행  ← 점검모드, IP 차단, 전역 변수 주입 등
    │         └─ dx_extend_top 훅도 여기서 발화
    │
    ├─ ③ Router: 라우트 확정 ($GLOBALS['dx_route'] 세팅)
    │
    ├─ ④ extend/middle/ 실행  ← 접근 로그, A/B 테스트, 라우트별 처리
    │         └─ dx_extend_middle 훅도 여기서 발화
    │
    ├─ ⑤ Dispatcher: 핸들러 실행 (페이지 렌더링)
    │
    ├─ ⑥ ob_end_flush() 직전
    │
    └─ ⑦ extend/bottom/ 실행  ← 캐시 저장, 성능 로그, 정리 작업
              └─ dx_extend_bottom 훅도 여기서 발화

[ 브라우저 응답 전송 ]


2. extend/ 폴더 자동 실행 시스템 (DxExtend)

extend/ 폴더 시스템은 "파일만 넣으면 실행되는" 가장 단순한 확장 방법입니다. DxExtend 엔진이 지정된 폴더의 PHP 파일을 파일명 오름차순으로 자동 실행합니다.


2.1 폴더 구조

extend/
├── top/
│   ├── 01_maintenance.php      ← 점검 모드
│   ├── 02_ip_block.php         ← IP 차단
│   └── 03_global_vars.php      ← 전역 변수 주입
│
├── middle/
│   ├── 01_visit_tracker.php    ← 방문자 통계 (내장)
│   ├── 02_analytics.php        ← GA 연동 등
│   └── 03_ab_test.php          ← A/B 테스트
│
└── bottom/
    ├── 01_cache_write.php       ← 페이지 캐시 저장
    ├── 02_darkmode_engine.php   ← 다크모드 JS 주입 (내장)
    └── 03_perf_log.php         ← 성능 로그

📌 파일 실행 규칙
• *.php 파일만 실행됩니다 (다른 확장자는 무시)
• 파일명 오름차순으로 실행됩니다 (01_ → 02_ → ... 순서)
• 하위 폴더의 파일도 자동 탐색합니다 (1단계 재귀)
• 한 파일에서 에러가 나도 다른 파일 실행에 영향 없음 (에러 격리)
• .disabled 확장자를 붙이면 비활성화됩니다 (PHP 인식 불가)


2.2 각 슬롯의 실행 시점과 용도


2.2.1 extend/top/ — CMS 초기화 완료 직후

항목 내용
실행 시점 DB 연결, 세션 시작, 인증, DxSite, DxTheme, 플러그인 로드가 모두 완료된 직후
가용 자원 DB 쿼리, 세션, Auth, dx_config(), dx_log(), 모든 CMS 함수 사용 가능
주의 사항 라우트 정보 ($GLOBALS['dx_route'])는 아직 확정되지 않음
권장 용도 점검 모드, IP 차단, 커스텀 인증, 전역 PHP 변수 주입, ob_start 훅 등록


2.2.2 extend/middle/ — 라우트 확정 직후

항목 내용
실행 시점 Router가 요청 URL을 분석하여 라우트를 확정한 직후, 핸들러 실행 전
가용 자원 top/의 모든 자원 + $GLOBALS['dx_route'] (현재 라우트 정보)
주의 사항 핸들러 실행 전이므로 페이지 출력을 가로챌 수 있음
권장 용도 방문자 로그, 방문 통계, 접근 제어, A/B 테스트, 라우트별 분기 처리


$GLOBALS['dx_route'] 구조 예시:

$GLOBALS['dx_route'] = array(
    'type'   => 'board',    // 라우트 타입 (page, board, admin, api 등)
    'slug'   => 'notice',   // 페이지/게시판 슬러그
    'action' => 'list',     // 동작 (list, view, write, edit 등)
    'id'     => 123,        // 게시글 ID (있는 경우)
);


2.2.3 extend/bottom/ — 렌더링 완료 후

항목 내용
실행 시점 ob_end_flush() 직전 (출력 버퍼 플러시 전)
가용 자원 모든 CMS 자원, 단 헤더 변경 불가 (이미 출력 시작)
주의 사항 ob_start가 활성인 경우 추가 출력 가능, 그렇지 않으면 헤더 불가
권장 용도 페이지 캐시 저장, 성능 측정 출력, 임시 파일 정리, JS 인젝션


2.3 파일 작성 방법

extend/ 파일 작성 시 반드시 지켜야 할 규칙이 있습니다.

필수 보안 헤더

<?php
// 직접 접근 차단 (필수)
if (!defined('DX_CMS')) exit;

// 이후에 원하는 코드 작성

⚠️ 경고: if (!defined('DX_CMS')) exit; 를 파일 첫 줄에 반드시 작성하세요.
이 한 줄이 없으면 외부에서 파일을 직접 호출하여 보안 위협이 발생할 수 있습니다.


파일명 네이밍 규칙

패턴 예시 설명
NN_설명.php 01_maintenance.php 숫자 2자리 + 언더스코어 + 설명
비활성화 99_example.php.disabled .disabled 추가 시 PHP가 인식 불가
그룹화 security/01_ip_block.php 하위 폴더로 논리 그룹화 가능


3. extend/ 실전 예제


3.1 점검 모드 구현 (top/)

사이트 점검 시 관리자를 제외한 모든 사용자에게 점검 안내 페이지를 보여줍니다.
파일: extend/top/01_maintenance.php
 
<?php
if (!defined('DX_CMS')) exit;

// ── 점검 모드 설정 ──────────────────────────
$maintenance = array(
    'enabled'  => true,                    // true: 점검 ON
    'message'  => '서비스 점검 중입니다.', // 안내 메시지
    'end_time' => '2026-05-01 03:00',      // 점검 종료 예정 시각
    'allow_ip' => array('127.0.0.1'),      // 허용 IP 목록
);

if (!$maintenance['enabled']) return;   // 점검 OFF면 즉시 종료

// 관리자는 통과
if (function_exists('dx_is_admin') && dx_is_admin()) return;

// 허용 IP는 통과
$clientIp = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
if (in_array($clientIp, $maintenance['allow_ip'], true)) return;

// 점검 페이지 출력
http_response_code(503);
header('Retry-After: 3600');
?>
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>서비스 점검중</title>
  <style>
    body { font-family: sans-serif; text-align: center; padding: 80px; }
    h1 { color: #1E3A5F; }
    .time { color: #2563EB; font-weight: bold; }
  </style>
</head>
<body>
  <h1>🔧 서비스 점검중</h1>
  <p>더 나은 서비스를 위해 잠시 점검 중입니다.</p>
  <p class="time">점검 종료 예정: <?php echo $maintenance['end_time']; ?></p>
</body>
</html>
<?php exit;


3.2 IP 차단 (top/)

특정 IP 또는 IP 대역을 차단합니다.
파일: extend/top/02_ip_block.php
 
<?php
if (!defined('DX_CMS')) exit;

$blockedIps = array(
    '192.168.1.100',       // 특정 IP
    '10.0.0.',             // 이 문자열로 시작하는 모든 IP
    '203.0.113.',          // 예: 특정 기관 IP 대역
);

$clientIp = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';

foreach ($blockedIps as $blocked) {
    if (strpos($clientIp, $blocked) === 0) {
        http_response_code(403);
        dx_log('[IP Block] 차단: ' . $clientIp, 'warning');
        exit('403 Forbidden');
    }
}


3.3 전역 변수 주입 (top/)

모든 페이지에서 공통으로 필요한 설정값이나 변수를 전역으로 주입합니다.
파일: extend/top/03_global_vars.php
 
<?php
if (!defined('DX_CMS')) exit;

// 전역 변수 정의
define('MY_APP_VERSION', '2.0.0');
define('MY_CDN_URL', 'https://cdn.example.com');

// PHP $_GLOBALS에 배열로 주입 (테마/플러그인에서 접근 가능)
$GLOBALS['my_app'] = array(
    'version'    => MY_APP_VERSION,
    'cdn'        => MY_CDN_URL,
    'start_time' => microtime(true),
);

// 세션 기반 사용자 커스텀 데이터 초기화
if (!isset($_SESSION['my_prefs'])) {
    $_SESSION['my_prefs'] = array(
        'lang'  => 'ko',
        'theme' => 'light',
    );
}


3.4 방문자 통계 로깅 (middle/)

라우트 확정 후 방문자 정보를 기록합니다. DXCMS 내장 extend/middle/01_visit_tracker.php는 이미 고성능으로 구현되어 있으며, 아래는 간단한 커스텀 접근 로그 예제입니다.
파일: extend/middle/02_custom_access_log.php
 
<?php
if (!defined('DX_CMS')) exit;

// 라우트 정보 가져오기 (middle에서만 사용 가능)
$route = isset($GLOBALS['dx_route']) ? $GLOBALS['dx_route'] : array();
$type  = isset($route['type']) ? $route['type'] : 'unknown';

// admin, api 요청은 로그에서 제외
if (in_array($type, array('admin', 'api'), true)) return;

// 봇 제외
$ua = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
if (preg_match('/bot|crawl|spider/i', $ua)) return;

// 로그 기록 (응답 후 처리로 성능 영향 최소화)
register_shutdown_function(function() use ($route, $ua) {
    $log = date('Y-m-d H:i:s')
         . ' | ' . dx_ip()
         . ' | ' . json_encode($route)
         . ' | ' . substr($ua, 0, 100);
    dx_log($log, 'info');
});


3.5 HTML 버퍼 조작으로 JS 주입 (top/ + bottom/ 조합)

다크모드 FOUC 방지와 같이 최종 HTML에 스크립트를 삽입해야 하는 경우, ob_start를 top/에서 등록하고 bottom/에서 JS를 출력합니다.
파일: extend/top/01_darkmode_early.php (내장 예제 요약)
 
<?php
if (!defined('DX_CMS')) exit;

// ob_start 콜백으로 최종 HTML 버퍼를 가로챔
ob_start(function($buffer) {
    // <head> 바로 뒤에 인라인 스크립트 삽입
    $script = '<script>
        (function(){
            var t = localStorage.getItem("theme");
            if (t === "dark") document.body.classList.add("dark");
        })();
    </script>';
    $buffer = preg_replace('/<head([^>]*)>/i', '<head$1>' . $script, $buffer, 1);
    return $buffer;
});


4. Hook 시스템 (HookManager)

Hook 시스템은 "특정 시점에 콜백 함수를 등록하고 실행"하는 방식입니다. extend/ 폴더보다 더 세밀하게 실행 시점과 우선순위를 제어할 수 있습니다.


4.1 Hook의 종류

종류 함수 역할 반환값
Action Hook dx_add_hook() / dx_run_hook() 특정 시점에 코드 실행 없음 (side effect)
Filter Hook dx_add_filter() / dx_apply_filter() 값을 가공하여 반환 가공된 값


4.2 전역 헬퍼 함수

함수 매개변수 설명
dx_add_hook($name, $callback, $priority) name: 훅 이름 / callback: 실행함수 / priority: 우선순위(기본 10) Action 훅 등록. 낮은 priority가 먼저 실행
dx_run_hook($name, $args) name: 훅 이름 / args: 전달 인자 배열 등록된 Action 훅 실행
dx_add_filter($name, $callback, $priority) 위와 동일 Filter 훅 등록 (내부적으로 add와 동일)
dx_apply_filter($name, $value, $args) name: 훅 이름 / value: 원본값 / args: 추가 인자 등록된 Filter 실행 후 최종값 반환
dx_remove_hook($name, $callback) name: 훅 이름 / callback: 제거할 콜백(null=전체) 등록된 훅 제거
dx_has_hook($name) name: 훅 이름 해당 훅이 등록되어 있는지 확인


4.3 내장 훅 포인트

DXCMS는 페이지 렌더링 시 자동으로 다음 훅을 발화합니다.


4.3.1 전역 훅 (모든 페이지)

훅 이름 발화 시점 주요 용도
dx_top 모든 페이지 상단 공통 헤더 요소 삽입, 스크립트 주입
dx_middle 모든 페이지 중간 콘텐츠 영역 내 삽입
dx_bottom 모든 페이지 하단 </body> 직전 JS 로드, 분석 코드, 채팅 위젯 등

 

4.3.2 타입별 훅 (자동 생성)

현재 라우트 타입이 board라면 dx_board_top, dx_board_middle, dx_board_bottom 훅이 자동으로 발화됩니다.
 
// 라우트 타입: 'board', 'page', 'admin', 'api' 등
dx_add_hook('dx_board_top', function($context) {
    // 게시판 페이지 상단에만 실행되는 코드
    echo '<div class="board-notice">게시판 공지</div>';
});

dx_add_hook('dx_page_top', function($context) {
    // 일반 페이지(type=page) 상단에만 실행
});


4.3.3 슬러그별 훅 (자동 생성)

라우트 slug가 notice라면 dx_page_notice_top 등 슬러그 전용 훅이 발화됩니다.
 
// slug='notice'인 페이지 상단에만 실행
dx_add_hook('dx_page_notice_top', function($context) {
    echo '<p>공지사항 전용 배너</p>';
});


4.3.4 extend/ 슬롯 훅

훅 이름 발화 위치
dx_extend_top extend/top/ 파일 실행 완료 직후
dx_extend_middle extend/middle/ 파일 실행 완료 직후
dx_extend_bottom extend/bottom/ 파일 실행 완료 직후


4.4 우선순위(Priority) 제어

여러 훅 콜백이 동일한 훅에 등록된 경우, priority 값이 낮을수록 먼저 실행됩니다.
 
// 1번 먼저 실행 (priority 1)
dx_add_hook('dx_bottom', function() {
    echo '<!-- 첫 번째 -->';
}, 1);

// 2번 나중에 실행 (priority 10, 기본값)
dx_add_hook('dx_bottom', function() {
    echo '<!-- 두 번째 -->';
}, 10);

// 3번 맨 마지막 실행 (priority 999)
dx_add_hook('dx_bottom', function() {
    echo '<!-- 맨 마지막 -->';
}, 999);


4.5 Filter Hook 실용 예제

Filter 훅은 값을 받아서 변환한 후 반환합니다. Action 훅과 달리 반환값이 중요합니다.
 
// 게시글 내용 필터: 특정 단어 마스킹
dx_add_filter('dx_post_content', function($content, $args) {
    // 금칙어 필터링
    $content = str_replace('금칙어1', '***', $content);
    $content = str_replace('금칙어2', '***', $content);
    return $content;  // 반드시 반환!
}, 10);

// 코어에서 필터 적용 (코어가 이미 이렇게 구현되어 있음)
$content = dx_apply_filter('dx_post_content', $rawContent, array('post_id' => $id));


5. Plugin 시스템 (PluginRegistry)

플러그인 시스템은 에디터, 결제 모듈, CAPTCHA, SMS 등 "교체 가능한 기능 모듈"을 위한 고급 확장 방법입니다. PluginRegistry에 등록된 플러그인은 관리자 페이지에서 선택하고 활성화할 수 있습니다.


5.1 지원 플러그인 타입

타입 키워드 설명 설정 키
에디터 editor 게시글 작성 에디터 (TinyMCE, CKEditor 등) active_editor
결제 payment PG사 결제 모듈 (KG이니시스, 토스 등) active_payment
CAPTCHA captcha 스팸 방지 (reCAPTCHA, hCaptcha 등) active_captcha
SMS sms 문자 발송 (NCP, 알리고 등) active_sms
소셜 로그인 social_login 카카오, 네이버, 구글 로그인 active_social_login
소켓 socket WebSocket 실시간 기능 active_socket
커스텀 자유 지정 개발자가 임의 타입 추가 가능 active_{타입}


5.2 플러그인 폴더 구조

plugins/
├── my-plugin/              ← 플러그인 폴더명 = 플러그인 ID
│   ├── plugin.php          ← 플러그인 메인 파일 (훅 등록 등)
│   ├── manifest.php        ← 플러그인 메타 정보
│   └── admin/              ← 관리자 설정 페이지 (선택)
│       └── settings.php
│
├── example-plugin/
│   ├── plugin.php
│   └── manifest.php
│
└── dx-payment-helper.php   ← 공용 결제 헬퍼 (내장)


5.3 플러그인 개발 3단계


Step 1: manifest.php 작성

manifest.php는 플러그인의 메타 정보를 담습니다. 마켓 등록 시 이 파일이 사용됩니다.
 
<?php
// plugins/my-editor/manifest.php
return array(
    'name'        => 'My Custom Editor',
    'version'     => '1.0.0',
    'description' => '커스텀 마크다운 에디터입니다.',
    'author'      => '홍길동',
    'author_url'  => 'https://example.com',
    'type'        => 'editor',          // 플러그인 타입
    'min_version' => '8.0.0',           // 최소 CMS 버전
    'tags'        => '에디터,마크다운',
    'license'     => 'MIT',
);


Step 2: plugin.php 작성

plugin.php는 플러그인의 핵심 파일입니다. 플러그인을 PluginRegistry에 등록하고, 훅을 통해 동작을 구현합니다.
 
<?php
if (!defined('DX_CMS')) exit('Direct access not allowed.');

// ── 1. 플러그인 등록 (PluginRegistry에 등록) ─────
dx_register_plugin(array(
    'id'          => 'my-editor',
    'type'        => 'editor',
    'name'        => 'My Custom Editor',
    'version'     => '1.0.0',
    'description' => '커스텀 마크다운 에디터',
    'author'      => '홍길동',
    'priority'    => 20,               // 낮을수록 목록 상단
    'settings'    => array(           // 플러그인 전용 설정 필드
        'theme'     => array('label' => '테마', 'type' => 'select',
                             'options' => array('light'=>'라이트', 'dark'=>'다크')),
        'height'    => array('label' => '에디터 높이(px)', 'type' => 'number'),
    ),
));

// ── 2. 에디터 렌더링 훅 구현 ──────────────────────
dx_add_hook('dx_editor_render', function($args) {
    // 이 플러그인이 활성화된 경우에만 실행
    if ($args['editor'] !== 'my-editor') return;

    $name  = htmlspecialchars($args['name'], ENT_QUOTES);
    $value = htmlspecialchars($args['value'], ENT_QUOTES, 'UTF-8');

    echo '<div class="my-editor-wrap">';
    echo '<textarea id="my-editor-' . $name . '" name="' . $name . '">'
       . $value . '</textarea>';
    echo '<script src="/plugins/my-editor/editor.js"></script>';
    echo '</div>';
}, 10);


Step 3: 활성 플러그인 확인 및 호출

테마나 다른 코드에서 현재 활성화된 플러그인을 확인하고 사용합니다.
 
// 현재 활성 에디터 ID 확인
$editorId = dx_active_plugin('editor');  // 예: 'my-editor'

// 에디터 렌더링 (모든 에디터 공통 인터페이스)
dx_render_editor('content', $existingContent, array(
    'height' => 400,
    'board'  => $board,   // 게시판별 에디터 오버라이드 가능
));

// 결제 요청
dx_request_payment(array(
    'order_id'     => 'ORD-2026-001',
    'amount'       => 29000,
    'product_name' => 'DXCMS Pro',
    'buyer_name'   => '홍길동',
    'buyer_email'  => 'hong@example.com',
    'return_url'   => 'https://example.com/payment/result',
));


6. Hook 활용 실전 패턴


6.1 디버그 패널 삽입 (dx_bottom)

DX_DEBUG 모드에서 실행 시간과 쿼리 수를 페이지 우하단에 표시합니다.
 
// extend/top/01_debug_panel.php 또는 plugin.php 내에서
dx_add_hook('dx_bottom', function($context) {
    if (!defined('DX_DEBUG') || !DX_DEBUG) return;

    $time    = round((microtime(true) - DX_START_TIME) * 1000, 2);
    $queries = Database::getInstance()->getQueryCount();
    $route   = isset($GLOBALS['dx_route']) ? json_encode($GLOBALS['dx_route']) : '-';

    echo '<div style="position:fixed;bottom:10px;right:10px;
               background:rgba(0,0,0,.85);color:#fff;
               padding:8px 14px;border-radius:8px;font-size:11px;
               font-family:monospace;z-index:9999;line-height:1.8">';
    echo '⚡ ' . $time . 'ms &nbsp;|&nbsp; 🗄 ' . $queries . ' queries';
    echo '<br>📍 ' . htmlspecialchars($route, ENT_QUOTES);
    echo '</div>';
}, 999);


6.2 특정 페이지에만 CSS/JS 삽입

특정 슬러그의 페이지에만 에셋을 추가합니다.
 
// 'gallery' 슬러그 페이지 상단에만 CSS 삽입
dx_add_hook('dx_page_gallery_top', function($context) {
    echo '<link rel="stylesheet" href="/assets/css/gallery-custom.css">';
});

// 'gallery' 슬러그 페이지 하단에만 JS 삽입
dx_add_hook('dx_page_gallery_bottom', function($context) {
    echo '<script src="/assets/js/gallery-lightbox.js"></script>';
});


6.3 관리자 전용 사이드바 메뉴 추가

dx_add_hook('dx_admin_sidebar', function($context) {
    echo '<li class="nav-item">';
    echo '  <a href="/admin/my-module" class="nav-link">';
    echo '    <i class="icon">📊</i> 내 모듈';
    echo '  </a>';
    echo '</li>';
});


6.4 훅 우선순위를 이용한 실행 순서 보장

// ① 가장 먼저: 보안 체크 (priority 1)
dx_add_hook('dx_top', 'my_security_check', 1);

// ② 다음: 사용자 권한 확인 (priority 5)
dx_add_hook('dx_top', 'my_permission_check', 5);

// ③ 기본: 일반 처리 (priority 10, 기본값)
dx_add_hook('dx_top', 'my_normal_process');

// ④ 맨 마지막: 로깅 (priority 999)
dx_add_hook('dx_top', 'my_access_log', 999);

function my_security_check($ctx) { /* ... */ }
function my_permission_check($ctx) { /* ... */ }
function my_normal_process($ctx) { /* ... */ }
function my_access_log($ctx) { /* ... */ }


7. extend/ vs Hook vs Plugin 선택 가이드

세 가지 확장 방법 중 어떤 것을 선택해야 하는지 기준을 정리합니다.
 
비교 항목 extend/ 폴더 Hook 시스템 Plugin 시스템
Plugin 시스템 ⭐ (쉬움) ⭐⭐ (중간) ⭐⭐⭐ (어려움)
PHP 코드 필요 최소 (파일 생성만) 필요 (콜백 함수) 필요 (등록+구현)
실행 시점 제어 슬롯 3개로 제한 훅 이름으로 정밀 제어 훅 기반으로 구현
우선순위 제어 파일명으로만 제어 priority 매개변수로 제어 priority 매개변수 지원
에러 격리 자동 (파일 단위) 수동 try-catch 필요 수동 try-catch 필요
관리자 UI 없음 없음 관리자 설정 화면 자동 생성
교체 가능성 없음 없음 동일 타입 중 선택/전환 가능
권장 사용 사례 점검모드, IP차단, 로그 콘텐츠 삽입, 값 필터링 에디터, 결제, SMS 교체


7.1 의사결정 트리

커스터마이징이 필요한가?
│
├─ 특정 시점에 코드만 실행하면 되는가?
│   ├─ YES, 복잡한 제어 불필요 → extend/ 폴더 사용
│   └─ YES, 실행 시점/순서를 정밀 제어해야 함 → Hook 사용
│
├─ 에디터/결제/SMS 같은 기능을 교체/추가하려는가?
│   └─ YES → Plugin 시스템 사용
│
└─ 코어 파일을 수정하고 싶은 유혹이 드는가?
    └─ 절대 NO! → 위 세 가지 중 하나로 반드시 해결 가능


7.2 혼합 사용 예제

실제 운영 환경에서는 세 가지를 혼합하여 사용합니다.
 
// plugins/my-plugin/plugin.php

// ① Plugin Registry에 등록 (Plugin 시스템)
dx_register_plugin(array(
    'id' => 'my-plugin', 'type' => 'editor', 'name' => 'My Plugin'
));

// ② Hook으로 전역 동작 추가 (Hook 시스템)
dx_add_hook('dx_bottom', function() {
    echo '<script src="/plugins/my-plugin/main.js"></script>';
}, 10);

// ③ extend/top/에 전역 변수 주입 (extend/ 폴더)
// → extend/top/01_my_plugin_vars.php 파일로 분리


8. 보안 및 주의사항


8.1 DxExtend의 보안 메커니즘

DxExtend 엔진은 다음과 같은 보안 장치를 내장하고 있습니다.

🔒 DxExtend 내장 보안 장치
1. realpath() 검증: 심볼릭 링크 등 경로 조작을 통해 extend/ 외부 파일을 실행하는 것을 차단합니다.
2. 에러 격리: set_error_handler()로 각 파일의 에러를 가로채어 로그에만 기록하고 다음 파일을 계속 실행합니다.
3. 직접 접근 차단: DX_CMS 상수 미정의 시 즉시 종료됩니다.
4. EXTR_SKIP: extract($context, EXTR_SKIP)으로 기존 변수를 덮어쓰지 않습니다.


8.2 개발 시 주의사항

⚠️ 주의 1: extend/ 파일에서 exit/die를 무분별하게 호출하면 안 됩니다.
   점검 모드처럼 의도적인 경우는 OK이지만, 일반 로직에서 exit하면 이후 파일이 실행되지 않습니다.

⚠️ 주의 2: Filter 훅에서 반드시 값을 return 해야 합니다.
   dx_apply_filter()를 통해 값이 전달되는데, 콜백에서 return을 빠뜨리면 null이 반환되어
   데이터가 사라집니다.

⚠️ 주의 3: bottom/ 슬롯에서 헤더를 변경할 수 없습니다.
   bottom은 렌더링 완료 후 실행되므로 header() 함수 호출이 무시됩니다.
   헤더 변경이 필요하면 top/ 슬롯을 사용하세요.

⚠️ 주의 4: DB 집중 작업은 register_shutdown_function()으로 처리하세요.
   방문자 로그, 통계 집계 등은 응답 후 처리해야 사용자 체감 속도가 향상됩니다.
   내장 visit_tracker는 이 패턴을 사용합니다.


8.3 파일 권한 설정

대상 권한 설명
extend/ 폴더 755 읽기/실행 가능, 웹서버 쓰기 불필요
extend/*.php 644 웹서버 실행 가능, 타인 쓰기 불가
plugins/ 폴더 755 읽기/실행 가능
plugins/**/plugin.php 644 안전한 권한


9. 빠른 참조 (Quick Reference)


9.1 가장 자주 쓰는 패턴

// ① 모든 페이지 하단에 스크립트 삽입
dx_add_hook('dx_bottom', function() {
    echo '<script src="/my-script.js"></script>';
}, 10);

// ② 점검 모드 (extend/top/01_maint.php)
if (!dx_is_admin()) { http_response_code(503); exit('점검중'); }

// ③ 현재 라우트 타입 확인 (middle/)
$type = isset($GLOBALS['dx_route']['type']) ? $GLOBALS['dx_route']['type'] : '';

// ④ 에디터 렌더링
dx_render_editor('content', $value, array('height' => 400));

// ⑤ 현재 활성 결제 플러그인 ID
$paymentId = dx_active_plugin('payment');

// ⑥ 훅 등록 확인 후 실행
if (dx_has_hook('my_custom_hook')) {
    dx_run_hook('my_custom_hook', array('key' => 'value'));
}

// ⑦ 훅 제거
dx_remove_hook('dx_bottom', 'my_callback_function');


9.2 내장 전역 함수 참조

함수 설명
dx_is_admin() 현재 사용자가 관리자인지 확인
dx_is_logged_in() 로그인 여부 확인
dx_ip() 클라이언트 IP 반환 (Cloudflare 우선)
dx_config($key, $default) CMS 설정값 조회
dx_log($message, $level) 로그 기록 (info/warning/error)
dx_base_url($path) 기본 URL + 경로 조합
dx_request_uri() 현재 요청 URI 반환
dx_error($message, $code) 에러 페이지 출력 후 종료


9.3 체크리스트: extend 파일 작성 시

1. if (!defined('DX_CMS')) exit; 를 파일 첫 줄에 작성
2. 파일명은 숫자 접두사로 실행 순서 지정 (예: 01_my_feature.php)
3. 에러가 발생해도 안전하게 처리 (try-catch 또는 @ 연산자)
4. 불필요한 전역 변수는 unset()으로 정리
5. DB 작업은 register_shutdown_function()으로 응답 후 처리
6. 비활성화 시 .disabled 확장자 추가 (삭제보다 안전)
7. 테스트: DX_DEBUG 모드에서 에러 로그 확인

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