1. 개요 및 설계 철학
DXCMS는 "DX(DesignOneX) 프레임워크 위에 CMS 기능이 층위별로 올라가는" 계층형 아키텍처를 따릅니다. 각 계층은 독립적으로 교체•확장 가능하며, 하위 계층은 상위 계층에 대해 알지 못합니다. 이 원칙을 통해 CMS는 저가형 공유 호스팅부터 동접 1만 명 대규모 서버까지 단일 코드베이스로 동작합니다.
1.1 핵심 설계 원칙
| 원칙 |
내용 |
| 단일 진입점 |
index.php가 모든 HTTP 요청을 수신. URL Rewrite 없는 환경에서도 ?_url= 방식으로 동일하게 동작 |
| 계층 분리 |
Infrastructure(DB·캐시·보안) → DX Core(라우터·훅·DI) → CMS Services(인증·게시판·SEO) → Presentation(테마·스킨) 4계층 엄격 분리 |
| 상향 의존 금지 |
하위 계층은 상위 계층을 import하지 않음. 하위에서 상위로의 통신은 훅(Hook) 시스템을 통해서만 허용 |
| 광범위 PHP 호환 |
PHP 5.6~8.4 단일 코드베이스. 삼항 연산자(?:)·array()·dirname(__FILE__) 사용, fn()·::class·반환타입 힌트 미사용 |
| 폴백 체인 |
테마 파일, 게시판 스킨, 플러그인 모두 "현재 → 커스텀 → 기본값" 3단계 폴백 체인으로 무결성 보장 |
| 격리와 안전 |
DxExtend·PluginRegistry·DxBoardSkin은 외부 코드를 안전하게 실행(safeExec). 오류가 전체 시스템에 전파되지 않음 |
2. 전체 계층 구조 (아키텍처 다이어그램)
DXCMS의 아키텍처는 4개의 주요 계층으로 구성됩니다. 최하단의 Infrastructure 계층이 가장 먼저 초기화되고, 상위 계층은 하위 계층이 완전히 준비된 후에만 동작합니다.
┌────────────────────────────────────────────────────────────────┐
│ LAYER 4 — Presentation Layer │
│ 테마(DxTheme) • 게시판스킨(DxBoardSkin) • 카테고리스킨 │
│ themes/default/ • boards/skins/ • pages/ • extend/ │
├────────────────────────────────────────────────────────────────┤
│ LAYER 3 — CMS Service Layer │
│ DxSite • Auth • DxSeo • DxCategory • DxNotification │
│ DxPoint • DxShop • DxFriend • DxMemberMonitor • DxPopup │
│ DxSms • DxMailer • DxCaptcha • DxSocialAuth • DxMarket │
├────────────────────────────────────────────────────────────────┤
│ LAYER 2 — DX Core Layer │
│ HookManager • PluginRegistry • DxContainer(DI) │
│ Router • Dispatcher • DxRouter • DxExtend │
│ DxCache • DxSanitizer • QueryBuilder • DxMigration │
├────────────────────────────────────────────────────────────────┤
│ LAYER 1 — Infrastructure Layer │
│ Secure.php • Database(PDO) • functions.php • DxCache │
│ data/config.php • PHP Runtime • Web Server • MySQL/MariaDB │
└────────────────────────────────────────────────────────────────┘
▲ ▲ ▲
HTTP Request (index.php)
| 계층 |
이름 |
핵심 컴포넌트 |
역할 |
| Layer 4 |
Presentation |
DxTheme, DxBoardSkin, 테마파일, 스킨파일 |
HTML 렌더링, UI 표현, 폴백 체인 |
| Layer 3 |
CMS Service |
DxSite, Auth, DxSeo, DxCategory, DxPoint 외 |
비즈니스 로직, 회원/게시판/SEO/포인트 |
| Layer 2 |
DX Core |
HookManager, DxContainer, Router, DxCache |
라우팅, 훅, DI, 캐시, 확장 메커니즘 |
| Layer 1 |
Infrastructure |
Secure.php, Database, config.php |
보안, DB 연결, 설정, PHP 런타임 |
3. Layer 1 — Infrastructure (기반 인프라)
모든 CMS 기능의 토대가 되는 계층입니다. 이 계층이 초기화되지 않으면 상위 계층은 전혀 동작하지 않습니다. index.php의 STEP 1~3에 해당합니다.
3.1 Secure.php — 보안 전담 클래스
보안 관련 모든 기능이 단일 파일에 집중되어 있습니다. 보안 패치 시 이 파일만 수정하면 됩니다. 설치 시 고유 해시 경로(core/security/{hash}/Secure.php)에 배치되어 파일 경로 예측 공격을 방어합니다.
| 기능 |
설명 |
| 세션 보안 |
HttpOnly · Secure · SameSite=Lax 쿠키 설정, 세션 고정 공격 방지 |
| CSRF 토큰 |
발급(csrfToken()) / 검증(csrfCheck()). 모든 POST 요청에 자동 적용 |
| 보안 헤더 |
X-Frame-Options, X-Content-Type-Options, Referrer-Policy 자동 발행 |
| XSS 방어 |
출력 이스케이프 함수(dx_esc()) 제공 |
| 경로 순회 방어 |
realpath() 검증으로 디렉터리 탈출 공격 차단 |
| 업로드 검증 |
MIME 타입 + 확장자 이중 검증, getimagesize() 실제 이미지 검증, 이중확장자 차단 |
| 비밀번호 해시 |
bcrypt 해싱, PHP 5.6 폴백 포함 |
| Rate Limiting |
시크릿 키 기반 동적 세션 키 이름으로 요청 제한 |
| 시크릿 키 주입 |
64자리 랜덤 secret_key로 세션/CSRF/RateLimit 키 이름을 동적 도출 |
3.2 Database — PDO 래퍼 (싱글턴)
MySQL/MariaDB PDO 연결을 싱글턴으로 관리합니다. data/config.php 로드 시 자동 연결됩니다. 준비된 구문(Prepared Statement)을 강제하여 SQL Injection을 원천 차단합니다.
| 메서드 / 기능 |
설명 |
| Database::getInstance() |
PDO 싱글턴 반환. 요청 내 연결 재사용 |
| $db->rows($sql, $params) |
다중 행 SELECT. PDO Prepared Statement 사용 |
| $db->row($sql, $params) |
단일 행 SELECT (LIMIT 1) |
| $db->value($sql, $params) |
단일 값 반환 (COUNT, MAX 등) |
| $db->insertRow($table, $data) |
INSERT. 배열을 자동 바인딩 |
| $db->table($name) |
테이블명에 프리픽스 자동 추가 (dx_prefix) |
| $db->tableExists($table) |
테이블 존재 여부 확인 (마이그레이션 호환) |
| $db->pdo() |
원시 PDO 객체 반환 (고급 용도) |
| QueryBuilder + dx_db() |
라라벨 스타일 체이닝: dx_db('boards')->where('status',1)->get() |
3.3 data/config.php — 설정 로드 및 DB 연결 트리거
📋 config.php가 로드되는 순간 DB 연결이 실행됩니다.
define('DB_HOST', 'localhost'); // DB 호스트
define('DB_NAME', 'my_database'); // DB 이름
define('DX_SITE_URL', 'https://...'); // 사이트 URL
define('DX_SECURITY_PATH', 'a1b2...'); // 보안 경로 해시 (16자리)
define('DX_SECRET_KEY', '...'); // 64자리 랜덤 시크릿 키
// $db->connect() 자동 실행됨
4. Layer 2 — DX Core (프레임워크 핵심)
DX Core는 CMS 기능과 인프라 사이의 미들웨어 역할을 합니다. 훅 시스템, DI 컨테이너, 라우터, 캐시 등 모든 CMS 서비스가 의존하는 공통 메커니즘을 제공합니다.
4.1 HookManager — 훅/이벤트 시스템
WordPress Action/Filter와 동일한 철학의 훅 시스템입니다. CMS의 모든 확장 포인트가 훅을 통해 제공됩니다. 플러그인과 테마는 훅을 통해 CMS 동작을 수정하거나 기능을 추가합니다.
| 유형 |
등록 |
실행 |
반환값 |
| Action (액션) |
dx_add_hook($name, $cb, $priority) |
dx_run_hook($name, $args) |
없음. 부수효과 실행 용도 |
| Filter (필터) |
dx_add_filter($name, $cb, $priority) |
dx_apply_filter($name, $value) |
변형된 값 반환. 데이터 변환 용도 |
주요 시스템 훅 포인트
| 훅 이름 |
발생 위치 |
용도 |
| dx_head |
<head> 내부 |
CSS/JS 추가 |
| dx_body_bottom |
</body> 직전 |
스크립트, 팝업 자동출력 |
| dx_top / dx_middle / dx_bottom |
페이지 상/중/하단 |
콘텐츠 삽입 |
| dx_after_login |
로그인 성공 직후 |
포인트 지급, 모니터링 |
| dx_after_logout |
로그아웃 직후 |
세션 정리 |
| dx_board_list_context |
게시판 목록 컨텍스트 생성 후 |
목록 데이터 변형 |
| dx_board_view_context |
게시글 보기 컨텍스트 생성 후 |
보기 데이터 변형 |
| dx_board_after_save |
게시글 저장 후 |
알림, 포인트, 인덱싱 |
| dx_editor_init |
에디터 초기화 요청 |
에디터 플러그인 렌더 |
| dx_editor_render |
에디터 실제 렌더 |
플러그인이 등록 |
| dx_extend_top/middle/bottom |
extend/ 파일 실행 후 |
추가 처리 훅 |
4.2 DxContainer — 경량 DI 컨테이너
Laravel의 Service Container와 동일한 철학의 PHP 5.6 호환 경량 DI 컨테이너입니다. 기존 getInstance() 싱글턴 패턴을 유지하면서 추가적인 의존성 주입을 지원합니다.
| 메서드 |
설명 |
| bind($abstract, $factory) |
팩토리 바인딩 — make() 호출마다 새 인스턴스 생성 |
| singleton($abstract, $factory) |
싱글턴 바인딩 — 첫 make() 이후 동일 인스턴스 반환 |
| instance($abstract, $obj) |
이미 생성된 인스턴스 직접 등록 (항상 싱글턴) |
| alias($abstract, $alias) |
별칭 등록. dx_app()->make("db") === dx_app()->make("database") |
| make($abstract) |
서비스 꺼내기. 바인딩 없으면 클래스명으로 직접 인스턴스화 |
| call('Controller@method', $params) |
컨트롤러 메서드 자동 호출 + 의존성 주입 |
| registerCoreServices() |
DB, Auth, Secure, Cache, Hook, SEO, Site, Theme 자동 등록 |
4.3 Router + Dispatcher — 라우팅 엔진
DXCMS는 두 가지 라우팅 방식을 병행합니다. DxRouter(라라벨 스타일 클래스 기반)가 먼저 시도되고, 매칭 실패 시 기존 Router+Dispatcher(파일 기반) 방식으로 폴백됩니다.
Router — URL 파싱 및 라우트 결정
| URL 패턴 |
TYPE 상수 |
처리 |
| / |
TYPE_HOME |
themes/{테마}/page/home.php → DB is_home=1 → pages/home.php (우선순위 폴백) |
| /{slug} |
TYPE_PAGE |
DB dx_pages 조회 → page_type/location에 따라 렌더 |
| /{board_key}/list |
TYPE_BOARD |
boards/handler.php → DxBoardSkin 폴백 체인 |
| /{board_key}/view/{id} |
TYPE_BOARD |
게시글 상세. BIGINT 오버플로우 방지(문자열 유지) |
| /{board_key}/write |
TYPE_BOARD |
게시글 작성. CSRF 검증 |
| /admin/{action} |
TYPE_ADMIN |
admin/{action}/index.php 실행 |
| /auth/{action} |
TYPE_AUTH |
core/auth/{action}.php 실행 |
| /api/{action} |
TYPE_API |
core/api/{action}.php 실행 |
| /search |
TYPE_SEARCH |
core/search/handler.php 실행 |
| /sitemap.xml |
TYPE_API |
core/api/sitemap.php 실행 |
| /robots.txt |
TYPE_API |
core/api/robots.php 실행 |
Dispatcher — 라우트 → 핸들러 실행
Router가 결정한 route 객체를 받아 실제 핸들러 파일을 실행합니다. 라우트 확정 직후 extend/middle/을 실행한 후 switch 문으로 타입별 dispatch 함수를 호출합니다.
// Dispatcher::dispatch() 핵심 흐름
$this->route = $this->router->resolve();
$GLOBALS['dx_route'] = $this->route;
// extend/middle/ 실행 (라우트 확정 직후)
DxExtend::getInstance()->runMiddle(['type' => $this->route['type'], ...]);
switch ($this->route['type']) {
case Router::TYPE_HOME: $this->dispatchHome(); break;
case Router::TYPE_PAGE: $this->dispatchPage(); break;
case Router::TYPE_BOARD: $this->dispatchBoard(); break;
case Router::TYPE_ADMIN: $this->dispatchAdmin(); break;
case Router::TYPE_AUTH: $this->dispatchAuth(); break;
case Router::TYPE_API: $this->dispatchApi(); break;
case Router::TYPE_SEARCH: $this->dispatchSearch(); break;
default: $this->dispatch404(); break;
}
4.4 DxCache — 파일/APCu 이중 드라이버 캐시
APCu 설치 여부를 자동 감지하여 드라이버를 선택합니다. 저가형 공유 호스팅에서는 파일 캐시를 사용하고, APCu가 있으면 메모리 캐시로 자동 전환됩니다.
| 캐시 항목 |
캐시 키 |
TTL |
무효화 시점 |
| 전체 설정 |
dx_settings |
5분 (300초) |
관리자 설정 저장 → 전체 flush |
| 게시판 목록 |
board_list_* |
1분 (60초) |
게시글 작성/수정/삭제 |
| 사이트맵 XML |
sitemap_* |
10분 (600초) |
게시글 작성/삭제 |
| 멀티사이트 설정 |
site_{md5(domain)} |
5분 (300초) |
사이트 설정 저장 |
| 카테고리 목록 |
cat_board_{id} |
5분 (300초) |
카테고리 변경 |
4.5 DxExtend — extend/ 폴더 자동 실행 엔진
파일을 지정된 폴더에 놓는 것만으로 코드를 자동 실행할 수 있는 노-코드 확장 시스템입니다. 플러그인이나 훅 없이도 CMS 동작을 수정할 수 있습니다.
- extend/top/ ← 초기화 완료 직후 (점검모드, IP차단, 커스텀 인증)
- extend/middle/ ← 라우트 확정 직후 (방문자 로그, A/B 테스트)
- extend/bottom/ ← 렌더링 완료 후 (캐시 저장, 성능 로그)
규칙:
파일명 오름차순 실행 • 하위 폴더 1단계 재귀 탐색
에러 발생해도 다음 파일 계속 실행 (격리)
보안: realpath() 검증으로 경로 탈출 공격 차단
5. Layer 3 — CMS Service Layer (CMS 서비스 계층)
비즈니스 로직의 핵심 계층입니다. DB 연결과 DX Core가 준비된 후 초기화됩니다. 각 서비스 클래스는 싱글턴 패턴으로 구현되어 요청 내에서 한 번만 인스턴스화됩니다.
5.1 CMS 서비스 클래스 전체 목록
| 클래스 |
싱글턴 |
역할 |
| DxSite |
싱글턴 |
멀티사이트 관리. 도메인별 설정(theme, site_name, menu_group)을 $dx_config에 오버라이드 |
| Auth |
싱글턴 |
세션 기반 인증. 로그인/로그아웃/권한 확인. isLoggedIn(), get('id') 등 |
| DxSeo |
static |
SEO 메타 자동 생성. OG, Twitter Card, JSON-LD, Canonical URL, noindex 처리 |
| DxCategory |
static |
게시판 카테고리. DB 기반 무한 계층, 목록/뷰 독립 표시, 배지 색상 |
| DxNotification |
static |
실시간 알림. DB 저장 + 소켓 서버 HTTP push. 댓글/답글/친구/스크랩/쪽지 |
| DxPoint |
static |
포인트/경험치/레벨. 로그인·작성·댓글·좋아요 등 이벤트별 자동 적립 |
| DxShop |
싱글턴 |
유료 콘텐츠 구매. 결제 플러그인 연동 추상화 |
| DxFriend |
static |
친구 관계 관리. 친구 추가/삭제/목록 |
| DxMemberMonitor |
static |
회원 모니터링. 로그인/로그아웃/last_seen/접속자 추적 |
| DxPopup |
static |
팝업 자동 출력. dx_body_bottom 훅에서 자동 렌더 |
| DxSms |
싱글턴 |
SMS 발송 추상화. 알리고/NCP/CoolSMS/Twilio 드라이버 |
| DxMailer |
싱글턴 |
이메일 발송 추상화. SMTP/Sendmail/PHP mail() 드라이버 |
| DxCaptcha |
static |
CAPTCHA 추상화. reCAPTCHA v2/v3, hCaptcha, Turnstile, 내장 |
| DxSocialAuth |
싱글턴 |
소셜 로그인. 카카오/네이버/구글/GitHub OAuth2 |
| DxMarket |
싱글턴 |
플러그인/테마 마켓 클라이언트. SHA-256 해시 검증 설치 |
| DxSanitizer |
static |
입력값 정제. HTML XSS 필터, 파일명 정규화 |
| DxThumb |
static |
이미지 썸네일. GD 기반 리사이즈, WebP 지원, 비율 유지 |
5.2 DxSite — 멀티사이트 관리자
같은 DB에 여러 도메인을 운영할 때 도메인별 독립 설정을 적용합니다. 요청이 들어오면 HTTP_HOST를 감지하여 dx_sites 테이블에서 해당 도메인 설정을 로드하고 전역 $dx_config를 오버라이드합니다.
| 동작 단계 |
설명 |
| ① 도메인 감지 |
HTTP_HOST에서 포트 번호 제거 후 소문자 정규화 |
| ② 캐시 조회 |
site_{md5(domain)} 키로 DxCache 조회 (TTL 300초). 히트 시 DB 조회 생략 |
| ③ DB 조회 |
dx_sites WHERE domain=? AND status=1. 미등록 도메인은 null 캐시 |
| ④ 설정 오버라이드 |
site_name, site_description, site_url, theme, language, timezone, footer_text를 dx_config에 덮어쓰기 |
| ⑤ menu_group 주입 |
active_menu_group을 도메인별 메뉴 그룹으로 설정 |
| ⑥ extra_config |
JSON 필드. 추가 설정 자유롭게 확장 가능 |
멀티사이트 도메인 독립 항목
사이트명 • 설명 • URL • 테마 • 언어 • 시간대 • 푸터 텍스트 • 메뉴 그룹
SEO 사이트맵 (각 도메인이 자신의 URL 기준으로 독립 생성)
게시판/페이지의 site_domain 컬럼으로 도메인별 콘텐츠 분리
5.3 Auth — 세션 기반 인증
DB 연결 후 세션을 검증하여 로그인 상태를 확인합니다. DI 컨테이너에 auth 키로 등록되어 dx_make('auth')로 어디서나 접근 가능합니다.
| 메서드 |
설명 |
| isLoggedIn() |
로그인 여부. 세션 검증 완료된 결과 |
| get($key, $default) |
로그인한 회원 정보 접근. get('id'), get('level') 등 |
| check($permission) |
권한 확인. level, group, 게시판별 권한 등 |
| login($loginId, $password) |
로그인 처리. bcrypt 검증, 세션 갱신, dx_after_login 훅 실행 |
| logout() |
로그아웃. 세션 파기, dx_after_logout 훅 실행 |
5.4 DxSeo — SEO 메타 자동 생성
페이지 타입(게시글 보기 / 목록 / 페이지 / 홈)에 따라 SEO 메타 데이터를 자동으로 생성합니다. 테마 레이아웃(layout/main.php)에서 DxSeo::render()를 호출하여 출력합니다.
| build() 타입 |
자동 생성 내용 |
| board_view |
title=게시글제목—게시판명|사이트명, description=본문앞150자, image=첫번째img src, JSON-LD Article, 비밀글→noindex |
| board_list |
title=게시판명|사이트명, 카테고리/검색 포함, 2페이지~=페이지번호 추가, 검색결과→noindex |
| page |
title=페이지명|사이트명, 페이지 description 사용 |
| home |
title=사이트명, site_description 사용, JSON-LD WebSite |
5.5 DxPoint — 포인트/경험치/레벨 엔진
회원 활동에 따라 포인트와 경험치를 자동으로 지급합니다. 레벨 설정은 DB dx_level_config 테이블에서 관리자가 커스터마이징 가능합니다. 테이블 없으면 하드코딩 기본값(1~15레벨)으로 폴백합니다.
| 이벤트 |
포인트 |
경험치 |
설명 |
| signup |
10 |
20 |
회원가입 |
| login |
1 |
2 |
로그인 |
| write |
5 |
10 |
게시글 작성 |
| comment |
2 |
5 |
댓글 작성 |
| like_recv |
1 |
2 |
좋아요 받음 |
| scrap_recv |
1 |
0 |
스크랩 받음 |
5.6 DxNotification — 실시간 알림 엔진
알림은 DB 저장과 소켓 서버 HTTP push를 동시에 처리합니다. 소켓 플러그인(dx-socket)이 비활성 상태여도 DB 기반 알림은 정상 동작합니다. 알림 실패가 댓글 작성 등 본 기능을 막지 않도록 모든 예외를 흡수합니다.
알림 타입:
comment (댓글) • comment_reply (답글) • friend (친구추가), scrap (스크랩) • memo (쪽지)
동작 흐름:
1. DxNotification::add() 호출
2. DB dx_notifications 테이블에 저장
3. 소켓서버 활성 시: POST https://socket-host:port/push → 실시간 전송
4. 소켓서버 없으면: 클라이언트가 폴링으로 조회 (API /api/notification)
6. Layer 4 — Presentation Layer (프레젠테이션 계층)
사용자에게 보이는 HTML을 생성하는 최상위 계층입니다. 테마 엔진(DxTheme)과 게시판 스킨 엔진(DxBoardSkin)이 폴백 체인을 통해 파일을 해석하고 렌더링합니다.
6.1 DxTheme — 테마 엔진
테마 파일을 해석하고 폴백 체인을 실행하는 핵심 엔진입니다. 현재 테마에 파일이 없으면 default 테마에서 자동으로 찾습니다.
| 메서드 |
역할 |
| resolve($relPath) |
테마 파일 경로 반환 (현재 테마 → default 폴백) |
| resolveBoardSkin($skin, $action) |
게시판 스킨 파일 해석 (4단계 폴백 체인) |
| resolvePart($name) |
테마 파셜 파일 해석 (pagination, breadcrumb 등) |
| resolveLatestSkin($skin) |
최신글 위젯 스킨 해석 (list/card/simple 등) |
| meta($themeName) |
theme.json 메타정보 반환 (이름, 버전, 작성자, 옵션) |
| option($key, $default) |
테마 옵션 값 반환 (DB theme_{테마명}_{키} 저장) |
| assetUrl($path) |
테마 에셋 URL 반환 |
| allThemes() |
설치된 모든 테마 목록 반환 (관리자용) |
테마 파일 폴백 체인
// DxTheme::resolve() 폴백 체인
1. themes/{현재 테마}/{relPath} ← 현재 테마 파일 (최우선)
2. themes/default/{relPath} ← default 테마 폴백
3. null 반환 → 404 ← 어디에도 없음
// DxTheme::resolveBoardSkin() 폴백 체인 (4단계)
1. themes/{테마}/board/{스킨}/{액션}.php
2. themes/{테마}/board/{액션}.php
3. themes/default/board/{스킨}/{액션}.php
4. themes/default/board/{액션}.php ← 최종 폴백
6.2 DxBoardSkin — 게시판 스킨 엔진
게시판의 외형을 담당하는 스킨 시스템입니다. boards/skins/ 폴더에 스킨을 추가하여 게시판별로 완전히 다른 UI를 구현할 수 있습니다. 기본 제공 스킨으로는 gallery와 shop이 있습니다.
| 우선순위 |
파일 경로 (예: gallery 스킨, list 액션) |
| 1 |
boards/skins/gallery/list/handler.php ← 완전 독립 핸들러 (있으면 바로 실행) |
| 2 |
boards/skins/gallery/list.php ← 독립 뷰 파일 |
| 3 |
themes/{현재테마}/board/gallery/list.php |
| 4 |
themes/{현재테마}/board/list.php |
| 5 |
themes/default/board/gallery/list.php |
| 6 |
themes/default/board/list.php ← 최종 폴백 |
6.3 테마 디렉터리 구조
themes/
default/ ← 기본 테마 (항상 존재, 폴백 기준)
theme.json ← 테마 메타정보 (이름, 버전, 옵션 정의)
layout/
main.php ← 전체 레이아웃 (헤더+본문+푸터)
board/
list.php ← 게시판 목록
view.php ← 게시글 상세 (가장 큰 파일, ~90KB)
write.php ← 게시글 작성/수정
_list_rows.php ← 목록 행 파셜
board_latest/
list.php ← 최신글 위젯 목록형
card.php ← 최신글 위젯 카드형
simple.php ← 최신글 위젯 심플형
page/
home.php ← 홈페이지
404.php ← 404 페이지
403.php ← 403 페이지
parts/
pagination.php ← 페이지네이션
search/
list.php ← 통합검색 결과
assets/ ← 테마 전용 CSS/JS
my-theme/ ← 커스텀 테마 (있는 파일만 오버라이드)
theme.json
layout/main.php
board/list.php ← 목록만 커스터마이징, 나머지는 default 폴백
6.4 홈페이지(/) 렌더링 우선순위
/ URL 접근 시 Dispatcher::dispatchHome()이 4단계 우선순위로 홈페이지 파일을 탐색합니다.
| 순위 |
경로/조건 |
설명 |
| 1 |
themes/{현재테마}/page/home.php |
테마 홈 파일 (최우선). 파일 존재 시 즉시 사용 |
| 2 |
DB: is_home=1, status=1, site_domain={현재도메인} |
멀티사이트 도메인 전용 홈페이지 |
| 3 |
DB: is_home=1, status=1, site_domain='' |
공통 홈페이지 (도메인 무관) |
| 4 |
pages/home.php |
기본 폴백 홈페이지 |
7. 플러그인 시스템 — CMS 위의 확장 계층
플러그인은 Layer 3(CMS Service)과 Layer 4(Presentation) 사이에서 동작하는 선택적 확장 계층입니다. 플러그인을 활성화하면 CMS 핵심 파일을 수정하지 않고도 에디터, 결제, 소켓, CAPTCHA 등의 기능을 추가할 수 있습니다.
7.1 플러그인 타입 및 기본 제공 플러그인
| 플러그인 |
타입 |
활성 설정키 |
역할 |
| ckeditor4-editor |
editor |
active_editor |
CKEditor 4 로컬 파일 서빙. 멀티이미지, 유튜브 자동임베드 |
| jodit-editor |
editor |
active_editor |
Jodit WYSIWYG 에디터 |
| tinymce-editor |
editor |
active_editor |
TinyMCE 에디터 |
| dx-socket |
socket |
active_socket |
WebSocket 실시간 접속자 추적, 채팅, 알림 push |
| tosspay-payment |
payment |
active_payment |
토스페이 결제 연동 |
| kg-inicis-payment |
payment |
active_payment |
KG이니시스 결제 연동 |
| nicepay-payment |
payment |
active_payment |
나이스페이 결제 연동 |
| kakaopay-payment |
payment |
active_payment |
카카오페이 결제 연동 |
| naverpay-payment |
payment |
active_payment |
네이버페이 결제 연동 |
| paypal-payment |
payment |
active_payment |
PayPal 결제 연동 |
| stripe-payment |
payment |
active_payment |
Stripe 결제 연동 |
7.2 플러그인 등록 구조
각 플러그인은 plugins/{id}/plugin.php 파일을 통해 CMS에 자신을 등록합니다. PluginRegistry에 타입과 메타데이터를 등록하고, HookManager에 콜백을 등록하여 동작합니다.
// plugins/my-editor/plugin.php
dx_register_plugin([
'id' => 'my-editor',
'type' => 'editor', // editor | payment | socket | captcha | sms | social_login
'name' => 'My Editor',
'version' => '1.0.0',
'settings' => [
'toolbar' => ['label'=>'툴바 설정', 'type'=>'select',
'options'=>['full'=>'전체','basic'=>'기본']],
],
]);
// 에디터 렌더링 훅 등록
dx_add_hook('dx_editor_render', function($args) {
$name = $args['name'];
$value = $args['value'];
echo "<textarea id='{$name}' class='my-editor'>{$value}</textarea>";
echo "<script>MyEditor.init('#{$name}');</script>";
}, 10);
8. 게시판 처리 전체 흐름
/{board_key}/view/{id} URL 요청이 들어왔을 때 DXCMS가 어떻게 처리하는지 전체 흐름을 추적합니다. 이 흐름은 CMS 아키텍처의 4개 계층이 어떻게 협력하는지 가장 잘 보여주는 예시입니다.
HTTP GET /free/view/123
│
▼ index.php
[Layer 1] Secure → DB → config.php 로드
│
▼
[Layer 2] HookManager, PluginRegistry, load_plugins() 완료
│
▼
[Layer 3] DxSite::getInstance() → 도메인 설정 오버라이드
DxTheme::getInstance() → 테마 결정
Auth::getInstance() → 로그인 상태 확인
│
▼
[Layer 2] DxRouter::dispatch() → 라우트 없음 → Dispatcher 폴백
│
▼
[Layer 2] Router::resolve() → TYPE_BOARD, slug='free', action='view', id='123'
│
▼
[Layer 2] DxExtend::runMiddle() (extend/middle/ 파일 실행)
│
▼
[Layer 2] Dispatcher::dispatchBoard()
│
▼
[Layer 3] boards/handler.php 실행
• DB에서 posts WHERE id=123 조회
• 조회수 +1 (세션으로 중복 방지)
• 댓글 목록 조회
• DxSeo::build('board_view', $ctx) → SEO 메타 생성
• DxCategory::getByBoard($boardId, 'view') → 카테고리 로드
• dx_run_hook('dx_board_view_context', $ctx)
│
▼
[Layer 4] DxBoardSkin::resolveView($skin, 'view')
폴백 체인: boards/skins/{skin}/view.php
→ themes/{테마}/board/{skin}/view.php
→ themes/default/board/view.php
│
▼
[Layer 4] Dispatcher::renderWithLayout($viewFile, $ctx)
→ themes/{테마}/layout/main.php 실행
→ DxSeo::render() 호출 (layout <head> 내)
→ dx_run_hook('dx_head') (CSS/JS 추가)
→ view.php include (본문 렌더링)
→ dx_run_hook('dx_body_bottom') (팝업, 스크립트)
│
▼
[Layer 2] DxExtend::runBottom() (extend/bottom/ 파일 실행)
│
▼
HTTP Response (HTML)
9. 데이터베이스 테이블 구조
DXCMS는 모든 테이블에 dx_ 프리픽스를 사용합니다. 테이블명은 Database::table($name)을 통해 자동으로 프리픽스가 추가됩니다.
| 테이블 |
관련 클래스/계층 |
주요 컬럼 |
| dx_members |
Auth, DxPoint, DxFriend |
id, login_id, password, name, level, point, exp, profile_img |
| dx_social_accounts |
DxSocialAuth |
member_id, provider(kakao/naver/google/github), provider_id |
| dx_boards |
Router, Dispatcher |
board_key, board_name, skin, site_domain, status |
| dx_posts |
boards/handler.php |
id(BIGINT), board_id, member_id, title, content, is_secret, view_count |
| dx_comments |
core/api/comment.php |
id, post_id, member_id, content, parent_id |
| dx_categories |
DxCategory |
id, board_id, parent_id, name, color, show_in_list, show_in_view |
| dx_pages |
Router, Dispatcher |
slug, title, content, page_type, is_home, site_domain, is_standalone |
| dx_menus |
DxSite, 레이아웃 |
id, menu_group, label, url, sort_order |
| dx_settings |
dx_config() |
setting_key, setting_value (전체 사이트 설정) |
| dx_sites |
DxSite |
domain, site_name, theme, menu_group, extra_config, status |
| dx_notifications |
DxNotification |
to_member_id, from_member_id, type, message, url, is_read |
| dx_point_log |
DxPoint |
member_id, type, point, exp, description |
| dx_level_config |
DxPoint |
level, exp_threshold, name (관리자 커스터마이징) |
| dx_likes |
core/api/like.php |
post_id, member_id, type |
| dx_scraps |
boards/handler.php |
post_id, member_id |
| dx_visitors |
extend/middle/ |
id, ip, member_id, url, visited_at |
| dx_member_monitor |
DxMemberMonitor |
member_id, last_seen, is_online |
| dx_popups |
DxPopup |
title, content, start_at, end_at, conditions |
10. 주요 전역 헬퍼 함수 레퍼런스
functions.php와 각 클래스 하단에 정의된 전역 함수들입니다. 테마/플러그인/extend 코드에서 자유롭게 사용할 수 있습니다.
| 함수 |
정의 위치 |
설명 |
| dx_config($key, $default) |
functions.php |
전역 설정값 반환 |
| dx_config_set($key, $val) |
functions.php |
전역 설정값 변경 (멀티사이트 오버라이드에 사용) |
| dx_base_url($path) |
functions.php |
기본 URL + 경로 반환 |
| dx_request_uri() |
functions.php |
현재 요청 URI (URL Rewrite 호환) |
| dx_esc($str) |
functions.php |
HTML 출력 이스케이프 (XSS 방어) |
| dx_csrf_token() |
functions.php |
CSRF 토큰 값 반환 |
| dx_csrf_field() |
functions.php |
<input type=hidden name=csrf ...> 출력 |
| dx_csrf_check() |
functions.php |
POST 요청 CSRF 토큰 검증 |
| dx_app() |
DxContainer.php |
DI 컨테이너 인스턴스 반환 |
| dx_make($abstract) |
DxContainer.php |
컨테이너에서 서비스 꺼내기 |
| dx_db($table) |
QueryBuilder.php |
QueryBuilder 인스턴스 반환 |
| dx_add_hook($name, $cb, $pri) |
HookManager.php |
훅 Action 등록 |
| dx_run_hook($name, $args) |
HookManager.php |
훅 Action 실행 |
| dx_apply_filter($name, $val) |
HookManager.php |
훅 Filter 실행 |
| dx_theme_file($relPath) |
DxTheme.php |
테마 파일 경로 반환 (폴백 포함) |
| dx_include_part($name, $vars) |
DxTheme.php |
테마 파셜 include |
| dx_theme_asset($path) |
DxTheme.php |
테마 에셋 URL 반환 |
| dx_theme() |
DxSite.php |
현재 테마명 반환 |
| dx_menu_group() |
DxSite.php |
현재 메뉴 그룹명 반환 |
| load_plugins() |
functions.php |
plugins/**/plugin.php 자동 로드 |
| dx_register_plugin($info) |
PluginRegistry.php |
플러그인 타입 등록 |
| dx_log($msg, $level) |
functions.php |
data/error.log에 에러/경고 기록 |
| dx_path_inside($path, $base) |
functions.php |
경로 탈출 방어 검증 |
11. 아키텍처 설계 결정 요약
DXCMS의 주요 설계 결정과 그 이유를 정리합니다.
| 설계 결정 |
이유 및 트레이드오프 |
| 싱글턴 패턴 우선 |
PHP 5.6 호환성 + 간결함. Composer autoload 없이도 동작. DI 컨테이너가 추가로 감싸 Laravel 스타일도 지원 |
| Secure.php 해시 경로 |
파일 경로를 알아도 공격 불가. 보안 패치 시 1파일만 교체하면 되는 단일 책임 원칙 |
| DxCache 최우선 로드 |
config.php 로드 전에 캐시가 준비되어야 설정 캐싱 가능. 매 요청마다 DB 조회 방지 |
| 이중 라우팅(DxRouter + Dispatcher) |
기존 파일 기반 방식 100% 유지로 하위 호환. 새로운 routes/*.php 방식은 선택적 확장 |
| extend/ 폴더 시스템 |
훅 없이 파일만 놓으면 실행. 개발자가 아닌 운영자도 사용 가능. 에러 격리로 안전 |
| DxTheme 폴백 체인 |
커스텀 테마는 바꾸고 싶은 파일만 오버라이드. 나머지는 default에서 자동 상속 |
| BIGINT post_id 문자열 유지 |
32bit PHP에서 (int) 캐스팅 시 오버플로우 발생. ctype_digit()으로 검증 후 문자열 유지 |
| DxSite 캐싱 |
모든 요청마다 dx_sites DB 조회는 비효율. DxCache로 도메인별 설정을 300초 캐싱 |
| DxNotification 예외 완전 흡수 |
알림 실패가 댓글 작성 등 본 기능을 막으면 UX 파괴. try-catch로 완전 격리 |
| DxMarket SHA-256 검증 |
개발자 서버 → 사용자 서버 직접 다운로드 후 중앙서버 해시와 대조. 무결성 보장 |
✅ 한 줄 요약: DX 프레임워크 위에 CMS가 올라가는 구조
Layer 1 (Infrastructure) — 보안•DB•설정: 모든 것의 토대
Layer 2 (DX Core) — 훅•DI•라우터•캐시: CMS가 작동하는 메커니즘
Layer 3 (CMS Service) — 인증•게시판•SEO•포인트: 비즈니스 로직
Layer 4 (Presentation) — 테마•스킨•폴백 체인: 사용자가 보는 HTML
플러그인 계층이 Layer 3~4 사이에서 선택적으로 기능을 추가합니다.
extend/ 파일이 Layer 2~4 전반에 걸쳐 코드를 삽입합니다.