1. 개요
DXCMS v8.1.0은 단일 진입점(index.php) 기반의 PHP 프레임워크로, PHP 5.6부터 8.x까지의 광범위한 버전 호환성을 유지하면서 모던 CMS 기능을 제공합니다. 모든 HTTP 요청은 index.php를 통과하며, 이 과정에서 엄격히 순서화된 자동 로딩 메커니즘이 동작합니다.
📌 자동 로딩의 핵심 철학
• 단일 진입점(Single Entry Point): 모든 요청이 index.php를 통해 처리됩니다.
• 순서 보장(Ordered Loading): 의존성 순서대로 클래스/함수가 로드됩니다.
• 격리(Isolation): 플러그인•확장 오류가 전체 시스템에 영향을 주지 않습니다.
• 보안 우선(Security First): 보안 클래스가 다른 모든 초기화보다 먼저 실행됩니다.
2. 전체 부트스트랩 흐름
index.php는 요청을 처리하기 위해 아래 5단계를 순서대로 실행합니다. 각 단계는 이전 단계가 완료된 이후에만 실행됩니다.
| 단계 |
이름 |
주요 작업 |
| STEP 1 |
클래스/함수 정의 로드 |
functions.php, DxCache, Secure.php, DxSanitizer, Database, HookManager, PluginRegistry, Auth 및 핵심 클래스 파일들을 require_once로 로드 |
| STEP 2 |
보안 초기화 |
세션 설정 → 세션 시작(조건부) → 보안 헤더 발행 → CSRF 토큰 발급 |
| STEP 3 |
DB 연결 + 설정 로드 |
data/config.php 로드 → DB 연결 → 시크릿 키 주입 |
| STEP 4 |
DB 연결 후 초기화 |
HookManager → PluginRegistry → 플러그인 로드 → DxSite → DxTheme → Auth → DI 컨테이너 → DxExtend → extend/top/ 실행 |
| STEP 5 |
라우팅 + 디스패치 |
routes/*.php 자동 로드 → DxRouter 매칭 시도 → 폴백: 파일 기반 Dispatcher → extend/bottom/ 실행 |
3. STEP 1 — 클래스/함수 정의 로드
STEP 1은 실행 없이 정의만 메모리에 올리는 단계입니다. 실제 동작은 STEP 2~4에서 이루어지며, STEP 1에서는 어떤 로직도 실행되지 않습니다.
3.1 로딩 순서
| # |
파일 |
역할 / 우선 로딩 이유 |
| 1 |
core/functions.php |
전역 헬퍼 함수 정의 (dx_config, dx_log, load_plugins 등) — 다른 클래스가 이 함수들에 의존하므로 최우선 로드 |
| 2 |
core/DxCache.php |
★ 성능 최적화 우선 로드 — config.php 캐싱에 필요하여 DB 연결 전에 반드시 로드 |
| 3 |
core/security/{hash}/Secure.php |
보안 전담 클래스 — 고유 해시 경로로 위치 예측 불가, 설치 시 생성된 경로에서 로드 (폴백: core/Secure.php) |
| 4 |
core/DxSanitizer.php |
입력값 정제(Sanitize) 클래스 |
| 5 |
core/db/Database.php |
DB 연결 클래스 (config.php 로드 전 클래스 정의만) |
| 6 |
core/hook/HookManager.php |
훅 시스템 (dx_add_hook, dx_run_hook 함수 정의) |
| 7 |
core/PluginRegistry.php |
플러그인 타입 등록소 |
| 8 |
core/auth/Auth.php |
인증 클래스 (세션 검증 로직) |
| 9 |
core/DxPoint.php ~ DxBoardSkin.php |
각종 기능 클래스 (포인트, 친구, 샵, 사이트, 테마, SEO, 카테고리, 썸네일, 알림, 회원모니터, 팝업, 게시판스킨, 마켓) |
| 10 |
core/DxSocialAuth.php |
소셜 로그인 클래스 (파일 존재 시에만 로드) |
| 11 |
core/DxExtend.php |
extend/ 폴더 자동 실행 엔진 |
| 12 |
core/router/Router.php |
기존 파일 기반 라우터 |
| 13 |
core/router/Dispatcher.php |
기존 디스패처 |
| 14 |
core/db/QueryBuilder.php |
QueryBuilder + dx_db() 헬퍼 (v6.2.0+) |
| 15 |
core/DxContainer.php |
DI 컨테이너 (v6.2.0+) |
| 16 |
core/DxRouter.php |
클래스 기반 라라벨 스타일 라우터 (v6.2.0+) |
3.2 보안 경로 해시 메커니즘
Secure.php는 설치 시 생성된 16자리 랜덤 해시를 경로로 사용합니다. 이 구조는 파일 경로 예측을 통한 직접 접근 공격을 방어합니다.
// data/config.php에 정의된 보안 경로 예시
define('DX_SECURITY_PATH', 'a1b2c3d4e5f6a7b8'); // 16자리 랜덤 해시
// 실제 로드 경로
// core/security/a1b2c3d4e5f6a7b8/Secure.php
// 폴백 처리 (파일 손실 또는 신규 설치 시)
if (!file_exists($_dxSecurePath)) {
$_dxSecurePath = DX_CORE . '/Secure.php';
}
💡 DxCache 최우선 로드의 이유
DxCache는 STEP 1에서 가장 먼저 로드되는 비-함수 클래스입니다. 이는 다음 STEP 3에서 data/config.php를 로드할 때 설정 값을 파일 캐시로 빠르게 읽어오는 기능이 필요하기 때문입니다. DxCache가 없으면 매 요청마다 config.php를 파싱해야 합니다.
| 경로 패턴 |
세션 필요 이유 |
| /admin |
관리자 인증 상태 확인 |
| /auth |
로그인/로그아웃/회원가입 처리 |
| /view/ |
조회수 중복 방지 (같은 사용자가 새로고침해도 조회수 증가 방지) |
| /api/ |
로그인 여부 확인이 필요한 AJAX API |
| /write |
게시글 작성 권한 확인 |
| /edit |
게시글 수정 권한 확인 |
| /reply |
답글 작성 권한 확인 |
4.2 보안 헤더 자동 발행
Secure::sendSecurityHeaders()는 .htaccess나 web.config 없이도 저가형 공유 호스팅 환경에서 보안 헤더를 보장합니다.
- Content-Security-Policy (CSP)
- X-Frame-Options: SAMEORIGIN
- X-Content-Type-Options: nosniff
- Referrer-Policy
- Permissions-Policy
5. STEP 3 — DB 연결 및 설정 로드
data/config.php를 require_once로 로드합니다. config.php 내부에서 DB 연결($db->connect())이 실행되므로, 이 시점부터 데이터베이스 쿼리가 가능해집니다.
5.1 시크릿 키 주입 (STEP 3-B)
DB 연결 직후, secret_key 기반으로 세션/CSRF/RateLimit 키 이름을 동적으로 도출합니다. 소스코드를 공개해도 키 이름을 예측할 수 없는 구조입니다.
// data/config.php에서 로드되는 핵심 상수들
define('DB_HOST', 'localhost');
define('DB_NAME', 'my_database');
define('DX_SITE_URL', 'https://example.com');
define('DX_SECURITY_PATH', 'a1b2c3d4e5f6a7b8'); // 보안 경로 해시
define('DX_SECRET_KEY', '랜덤64자리키...'); // 시크릿 키
// Secure 클래스가 secret_key로 키 이름을 동적 생성
$_dxSecure->initSecretKeys($_dxSecretVal);
6. STEP 4 — 플러그인 및 확장 자동 로딩
가장 복잡한 단계입니다. DB 연결 완료 후 CMS의 확장 시스템이 순서대로 초기화됩니다.
6.1 초기화 순서
| # |
호출 |
이름 |
설명 |
| 1 |
HookManager::getInstance() |
훅 시스템 인스턴스 생성 |
훅은 플러그인 로딩 전에 반드시 준비되어 있어야 합니다. |
| 2 |
PluginRegistry::getInstance() |
플러그인 타입 등록소 인스턴스 생성 |
editor, payment, captcha, sms 등의 타입 정의 초기화 |
| 3 |
load_plugins() |
플러그인 자동 로드 ★ |
plugins/**/plugin.php 파일을 모두 require_once |
| 4 |
_dx_register_point_hooks() |
포인트 훅 등록 |
게시글 작성/삭제/댓글 등 포인트 지급 훅 일괄 등록 |
| 5 |
DxSite::getInstance() |
멀티사이트 설정 적용 |
도메인별 설정을 $dx_config에 덮어씁니다. |
| 6 |
DxTheme::getInstance() |
테마 엔진 초기화 |
DxSite 이후 실행 (테마 확정 후 초기화) |
| 7 |
Auth::getInstance() |
인증 초기화 |
DB 연결 후 세션 검증, 로그인 상태 확인 |
| 8 |
DxContainer::getInstance()->registerCoreServices() |
DI 컨테이너 핵심 서비스 등록 |
db, auth, secure, cache, hook, seo, site, theme 등록 |
| 9 |
DxExtend::getInstance()->ensureDirs() |
extend/ 폴더 자동 생성 |
top/, middle/, bottom/ 폴더 없으면 자동 생성 |
| 10 |
dx_add_hook(팝업/모니터링) |
시스템 훅 등록 |
팝업 자동출력, 로그인/로그아웃 모니터링, last_seen 갱신 훅 등록 |
| 11 |
DxExtend::runTop() |
extend/top/ 파일 자동 실행 |
사용자 커스텀 코드 실행 (점검모드, IP차단 등) |
6.2 플러그인 자동 로드 (load_plugins)
load_plugins() 함수는 plugins/ 디렉터리의 모든 서브폴더를 탐색하여 plugin.php 파일을 자동으로 로드합니다.
function load_plugins()
{
if (!defined('DX_PLUGINS') || !is_dir(DX_PLUGINS)) return;
$dirs = glob(DX_PLUGINS . '/*', GLOB_ONLYDIR);
if (!$dirs) return;
foreach ($dirs as $dir) {
$f = $dir . '/plugin.php';
if (file_exists($f)) {
require_once $f; // 각 플러그인의 진입점 로드
}
}
// DXB CSS 엔진 자동 주입
_dx_inject_dxb_css();
// 에디터 훅 브릿지 등록
dx_add_hook('dx_editor_init', function($args) { ... }, 10);
}
플러그인 디렉터리 구조:
plugins/
my-editor/
plugin.php ← load_plugins()가 자동 로드하는 진입점
editor.js
style.css
kg-inicis/
plugin.php
inicis.php
6.3 PluginRegistry — 플러그인 타입 등록소
각 플러그인은 plugin.php 내에서 dx_register_plugin()을 호출하여 자신의 "타입"을 등록합니다. 관리자 설정 화면에 해당 타입의 선택 박스가 자동으로 나타납니다.
| 타입 ID |
라벨 |
설정 키 |
용도 |
| editor |
에디터 |
active_editor |
TinyMCE, CKEditor, Quill 등 게시글 편집기 |
| payment |
결제 모듈 |
active_payment |
KG이니시스, 토스페이 등 PG사 연동 |
| captcha |
CAPTCHA |
active_captcha |
reCAPTCHA v2/v3, hCaptcha, Turnstile 등 |
| sms |
SMS 발송 |
active_sms |
알리고, 네이버 클라우드 SMS 등 |
| social_login |
소셜 로그인 |
active_social_login |
카카오, 네이버, 구글, GitHub OAuth |
| socket |
실시간 소켓 |
active_socket |
WebSocket 기반 실시간 채팅·모니터링 |
6.4 DxContainer — 경량 DI 컨테이너
DxContainer는 Laravel의 Service Container와 동일한 철학으로 설계된 PHP 5.6 호환 경량 DI 컨테이너입니다. 기존 getInstance() 싱글턴 패턴을 유지하면서 추가적인 의존성 주입을 지원합니다.
자동 등록되는 핵심 서비스
| 서비스 키 |
클래스 |
별칭 / 설명 |
| db |
Database::getInstance() |
별칭: database — 쿼리빌더, 직접 쿼리 모두 지원 |
| auth |
Auth::getInstance() |
로그인 상태, 회원 정보 접근 |
| secure |
Secure::getInstance() |
보안 헤더, CSRF, 세션 관리 |
| cache |
DxCache (클래스명) |
static 메서드 기반 파일 캐시 |
| hook |
HookManager::getInstance() |
별칭: hooks — 훅 등록/실행 |
| seo |
DxSeo (클래스명) |
title, description, OG 태그 등 |
| site |
DxSite::getInstance() |
멀티사이트 설정 관리 |
| theme |
DxTheme::getInstance() |
테마 경로, 변수 관리 |
컨트롤러 자동 로드 경로
DxContainer::call()로 컨트롤러를 호출할 때, 클래스가 없으면 다음 경로를 순서대로 탐색합니다.
- controllers/{ClassName}.php
- controllers/{lowercase_name}.php (예: BoardController → board.php)
- core/controllers/{ClassName}.php
- plugins/*/controllers/{ClassName}.php (플러그인 컨트롤러)
// 컨트롤러 자동 의존성 주입 사용 예시
dx_app()->call('BoardController@index', ['slug' => 'free']);
// 서비스 바인딩 (플러그인에서)
dx_app()->singleton('mailer', function() {
return new MyMailer(dx_config('smtp_host'));
});
// 서비스 꺼내기
$mailer = dx_make('mailer');
6.5 DxExtend — extend/ 폴더 자동 실행 엔진
DxExtend는 파일을 지정된 폴더에 넣는 것만으로 코드를 자동 실행할 수 있는 노-코드 확장 시스템입니다. 훅(Hook)이 개발자 코드에서 등록하는 방식인 것과 달리, DxExtend는 파일 배치만으로 동작합니다.
| 슬롯 |
실행 시점 |
주요 용도 |
| extend/top/ |
모든 초기화(DB·세션·Auth) 완료 직후, 라우팅 전 |
점검 모드, IP 차단, 커스텀 인증, 글로벌 변수 주입, 방문자 제한 |
| extend/middle/ |
라우트 확정 직후, 실제 핸들러 실행 전 |
방문자 로그, A/B 테스트, 접근 로그, 라우트별 처리 ($GLOBALS['dx_route'] 사용 가능) |
| extend/bottom/ |
렌더링 완료 후, ob_end_flush() 직전 |
캐시 저장, 성능 로그, 정리 작업 |
파일 수집 및 실행 규칙
- 폴더 내 *.php 파일을 파일명 오름차순으로 자동 수집
- 파일명으로 실행 순서 제어 가능: 01_init.php → 02_log.php → 03_custom.php
- 하위 폴더도 1단계까지 재귀 탐색
- 에러 발생해도 다른 파일 계속 실행 (set_error_handler로 격리)
- 보안: extend/ 내부 파일만 실행 (realpath 검증으로 경로 탈출 방지)
extend/
top/
01_maintenance.php // 점검 모드 체크
02_ip_block.php // IP 차단
03_custom_auth.php // 커스텀 인증
middle/
01_access_log.php // 방문자 로그
bottom/
01_cache_save.php // 캐시 저장
02_perf_log.php // 성능 로그
7. STEP 5 — 라우팅 및 디스패치
모든 초기화가 완료된 후 요청 URL에 맞는 핸들러를 찾아 실행합니다. DXCMS는 두 가지 라우팅 방식을 지원하며, DxRouter(클래스 기반) → Dispatcher(파일 기반) 순으로 폴백됩니다.
7.1 routes/ 폴더 자동 로드
routes/ 폴더의 모든 .php 파일을 파일명 오름차순으로 자동 로드합니다.
$_dxRouteDir = DX_ROOT . '/routes';
$_dxRouteFiles = glob($_dxRouteDir . '/*.php');
sort($_dxRouteFiles); // 파일명 오름차순 정렬
foreach ($_dxRouteFiles as $_dxRouteFile) {
require_once $_dxRouteFile;
}
// routes/web.php 예시
DxRouter::get('/mypage/dashboard', 'MemberController@dashboard')
->middleware('auth');
DxRouter::post('/api/update', 'MemberController@update')
->middleware(['auth', 'csrf']);
7.2 이중 라우팅 폴백 구조
| 라우터 |
동작 방식 |
설명 |
| DxRouter (1순위) |
routes/*.php에서 등록된 URI 패턴 매칭 |
Laravel 스타일 — GET/POST/PUT/PATCH/DELETE, 그룹, 미들웨어, 리소스 라우트 지원 (v6.2.0+) |
| Dispatcher (폴백) |
URI를 파일 경로로 변환하여 해당 PHP 파일 실행 |
기존 파일 기반 라우팅 유지 — DxRouter에 매칭 라우트가 없을 때만 실행 |
7.3 DxRouter 지원 HTTP 메서드
| 메서드 |
함수 |
설명 |
| GET |
DxRouter::get($uri, $action) |
조회 요청 |
| POST |
DxRouter::post($uri, $action) |
생성 요청 |
| PUT |
DxRouter::put($uri, $action) |
전체 수정 |
| PATCH |
DxRouter::patch($uri, $action) |
부분 수정 |
| DELETE |
DxRouter::delete($uri, $action) |
삭제 요청 |
| GET+POST |
DxRouter::any($uri, $action) |
GET과 POST 동시 등록 |
| REST |
DxRouter::resource($uri, $controller) |
index/show/store/update/destroy 자동 등록 |
8. HookManager — 훅 시스템
HookManager는 WordPress 스타일의 훅/필터 시스템을 구현합니다. 플러그인과 테마가 CMS 동작을 수정할 수 있는 핵심 확장 메커니즘입니다.
8.1 훅 유형
| 유형 |
함수 |
설명 |
| Action |
dx_add_hook() / dx_run_hook() |
반환값 없이 동작을 수행. 예: 로그인 후 포인트 지급, 게시글 저장 후 알림 발송 |
| Filter |
dx_add_filter() / dx_apply_filter() |
값을 변형하여 반환. 예: 게시글 내용 필터링, URL 변환, HTML 정제 |
8.2 표준 훅 포인트
각 페이지 렌더링 시 dx_hook_top(), dx_hook_middle(), dx_hook_bottom() 함수가 자동으로 호출됩니다. 이 함수들은 전역 훅 외에 페이지 타입별, 슬러그별 훅도 연쇄 실행합니다.
| 함수 |
실행되는 훅 |
예시 |
| dx_hook_top($context) |
dx_top dx_{type}_top dx_page_{slug}_top |
dx_top, dx_board_top, dx_page_free_top — 전역→타입별→슬러그별 순으로 실행 |
| dx_hook_middle($context) |
dx_middle dx_{type}_middle dx_page_{slug}_middle |
dx_middle, dx_board_middle, dx_page_free_middle |
| dx_hook_bottom($context) |
dx_bottom dx_{type}_bottom dx_page_{slug}_bottom |
dx_bottom, dx_board_bottom, dx_page_free_bottom |
8.3 우선순위 정렬
dx_add_hook()의 세 번째 파라미터로 priority를 지정합니다. 숫자가 낮을수록 먼저 실행됩니다. 기본값은 10입니다.
// 우선순위 예시
dx_add_hook('dx_after_login', function($args) {
// priority 5: 다른 훅보다 먼저 실행
record_login_log($args['user']['id']);
}, 5);
dx_add_hook('dx_after_login', function($args) {
// priority 20: 나중에 실행
DxMemberMonitor::onLogin($args['user']['id']);
}, 20);
9. 자동 로딩 구조 전체 요약
아래 다이어그램은 HTTP 요청부터 응답까지의 전체 자동 로딩 흐름을 요약합니다.
HTTP 요청
│
▼
┌─────────────────────────────────────────────────────────┐
│ index.php (단일 진입점) │
│ │
│ [STEP 1] 클래스/함수 정의 로드 │
│ functions.php → DxCache → Secure → DxSanitizer │
│ → Database → HookManager → PluginRegistry → Auth │
│ → 기능 클래스들 → DxExtend → Router → DxContainer │
│ │
│ [STEP 2] 보안 초기화 │
│ Secure::initSession() → startSession() → 보안헤더 │
│ → CSRF 토큰 발급 │
│ │
│ [STEP 3] DB 연결 + 설정 로드 │
│ data/config.php 로드 → DB 연결 → 시크릿키 주입 │
│ │
│ [STEP 4] DB 연결 후 초기화 │
│ HookManager → PluginRegistry → load_plugins() │
│ → DxSite → DxTheme → Auth → DxContainer │
│ → DxExtend::ensureDirs() → extend/top/ 실행 │
│ │
│ [STEP 5] 라우팅 + 디스패치 │
│ routes/*.php 로드 → DxRouter::dispatch() │
│ → 매칭 실패 시: Dispatcher::dispatch() (폴백) │
│ → extend/bottom/ 실행 │
│ → ob_end_flush() │
└─────────────────────────────────────────────────────────┘
│
▼
HTTP 응답
✅ 핵심 설계 원칙 요약
1) 보안 우선: Secure.php는 DB 연결보다 먼저, 고유 해시 경로에서 로드
2) 성능 최적화: DxCache 최우선 로드, GET 요청 조건부 세션 생략
3) 확장성: 플러그인(plugin.php), 훅(HookManager), 확장(extend/) 세 가지 계층
4) 하위 호환: 파일 기반 라우팅(Dispatcher)은 유지, DxRouter가 먼저 시도
5) 격리: DxExtend의 safeExec()으로 확장 파일 오류가 시스템에 전파되지 않음
10. 자동 로딩 관련 전역 헬퍼 함수
| 함수 |
정의 파일 |
설명 |
| dx_app() |
DxContainer.php |
DxContainer 인스턴스 반환 |
| dx_make($abstract) |
DxContainer.php |
컨테이너에서 서비스 꺼내기 |
| dx_bind($abstract, $factory) |
DxContainer.php |
팩토리 바인딩 등록 |
| dx_singleton($abstract, $factory) |
DxContainer.php |
싱글턴 바인딩 등록 |
| dx_add_hook($name, $cb, $priority) |
HookManager.php |
훅 Action 등록 |
| dx_run_hook($name, $args) |
HookManager.php |
훅 Action 실행 |
| dx_add_filter($name, $cb, $priority) |
HookManager.php |
훅 Filter 등록 |
| dx_apply_filter($name, $value) |
HookManager.php |
훅 Filter 실행 |
| dx_remove_hook($name, $callback) |
HookManager.php |
훅 제거 |
| dx_has_hook($name) |
HookManager.php |
훅 등록 여부 확인 |
| load_plugins() |
functions.php |
plugins/**/plugin.php 자동 로드 |
| dx_register_plugin($info) |
PluginRegistry.php |
플러그인 타입 등록 |
| dx_extend_top($ctx) |
DxExtend.php |
extend/top/ 수동 실행 |
| dx_extend_middle($ctx) |
DxExtend.php |
extend/middle/ 수동 실행 |
| dx_extend_bottom($ctx) |
DxExtend.php |
extend/bottom/ 수동 실행 |
| dx_hook_top($context) |
HookManager.php |
페이지 최상단 표준 훅 실행 |
| dx_hook_middle($context) |
HookManager.php |
페이지 중간 표준 훅 실행 |
| dx_hook_bottom($context) |
HookManager.php |
페이지 최하단 표준 훅 실행 |