회원가입 | 고객센터 |
DESIGNONEX
디자인원엑스
About
Service
Q&A
PR리그
자유게시판N
갤러리
포인트게임
공지사항N
통계
로그인 회원가입
고객센터
3.6 데이터 처리 구조

데이터 흐름 상세 기술

A Administrator
2026.04.21 00:54(수정됨) 97 0

1. 개요 — 전체 데이터 흐름 조감도

DX CMS의 모든 요청은 단일 진입점 index.php를 통해 처리됩니다. HTTP 요청이 수신된 순간부터 HTML 응답이 전송되기까지, 데이터는 다음 6단계 파이프라인을 직렬로 통과합니다.
 
flowchart TD

A["HTTP 요청 (GET / POST / PUT / DELETE)"]

B["PHASE 1: 전처리
- ob_start()
- URL 정규화 (이중슬래시 301)
- PHP 버전체크
- 상수 정의 (DX_CMS, DX_ROOT)
- 설치 여부 확인"]

C["PHASE 2: 보안 초기화
- Secure::initSession()
- session_start()
- 보안 헤더 발행
- CSRF 토큰 발급
- 동적 세션키 (DX_SECRET_KEY)"]

D["PHASE 3: 서비스 초기화
- config.php (DB 연결)
- HookManager
- PluginRegistry / load_plugins()
- DxSite (멀티사이트)
- DxTheme (테마 확정)
- Auth (세션/쿠키 인증)
- DxContainer
- extend/top 실행"]

E["PHASE 4: 라우팅
- DxRouter::dispatch()
  → 성공: Controller 실행
  → 실패: Router::resolve()
- URI 파싱
- DB 조회 (게시판/페이지)
- route 배열 확정
- extend/middle 실행
- 접근권한 체크
- 핸들러 결정"]

F["PHASE 5: 비즈니스 로직 + 렌더링
- handler.php
- 입력 검증 / CSRF 확인
- DB 조회/쓰기
- 데이터 준비
- DxTheme::resolveBoardSkin()
- 스킨 include
- ob_start(스킨)
- layout/main.php 렌더링"]

G["PHASE 6: 후처리 + 응답
- extend/bottom 실행
- ob_end_flush()
- HTML 응답 전송"]

A --> B --> C --> D --> E --> F --> G

핵심 설계 원칙
모든 데이터는 단방향(요청→처리→응답)으로 흐르며, 각 Phase는 이전 Phase의 결과에만 의존합니다.
싱글턴 패턴으로 관리되는 서비스(Database, Auth, DxTheme 등)는 Phase 3에서 한 번만 초기화되고,
이후 어떤 파일에서든 getInstance()로 즉시 재사용됩니다.


2. PHASE 1 — 전처리

PHASE 1    전처리 단계

ob_start() • URL 정규화 • 상수 정의 • 설치 확인


2.1 출력 버퍼링 활성화

index.php의 첫 번째 실행 코드는 ob_start()입니다. 이 한 줄이 DX CMS의 신뢰성을 크게 높이는 역할을 합니다.
 
ob_start();   // index.php 최상단 — 가장 먼저 실행
 
역할 설명
헤더 충돌 방지 PHP 출력 발생 후 header() 호출 시 나타나는 "headers already sent" 오류를 근본 차단
IIS/CGI 호환 IIS, CGI, 저가형 웹호스팅 환경에서 header() 동작을 안정적으로 보장
에러 복구 Phase 진행 중 예상치 못한 출력 발생 시 ob_end_clean()으로 버퍼 초기화 가능
최종 flush Phase 6의 ob_end_flush()가 모든 HTML을 한 번에 전송 — I/O 효율 향상


2.2 URL 정규화 (이중 슬래시 301)

REQUEST_URI에 이중 슬래시(//)가 포함된 경우 301 리다이렉트로 정규화합니다. 중복 URL로 인한 SEO 불이익과 라우팅 오작동을 예방합니다.
 
// 예: /board//free → /board/free (301 Redirect)
if (strpos($_dxRawPath, "//") !== false) {
    header("Location: " . preg_replace("#/+#", "/", $_dxRawPath) . $_dxRawQuery, true, 301);
    exit;
}


2.3 핵심 상수 정의

Phase 1에서 정의된 상수는 이후 모든 파일에서 경로 하드코딩 없이 사용됩니다.
 
상수명 값 및 역할
DX_CMS true — 직접 접근 차단 체크에 사용 (if (!defined("DX_CMS")) exit)
DX_VERSION "8.1.0" — 버전 문자열
DX_ROOT 설치 루트 절대 경로 (백슬래시→슬래시 정규화)
DX_CORE DX_ROOT . "/core" — 핵심 클래스 디렉터리
DX_DATA DX_ROOT . "/data" — config.php, error.log 위치
DX_THEMES DX_ROOT . "/themes" — 테마 디렉터리
DX_PLUGINS DX_ROOT . "/plugins" — 플러그인 디렉터리
DX_BOARDS DX_ROOT . "/boards" — 게시판 핸들러 위치
DX_PAGES DX_ROOT . "/pages" — 페이지 파일 위치
DX_EXTEND DX_ROOT . "/extend" — 확장 스크립트 위치
DX_START microtime(true) — 성능 측정용 시작 시각


2.4 설치 여부 확인 및 분기

data/config.php 파일이 존재하는지 확인합니다. 파일이 없으면 설치 화면으로, 있으면 정상 부팅 경로로 진행합니다.
 
$_dxConfigFile = DX_ROOT . "/data/config.php";
$_dxInstalled  = file_exists($_dxConfigFile);

if (!$_dxInstalled) {
    // install/ 디렉터리로 301 리다이렉트
    header("Location: " . $scheme . "://" . $host . "/install/");
    exit;
}

💡  에러 출력 전략
미설치 환경: display_errors=1 (설치 디버깅을 위해 화면에 오류 표시)
설치 후 운영: display_errors=0, log_errors=1 → data/error.log에 기록
이 분기는 하나의 코드베이스가 설치 전/후를 모두 처리할 수 있게 합니다.


3. PHASE 2 — 보안 초기화

PHASE 2    보안 초기화

Secure 클래스 • 세션 • CSRF • 보안 헤더

보안 관련 모든 처리는 Secure 클래스 하나에 집중됩니다. 이 클래스는 설치 시 core/security/{16자리 해시}/ 에 복사되어 공격자가 예측할 수 없는 경로에 위치합니다.


3.1 보안 경로 선취 파싱

Secure.php 로드 이전에 config.php에서 DX_SECURITY_PATH 상수만 정규식으로 먼저 추출합니다. DB 연결 없이 파일 텍스트만 파싱하는 이유는, 이 단계에서 아직 PDO가 초기화되지 않았기 때문입니다.
 
// config.php 텍스트에서 DX_SECURITY_PATH만 선취 파싱
$_dxCfgRaw = @file_get_contents($_dxConfigFile);
preg_match("/define.*DX_SECURITY_PATH.*([a-f0-9]{16}).*/", $_dxCfgRaw, $_dxSpM);
define("DX_SECURITY_PATH", $_dxSpM[1]);  // "a3f1c8e2d94b5071" 형태

// Secure.php 로드 경로: core/security/{hash}/Secure.php
$_dxSecurePath = DX_CORE . "/security/" . DX_SECURITY_PATH . "/Secure.php";


3.2 세션 초기화 흐름

Secure::initSession()은 session_start() 전에 세션 설정을 완료합니다. 또한 GET 요청이고 세션 쿠키가 없는 경우 세션 시작을 건너뛰어 불필요한 파일 락을 제거합니다.
 
// 세션 필요 여부 판단
$_dxNeedSession = true;
if ($_SERVER["REQUEST_METHOD"] === "GET" && empty($_COOKIE[session_name()])) {
    // admin, auth, view, api, write, edit, reply 는 세션 필요
    // 단순 목록•페이지 조회는 세션 불필요 → 파일락 제거, 성능 향상
    $_dxNeedSession = false;
}
if ($_dxNeedSession) { Secure::getInstance()->startSession(); }


3.3 DX_SECRET_KEY 기반 동적 키

사이트마다 다른 64자리 랜덤 secret_key를 DX_SECRET_KEY 상수로 정의합니다. 이 키를 기반으로 세션 키 이름, CSRF 토큰 이름을 동적으로 도출합니다.
 
// secret_key 로드 (DB config에서)
$_dxSecretVal = dx_config("secret_key", "");
define("DX_SECRET_KEY", $_dxSecretVal);

// Secure::initSecretKeys() 내부에서
// SESSION_KEY = hash_hmac("sha256", "session", secret_key)의 앞 16자리
// CSRF_KEY    = hash_hmac("sha256", "csrf",    secret_key)의 앞 16자리
// 사이트마다 키 이름이 달라 소스코드 공개로도 예측 불가


3.4 보안 헤더 발행

Secure::sendSecurityHeaders()는 .htaccess나 web.config 없는 공유 호스팅에서도 브라우저 수준의 보안을 보장합니다.
 
헤더 역할
X-Frame-Options: SAMEORIGIN Clickjacking 방어 — 외부 iframe 삽입 차단
X-Content-Type-Options: nosniff MIME 타입 스니핑 공격 방어
X-XSS-Protection: 1; mode=block    구형 브라우저 XSS 필터 활성화
Referrer-Policy: strict-origin... 외부 링크 클릭 시 민감한 URL 정보 유출 방지
Content-Security-Policy 스크립트•스타일 출처 제한 (설정 기반 동적 구성)


3.5 CSRF 토큰 흐름

세션이 있는 요청에서만 CSRF 토큰을 발급합니다. POST 요청 처리 시 handler.php에서 dx_csrf_verify()로 검증합니다.
 
단계 위치 동작
토큰 발급 Phase 2 — index.php Secure::csrfToken() → 세션에 토큰 저장
토큰 출력 폼 템플릿 — 스킨 파일 dx_csrf_field() → <input type="hidden" value="토큰">
토큰 검증 Phase 5 — handler.php dx_csrf_verify() → 세션 값과 POST 값 비교
실패 처리 DxRouter 미들웨어 403 + JSON 오류 응답 즉시 반환


4. PHASE 3 — 서비스 초기화

PHASE 3    서비스 초기화

DB • 플러그인 • 멀티사이트 • 테마 • 인증

Phase 3는 CMS가 실제로 "살아나는" 단계입니다. DB 연결이 완료되고, 모든 서비스가 순서대로 초기화됩니다. 초기화 순서가 중요합니다 — 각 서비스는 앞서 초기화된 서비스에 의존합니다.


4.1 DB 연결 — config.php

require_once $_dxConfigFile 한 줄로 DB 연결이 완료됩니다. config.php 내부에서 Database::getInstance()->connect()가 호출되며, PDO 옵션•접두사•캐릭터셋이 설정됩니다
 
// data/config.php 내부 구조 (예시)
define("DX_SECRET_KEY", "abcdef...64자리...123456");
define("DX_SECURITY_PATH", "a3f1c8e2d94b5071");

$db = Database::getInstance();
$db->connect($host, $dbName, $dbUser, $dbPass, "utf8mb4", "dx_");

// DB에서 전역 설정($dx_config) 로드
$settings = $db->rows("SELECT * FROM `dx_settings`");
foreach ($settings as $s) { $dx_config[$s["key"]] = $s["value"]; }


4.2 서비스 초기화 순서와 의존성

순서 서비스 의존 서비스 및 역할
HookManager 의존: 없음 — 이벤트 버스, 가장 먼저 초기화
PluginRegistry 의존: 없음 — 플러그인 타입 저장소
load_plugins() 의존: HookManager + PluginRegistry — plugins/**/plugin.php 자동 로드
DxSite 의존: Database — 도메인별 $dx_config 오버라이드 (멀티사이트)
DxTheme 의존: DxSite 완료 후 (테마명 확정 후) — 테마 파일 해석기 초기화
Auth 의존: Database + Secure(세션) — 세션/쿠키로 사용자 로드
DxContainer 의존: 모든 서비스 — DI 컨테이너에 핵심 서비스 등록
extend/top/ 실행    의존: 모든 서비스 초기화 완료 — 사용자 정의 초기화 스크립트


4.3 DxSite — 멀티사이트 설정 오버라이드

DxSite::getInstance()는 현재 요청의 HTTP_HOST를 감지하고, dx_sites 테이블에서 도메인 매핑을 조회합니다. 매핑이 있으면 해당 도메인 설정이 전역 $dx_config를 덮어씁니다.
 
// DxSite 내부 흐름
$host = preg_replace("/:\d+$/", "", $_SERVER["HTTP_HOST"]);  // 포트 제거
$site = $db->row("SELECT * FROM `dx_sites` WHERE domain=? AND status=1", [$host]);

if ($site) {
    // site_name, theme, language, timezone 등 오버라이드
    foreach ($map as $field => $configKey) {
        if (!empty($site[$field])) dx_config_set($configKey, $site[$field]);
    }
    // extra_config (JSON) 추가 설정
    $extra = json_decode($site["extra_config"], true);
    foreach ($extra as $k => $v) dx_config_set($k, $v);
}


4.4 DxTheme — 테마 폴백 체인 초기화

DxSite 완료 후 테마명이 확정되면 DxTheme이 초기화됩니다. 테마 디렉터리가 없으면 자동으로 "default"로 폴백합니다.
 
// DxTheme 파일 해석 우선순위 (resolveBoardSkin 예시)
// 1. themes/{활성테마}/board/{스킨}/list.php
// 2. themes/{활성테마}/board/list.php
// 3. themes/default/board/{스킨}/list.php
// 4. themes/default/board/list.php
// → null이면 404


4.5 Auth — 세션•쿠키 기반 인증

Auth::getInstance() 호출 시 loadSession()이 실행되어 현재 요청자의 인증 상태가 결정됩니다.
 
// Auth::loadSession() 흐름
if (!isset($_SESSION[$sessionKey])) {
    $this->tryRememberMe();  // Remember Me 쿠키로 자동 로그인 시도
    return;
}

$userId = (int)$sessionData["id"];
$user   = $db->find("members", ["id"=>$userId, "status"=>1]);

// 세션 토큰 검증 (hash_hmac SHA-256: id + join_date + secret_key)
if ($sessionData["token"] !== $this->makeToken($user)) {
    $this->logout();  // 토큰 불일치 → 강제 로그아웃
}
 
인증 경로 트리거 처리 내용
세션 로그인 세션 쿠키 존재 $_SESSION에서 user_id 읽기 → DB 조회 → 토큰 검증
Remember Me dx_remember 쿠키 DB remember_token 비교 → 성공 시 토큰 롤링 갱신(30일)
비로그인 세션•쿠키 없음 $this->user = null → dx_is_login() = false
토큰 불일치 세션 위변조 시도 logout() → 세션 삭제 + 쿠키 만료


5. PHASE 4 — 라우팅

PHASE 4    라우팅

DxRouter • Router::resolve() • Dispatcher

라우팅은 URI를 분석하여 어떤 핸들러가 요청을 처리할지 결정하는 단계입니다. DX CMS는 두 개의 라우팅 레이어를 직렬로 실행합니다.


5.1 DxRouter — 코드 기반 라우팅 (우선)

routes/ 폴더의 PHP 파일에 등록된 라우트를 먼저 처리합니다. URI 패턴과 HTTP 메서드가 일치하면 미들웨어를 실행하고 액션을 호출합니다.
 
// DxRouter::dispatch() 내부 흐름
$method = $_SERVER["REQUEST_METHOD"];  // GET / POST / PUT …
$uri    = self::currentUri();           // "/board/free/view/123"

foreach (self::$routes as $route) {
    if ($route["method"] !== $method) continue;
    if (!self::matchUri($route["uri"], $uri, $params)) continue;
    // URI 패턴 매칭: {id} → (?P<id>[^/]+) 정규식 변환

    // 미들웨어 순차 실행
    foreach ($route["middleware"] as $mw) {
        $result = self::runMiddleware($mw, $params);
        if ($result === false) return true;
    }

    self::runAction($route["action"], $params);
    return true;  // 매칭 성공
}
return false;  // 매칭 실패 → Dispatcher 폴백


5.2 내장 미들웨어 종류

미들웨어 동작
auth 미로그인 시 auth/login?redirect=... 으로 리다이렉트
admin 관리자가 아닌 경우 HTTP 403 즉시 반환
guest 이미 로그인한 경우 홈으로 리다이렉트 (로그인 페이지 보호)
csrf dx_csrf_verify() 실패 시 403 + JSON 오류 응답
json Content-Type: application/json 헤더 자동 설정
throttle 분당 요청 수 제한 (기본값 60회/분, 향후 확장 예정)
커스텀 클로저 또는 ClassName@handle 형태로 직접 구현 가능


5.3 Router::resolve() — 파일 기반 라우팅 (폴백)

DxRouter에서 매칭되지 않으면 Dispatcher → Router::resolve()가 실행됩니다. URI 세그먼트를 분석하고 DB를 조회하여 route 배열을 확정합니다.
 
// URI: /free/view/1745123456789012
$segments = ["free", "view", "1745123456789012"];

// $segments[0] = "free"  → boards 테이블에서 board_key="free" 조회
// $segments[1] = "view"  → boardActions 목록에 있음 → TYPE_BOARD
// $segments[2] = "1745…" → BIGINT ID (ctype_digit 검증 후 문자열 유지)
 
URI 패턴 타입 처리 파일
/ (루트) TYPE_HOME 테마/page/home.php → pages/home.php
/admin/… TYPE_ADMIN admin/index.php (관리자 전용)
/auth/login TYPE_AUTH core/auth/login.php
/api/… TYPE_API core/api/{action}.php
/search TYPE_SEARCH core/search/handler.php
/{board_key} TYPE_BOARD boards/handler.php (DB 조회 필요)
/{slug} TYPE_PAGE pages/{slug}.php (DB 조회 필요)
매칭 없음 TYPE_404 테마/page/404.php


5.4 라우팅 데이터 흐름 — route 배열

Router::resolve()의 결과는 route 배열로 반환되어 $GLOBALS["dx_route"]에 저장됩니다. 이 배열이 Phase 5 핸들러로 전달되는 핵심 데이터 컨텍스트입니다.
 
// route 배열 구조 (게시판 view 예시)
$route = array(
    "type"     => "board",
    "uri"      => "/free/view/1745123456789012",
    "segments" => ["free", "view", "1745123456789012"],
    "slug"     => "free",
    "action"   => "view",
    "id"       => "1745123456789012",  // 문자열 — 32bit 오버플로우 방지
    "board"    => [/* dx_boards row */],
    "page"     => null,
);
$GLOBALS["dx_route"] = $route;


5.5 extend/middle/ — 라우트 확정 직후 실행

Dispatcher::dispatch()에서 route 배열이 확정된 직후, 핸들러 실행 전에 extend/middle/ 폴더의 스크립트가 자동 실행됩니다. 이 시점에 $GLOBALS["dx_route"]를 통해 라우트 정보를 읽을 수 있습니다.
 
// Dispatcher::dispatch() 내부
$this->route = $this->router->resolve();
$GLOBALS["dx_route"] = $this->route;

// extend/middle/ 실행 — 방문자 통계, A/B테스트, 접근제어 등
DxExtend::getInstance()->runMiddle(array(
    "type"  => $this->route["type"],
    "route" => $this->route,
));

// 이후 switch($type) → 핸들러 실행


6. PHASE 5 — 비즈니스 로직 + 렌더링

PHASE 5    비즈니스 로직 + 렌더링

handler.php • DxTheme 스킨 • 레이아웃

Phase 5는 실제 CMS 기능이 실행되는 핵심 단계입니다. 입력 데이터 검증, DB 조회•쓰기, 출력 데이터 준비, 테마 렌더링이 순차적으로 이루어집니다.


6.1 게시판 핸들러 — boards/handler.php

TYPE_BOARD 라우트는 boards/handler.php로 디스패치됩니다. 핸들러는 action에 따라 분기하여 각각의 로직을 실행합니다.
 
// Dispatcher::dispatchBoard() → boards/handler.php
$GLOBALS["dx_board"]      = $board;      // boards 테이블 row
$GLOBALS["dx_action"]     = $action;     // list / view / write / edit / delete …
$GLOBALS["dx_board_skin"] = $board["skin"] ?: "default";
require DX_BOARDS . "/handler.php";


6.1.1 게시판 액션별 데이터 흐름

액션 입력 데이터 DB 조작 출력 데이터
list page, sort, keyword (GET) 게시물 목록+COUNT SELECT 게시물 배열, 페이지네이션
view id (URL 세그먼트) 게시물 SELECT + view_count UPDATE 게시물 row, 댓글 배열
write POST title, content, files (POST) insertWithMicrotimeId + 파일 INSERT 신규 게시물 ID, 리다이렉트
edit POST id, title, content (POST) UPDATE posts + 파일 처리 수정된 게시물 ID, 리다이렉트
delete id (POST + CSRF) DELETE posts + 파일 삭제 목록 리다이렉트
reply id, content (POST) INSERT comments + hook 댓글 추가, AJAX JSON 응답


6.1.2 list 액션 데이터 흐름 상세

1.    URL 파라미터 읽기: $_GET["page"], $_GET["sort"], $_GET["keyword"]
2.    Board 설정 로드: $GLOBALS["dx_board"]에서 per_page, read_level, write_level 읽기
3.    접근 권한 체크: read_level에 따라 비로그인자 차단 여부 결정
4.    검색 조건 조립: keyword가 있으면 WHERE title LIKE ? OR content LIKE ?
5.    COUNT 쿼리: Database::value()로 전체 게시물 수 조회 → 페이지네이션 계산
6.    목록 쿼리: Database::rows()로 현재 페이지 게시물 배열 조회
7.    스킨 파일 결정: DxTheme::resolveBoardSkin(skin, "list") → 폴백 체인 실행
8.    렌더링: ob_start() → 스킨 include → $dx_content → layout/main.php include


6.1.3 write POST 데이터 흐름 상세

1.    CSRF 검증: dx_csrf_verify() — 실패 시 즉시 403 반환
2.    로그인 확인: Auth::isLoggedIn() — write_level에 따라 차단
3.    입력 정제: DxSanitizer::clean() — XSS 방어 (허용 태그 화이트리스트)
4.    파일 업로드 처리: MIME 검증 → 확장자 검증 → 랜덤 파일명 → uploads/ 저장
5.    트랜잭션 시작: $db->begin()
6.    게시물 INSERT: $db->insertWithMicrotimeId("posts", $data)
7.    파일 메타 INSERT: $db->insertRow("post_files", $fileData)
8.    트랜잭션 커밋: $db->commit() — 실패 시 rollback()
9.    훅 실행: dx_run_hook("dx_after_post_write", [...]) — 포인트 지급 등
10.    응답: dx_redirect(게시물 view URL)


6.2 렌더링 파이프라인 — ob_start 중첩

DX CMS의 렌더링은 ob_start()를 중첩 사용하여 스킨 출력을 버퍼에 포착하고, 이를 레이아웃에 $dx_content로 주입하는 방식으로 동작합니다.
 
// 1. index.php의 ob_start()가 전체 버퍼를 감쌈

// 2. handler.php의 _brd_render() 내부
ob_start();
extract($vars);                           // 스킨 변수 주입
require $skinFile;                        // 스킨 파일 실행
$dx_content = ob_get_clean();             // 스킨 출력 포착

// 3. 레이아웃에 주입
$layoutFile = DxTheme::getInstance()->resolve("layout/main.php");
extract($context);
require $layoutFile;                      // $dx_content 사용
// layout/main.php 예:
// <?php include header.php; ?>
// <main><?php echo $dx_content; ?></main>
// <?php include footer.php; ?>


6.3 페이지 렌더링 — 에러 격리

페이지 파일(pages/*.php)은 set_error_handler + try/catch로 격리되어 실행됩니다. 페이지 파일에서 오류가 발생해도 레이아웃(헤더/푸터)은 유지됩니다.
 
// renderPageWithLayout 내부 에러 격리 패턴
set_error_handler(function($errno, $errstr, $errfile, $errline) {
    dx_log("[Page][{$errfile}:{$errline}] " . $errstr, "error");
    return true;  // PHP 기본 에러 핸들러 비활성화
});

ob_start();
try {
    extract($context, EXTR_SKIP);
    include $contentFile;  // 페이지 파일 실행
} catch (Exception $e) {
    echo "<div>페이지 오류: " . htmlspecialchars($e->getMessage()) . "</div>";
}
$dx_content = ob_get_clean();
restore_error_handler();

require $layoutFile;  // 레이아웃은 정상 출력


6.4 API 요청 데이터 흐름

TYPE_API 라우트는 core/api/{action}.php로 디스패치됩니다. Content-Type: application/json 헤더가 자동 설정되며, 각 파일은 JSON 응답을 직접 출력합니다.
 
// 예: /api/post_like POST 요청 흐름
// 1. Dispatcher::dispatchApi() → core/api/post_like.php
// 2. CSRF 검증 (POST 요청)
// 3. 로그인 확인
// 4. $db->exists("post_likes", ["post_id"=>$id, "member_id"=>$uid])
// 5. INSERT 또는 DELETE (토글)
// 6. $db->value("SELECT COUNT(*) FROM dx_post_likes WHERE post_id=?", [$id])
// 7. echo json_encode(["success"=>true, "count"=>$cnt])


6.5 인증 요청 데이터 흐름 (로그인)

단계 위치 데이터 흐름
1. GET /auth/login Dispatcher::dispatchAuth() 로그인 폼 HTML 출력 (토큰 포함)
2. POST 입력 수신 core/auth/login.php $_POST["login_id"], $_POST["password"]
3. CSRF 검증 dx_csrf_verify() 세션 토큰 vs POST 토큰 비교
4. DB 조회 Auth::login() members WHERE login_id=? OR email=?
5. 비밀번호 검증 Auth::verifyPassword() password_verify() bcrypt 검증
6. 세션 생성 $_SESSION[$sessionKey] ["id"=>$uid, "token"=>hash_hmac(...)]
7. Remember Me setcookie("dx_remember") userId:랜덤토큰 (30일, httpOnly)
8. 훅 실행 dx_run_hook("dx_after_login") 회원 모니터링, 포인트, 알림 등
9. 리다이렉트 header("Location: ...") redirect 파라미터 또는 홈으로


7. PHASE 6 — 후처리 + 응답

PHASE 6    후처리 + 응답 전송

extend/bottom/ • ob_end_flush()

렌더링이 완료된 후 extend/bottom/ 스크립트가 실행되고, 최종 버퍼가 브라우저로 전송됩니다.


7.1 extend/bottom/ 실행

DxExtend::getInstance()->runBottom(array(
    "elapsed" => round((microtime(true) - DX_START) * 1000, 2),
));
 
활용 사례 설명
성능 로그 elapsed ms + DB 쿼리 수를 로그에 기록
캐시 저장 렌더링 완료된 HTML을 파일 캐시에 저장
통계 집계 페이지뷰 카운터 비동기 업데이트
정리 작업 임시 파일 삭제, 메모리 해제
모니터링 외부 APM 서비스에 요청 완료 신호 전송


7.2 최종 버퍼 flush

// ob_get_level() > 0 체크 — 이중 flush 방지
if (ob_get_level() > 0) {
    ob_end_flush();  // 전체 HTML을 브라우저로 한 번에 전송
}

💡  ob_end_flush() 의미
index.php 최상단 ob_start()부터 쌓인 모든 출력 버퍼를 한 번에 전송합니다.
단일 TCP 패킷으로 전송되어 IIS•CGI 환경에서도 안정적으로 동작합니다.
이미 headers_sent 상태가 되어도 버퍼 내용은 정상 전송됩니다.


8. 주요 데이터 변환 지점

각 Phase의 경계에서 데이터가 어떻게 변환•전달되는지 정리합니다.


8.1 입력 데이터 정제 흐름

각 Phase의 경계에서 데이터가 어떻게 변환•전달되는지 정리합니다.


8.1 입력 데이터 정제 흐름

입력 소스 정제 방법 전달 대상
URL 세그먼트 preg_replace(/[^a-zA-Z0-9\-_]/,"") Router::parseSegments → route 배열
GET 파라미터 htmlspecialchars / (int) 캐스팅 handler.php 내 직접 사용
POST 본문 본문    DxSanitizer::clean() — XSS 필터링 DB INSERT/UPDATE 값
파일 업로드 MIME 검증 + 확장자 화이트리스트 uploads/ 디렉터리 저장
BIGINT ID ctype_digit() 검증 후 문자열 유지 DB WHERE id=? 바인딩
DB 조회값 바인딩 결과 — 이미 안전 스킨 파일 변수로 extract()


8.2 전역 상태 흐름

$dx_config, $GLOBALS["dx_route"] 등 전역 변수가 Phase 간 데이터를 전달하는 중요한 매개체입니다.
 
전역 변수 설정 위치 참조 위치
$dx_config config.php (DB settings) + DxSite::load() 전체 파일 — dx_config()로 접근
$GLOBALS["dx_route"] Dispatcher::dispatch() extend/middle/, handler.php, 테마 파일
$GLOBALS["dx_board"] Dispatcher::dispatchBoard() boards/handler.php
$GLOBALS["dx_action"] Dispatcher::dispatchBoard() boards/handler.php
$_SESSION[$key] Auth::login() + Auth::loadSession() Auth::user(), dx_is_login()
DX_START_TIME index.php define() extend/bottom/, 성능 위젯


8.3 훅이 데이터 흐름에 개입하는 지점

훅 포인트 Phase 개입 내용
dx_after_login Phase 3 → Auth::login() 포인트 지급, 회원 모니터링, 알림 발송
dx_after_register Phase 5 → Auth::register() 가입 환영 이메일, 기본 포인트 지급
dx_after_post_write Phase 5 → handler.php 포인트 지급, 알림, 검색 색인
dx_editor_render Phase 5 → 스킨 파일 활성 에디터 HTML 출력 (플러그인 교체)
dx_body_bottom Phase 5 → 레이아웃 팝업, 소켓 스크립트, 통계 코드 삽입
dx_top / dx_bottom Phase 5 → 테마 파일 광고•위젯 삽입, 추가 메타태그
dx_content_before_save Phase 5 → handler.php 본문 필터링, 금칙어 처리 (Filter 훅)


9. 전체 데이터 흐름 요약

HTTP 요청 → HTML 응답까지 한 눈에 보는 전체 데이터 흐름도입니다.
 
HTTP 요청
    │
    ▼  [PHASE 1: index.php 전처리]
    │  ob_start() → URL 정규화 → 상수 정의 → 설치 확인
    │
    ▼  [PHASE 2: 보안 초기화]
    │  Secure.php 로드 → initSession() → startSession()
    │  sendSecurityHeaders() → csrfToken() → secret_key 주입
    │
    ▼  [PHASE 3: 서비스 초기화]
    │  config.php(DB연결 + $dx_config 로드)
    │  HookManager → PluginRegistry → load_plugins()
    │  DxSite($dx_config 도메인별 오버라이드)
    │  DxTheme(테마명 확정 + 폴백 체인)
    │  Auth(세션/쿠키 → $this->user 로드)
    │  DxContainer → extend/top/ 실행
    │
    ▼  [PHASE 4: 라우팅]
    │  routes/*.php 로드
    │  DxRouter::dispatch() ─ 매칭성공 → 미들웨어 → Controller@action
    │                       └ 매칭실패 → Dispatcher → Router::resolve()
    │  $GLOBALS["dx_route"] 확정
    │  extend/middle/ 실행
    │  접근 권한 체크(read_level, write_level, access_level)
    │
    ▼  [PHASE 5: 비즈니스 로직 + 렌더링]
    │  handler.php(게시판) / pages/*.php / auth/*.php / api/*.php
    │  입력 정제(DxSanitizer) → CSRF 검증 → DB 조회/쓰기
    │  트랜잭션(begin/commit/rollback)
    │  훅 실행(dx_after_post_write 등)
    │  ob_start() → 스킨 파일 → $dx_content → layout/main.php
    │
    ▼  [PHASE 6: 후처리 + 응답]
    │  extend/bottom/ 실행(성능 로그, 캐시 저장)
    │  ob_end_flush() → HTML 전송
    ▼
브라우저 렌더링

⚠️  데이터 흐름 설계 시 주의 사항
라우팅(Phase 4) 이전에는 $GLOBALS["dx_route"]가 설정되지 않습니다. extend/top/에서 라우트 정보를 사용하면 안 됩니다.
Auth(Phase 3)는 DB 연결 이후에만 초기화됩니다. config.php 로드 전에 Auth를 사용할 수 없습니다.
BIGINT ID는 (int) 캐스팅을 절대 사용하지 않습니다. 32bit PHP에서 오버플로우가 발생합니다.
ob_start()가 여러 겹으로 중첩될 수 있습니다. ob_end_flush() 전에 ob_get_level() > 0 체크가 필수입니다.

댓글0

로그인 후 댓글을 작성할 수 있습니다.
3.6 데이터 처리 구조 공통 함수 활용 2026.04.21 3.6 데이터 처리 구조 데이터 흐름 상세 기술 2026.04.21 3.6 데이터 처리 구조 DB 접근 방식 2026.04.21
30
전체 회원
269
전체 게시글
144
전체 댓글
181
오늘 방문
28,530
전체 방문
1
현재 접속
인기글 7일 이내
최신글
최신댓글
목록