회원가입 | 고객센터 |
DESIGNONEX
dxcms.kr
로그인 회원가입
고객센터
DXCMS 활용 (CMS)

DXCMS 날코딩•막코딩 완전 허용

D DX
2026.04.12 15:31(수정됨) 91 0

이 문서가 답하는 질문

"DXCMS는 정해진 방식대로만 코딩해야 하는가?"
 
아닙니다. 스킨 파일에서 PHP Notice가 터져도 페이지는 출력됩니다.
아닙니다. extend/ 파일에서 Exception이 발생해도 CMS는 계속 실행됩니다.
아닙니다. pages/home.php에 아무 HTML을 때려 넣어도 레이아웃은 살아있습니다.
아닙니다. 전역변수 $GLOBALS를 직접 쓰는 날코딩도 정식 지원됩니다.

이 문서는 그것을 소스코드 레벨에서 하나씩 증명합니다.


01. DXCMS의 철학 — "틀려도 죽지 않는 CMS"

막코딩을 허용하는 것은 버그가 아니라 설계입니다

라라벨에서 View 파일에 존재하지 않는 변수를 쓰면 → ErrorException이 터지고 500 페이지가 뜹니다.
DXCMS에서 스킨 파일에 존재하지 않는 변수를 쓰면 → 에러 로그에만 기록되고 페이지는 출력됩니다.
이것은 DXCMS가 "실수에 관대한 개발 환경"을 의도적으로 설계한 결과입니다.

왜 이렇게 설계했는가?

  • DXCMS의 주요 사용자층은 PHP 숙련자부터 입문자까지 매우 넓습니다.
  • 스킨•플러그인•extend/ 파일은 제3자 개발자가 자유롭게 작성하는 영역입니다.
  • 단 하나의 실수가 전체 사이트를 죽이면 생태계가 자라날 수 없습니다.
  • 에러는 숨기지 않습니다 — data/error.log에 기록하고 디버그 모드로 화면 출력도 가능합니다.
 
❌ 다른 CMS/프레임워크 ✅ DXCMS
변수 미정의 → 500 Error 변수 미정의 → 로그 + 계속 출력
예외 발생 → 흰 화면 예외 발생 → 에러박스 표시 + 계속
규칙 위반 → 작동 불능     규칙 위반 → 일단 실행, 나중에 수정
엄격한 PSR 준수 요구     PSR 불필요, 날것의 PHP로 OK
Composer 패키지 방식 강제 파일 복사만으로 기능 추가


02. 소스코드가 증명한다

실제 코드에 막코딩 지원이 명시되어 있습니다


증거 1 — SKIN_GUIDE.md에 직접 명시된 "막코딩 지원"

DXCMS 공식 가이드 문서(boards/skins/SKIN_GUIDE.md)에는 다음 내용이 명시되어 있습니다.

boards/skins/SKIN_GUIDE.md — 막코딩 지원 항목 (원문)
## 막코딩 지원
스킨 파일에서 PHP Notice/Warning이 발생해도 CMS는 계속 동작합니다.
- Notice/Warning → data/error.log에만 기록, 페이지 정상 출력
- Exception/Fatal → 에러 박스를 레이아웃 안에 표시하고 계속 동작
- define('DX_DEBUG', true) → 화면에 에러 상세 표시 (개발 시 활용)


증거 2 — DxExtend::safeExec() 에러 격리 메커니즘

core/DxExtend.php의 safeExec() 메서드는 extend/ 파일 실행 시 어떤 에러가 발생해도 CMS 전체를 보호합니다. 실제 소스코드입니다.

core/DxExtend.php — safeExec() 실제 소스코드
/**
 * 파일 안전 실행 (에러 격리)
 * 어떤 코드가 들어있어도 CMS 전체를 죽이지 않음
 */
private function safeExec($file, $context, $slot)
{
    // 에러 핸들러: 이 파일의 에러를 로그에만 기록하고 계속 진행
    set_error_handler(function($errno, $errstr, $errfile, $errline) {
        dx_log("[DxExtend] " . $errstr . " (line " . $errline . ")", "error");
        return true;  // ← 에러를 잡아서 로그만 쓰고 계속 실행
    });

    try {
        extract($context, EXTR_SKIP);
        include $file;  // ← 이 파일에서 뭐가 터져도 아래로 진행
    } catch (Exception $e) {
        // Exception도 로그에만 기록, CMS는 계속 동작
        dx_log("[DxExtend][" . $e->getFile() . "] " . $e->getMessage(), "error");
    }

    restore_error_handler();  // ← 다음 파일은 새 핸들러로
}

이 코드가 의미하는 것:

extend/ 폴더에 넣는 PHP 파일은 어떻게 짜도 됩니다.
Exception이 터지면? → 로그 기록 후 다음 파일 실행.
Notice가 뜨면? → 로그 기록 후 계속 출력.
PHP Fatal Error면? → 에러 핸들러가 잡아서 CMS 보호.

단 하나의 파일이 전체 사이트를 다운시킬 수 없습니다.


증거 3 — boards/handler.php 스킨 독립 핸들러 위임

boards/handler.php 소스코드를 보면, 스킨에 handler.php가 있으면 DB 쿼리부터 렌더링까지 모든 것을 그 파일에 완전히 맡깁니다.

boards/handler.php — 스킨 독립 핸들러 위임 (실제 소스코드)
// ════════════════════════════════════════════════════
// 1단계: 스킨 독립 핸들러 위임
//   boards/skins/{스킨}/{액션}/handler.php 가 있으면 완전 위임
// ════════════════════════════════════════════════════
$skinHandler = $dxSkin->resolveHandler($boardSkin, $action);
if ($skinHandler) {
    // 컨텍스트 주입 — 독립 핸들러에서 $dx_* 변수 바로 사용 가능
    $GLOBALS["dx_handler_context"] = array(
        "db"=>$db, "auth"=>$auth, "board"=>$board,
        "action"=>$action, "id"=>$id, "boardSkin"=>$boardSkin,
    );
    include $skinHandler;
    return;  // ← CMS 핵심 로직 완전 우회
}

boards/skins/my-skin/list/handler.php 파일 하나만 만들면
CMS 핵심 코드를 완전히 우회하고 원하는 대로 처리할 수 있습니다.
SELECT 쿼리를 날것으로 짜도 되고, 전역변수를 마음대로 써도 됩니다.


03. 5가지 날코딩 시나리오 — 전부 동작합니다

실제로 어떻게 코딩해도 되는지 하나씩 보여줍니다


시나리오 1 — 스킨 파일에 날것의 PHP 때려 넣기

boards/skins/my-skin/list.php 파일에 변수 체크도 없이, 구조도 없이, 마음대로 쓴 코드입니다.

boards/skins/my-skin/list.php — 완전 날코딩
<?php
// boards/skins/my-skin/list.php
// 변수 체크? 없음. 에러처리? 없음. 구조? 없음.
// 그냥 내 마음대로 출력합니다.

// $posts는 CMS가 자동으로 넘겨줌 — 확인도 안 하고 바로 사용
echo "<ul>";
foreach ($posts as $p) {
    echo "<li><a href='/" . $board["board_key"] . "/view/" . $p["id"] . "'>";
    echo $p["title"];  // dx_esc() 안 씀 — 날것 출력 (개발 중에는 OK)
    echo " (" . $p["view_cnt"] . "회) ";
    echo date("m/d", strtotime($p["created_at"]));
    echo "</a></li>";
}
echo "</ul>";

// 페이지네이션도 직접 박음
if ($total > $perPage) {
    echo "<div>총 " . $total . "개 / " . $page . " 페이지</div>";
}
?>

✅ 결과: 페이지 정상 출력

→ $posts, $board, $total, $perPage, $page 변수는 CMS가 자동으로 넘겨줍니다.
→ list.php 파일 하나만 있으면 됩니다. view.php/write.php는 default 테마로 폴백.
→ 구조 없음, 들여쓰기 없음, 에러처리 없음 — 전혀 문제 없습니다.


시나리오 2 — extend/ 파일에 에러 나는 코드를 넣어도 CMS 계속 동작

❌ 이렇게 짜도...
// extend/top/99_my_bad_code.php
// 일부러 에러 나는 코드를 넣었음

// 존재하지 않는 함수 호출
this_function_does_not_exist();

// 정의 안 된 변수 사용
echo $undefined_variable;

// 0으로 나누기
$x = 10 / 0;

// 존재하지 않는 클래스
$obj = new NonExistentClass();
✅ CMS는 안 죽습니다
// 결과: 위 파일에서 에러가 나도
// CMS는 멈추지 않습니다.

// safeExec()가 모든 에러를 격리:
// 1. set_error_handler → 로그 기록
// 2. try/catch → Exception 격리
// 3. restore_error_handler → 복구

// data/error.log에는 기록됨:
// [DxExtend] Call to undefined
//   function this_function...
// [DxExtend] Undefined variable:
//   undefined_variable

// 사이트는 정상적으로 열립니다.
// 에러 파일만 수정하면 됩니다.

→ extend/top/ 파일들은 파일명 오름차순으로 실행됩니다.
→ 99_my_bad_code.php에서 에러가 나도 01_~, 02_~ 파일들은 이미 실행 완료.
→ 에러가 난 파일만 skip하고 나머지는 계속 실행됩니다.
→ DX_DEBUG=true로 설정하면 화면에 에러 내용이 표시됩니다.


시나리오 3 — pages/home.php에 순수 HTML+PHP 막코딩

pages/home.php는 홈페이지 파일입니다. 아무 구조 없이 HTML과 PHP를 마구 섞어 써도 됩니다.

pages/home.php — 막코딩 홈페이지 예시
<?php
// pages/home.php — 아무렇게나 짜도 됩니다
// 레이아웃(헤더+푸터)은 CMS가 자동으로 감쌉니다

// 1. DB에서 직접 쿼리 — QueryBuilder 안 써도 됨
$db = Database::getInstance();
$hot = $db->rows("SELECT * FROM dx_posts WHERE status=1 ORDER BY view_cnt DESC LIMIT 5");

// 2. 전역변수 직접 접근 — 날것의 PHP
$siteTitle = $GLOBALS["dx_config"]["site_name"];

// 3. HTML을 그냥 때려 넣기
?>
<h2 style="color:red;font-size:32px;text-align:center">
    🔥 오늘의 인기글 — <?php echo $siteTitle; ?>
</h2>

<div style="border:3px solid blue;padding:20px">
<?php foreach ($hot as $post): ?>
    <div style="margin:10px 0;font-size:18px">
        👉 <a href="/free/view/<?= $post["id"] ?>">
            <?= $post["title"] ?> (<?= $post["view_cnt"] ?>회)
        </a>
    </div>
<?php endforeach; ?>
</div>

<?php
// 4. 최신글 위젯 — 함수 하나로 끝
dx_board_latest("notice", 5, "list", "공지사항", "📢");
dx_board_latest("free",   5, "card", "자유게시판", "💬");
?>

<!-- 5. 그냥 인라인 스타일 남발해도 됨 -->
<div style="background:#ff0;padding:30px;text-align:center;font-size:24px">
    ✨ 오늘도 좋은 하루 되세요! ✨
</div>

✅ 결과: 헤더•메뉴•푸터가 자동으로 감싸진 완성된 홈페이지 출력

→ pages/home.php는 $dx_content 영역만 채우면 됩니다.
→ 레이아웃(header/footer/menu)은 DxTheme::resolve()가 자동 처리합니다.
→ <?= ?>  단축 문법도 됩니다. 인라인 스타일도 됩니다. 직접 SQL도 됩니다.
→ dx_board_latest() 함수 하나로 최신글 위젯도 끝납니다.


시나리오 4 — 스킨 actions/ 에서 $GLOBALS 날것으로 접근

boards/skins/my-skin/actions/custom.php — CMS 내부 변수에 $GLOBALS로 직접 접근하는 것이 정식 지원됩니다.
실제 ERP 스킨(boards/skins/erp/actions/inventory.php) 코드에 이 패턴이 그대로 사용되고 있습니다.

boards/skins/my-skin/actions/custom.php — $GLOBALS 날것 접근 (공식 패턴)
<?php
// boards/skins/my-skin/actions/custom.php
// URL: /my-board/custom
// 이 파일을 만드는 것만으로 URL이 자동 생성됩니다.

// $GLOBALS 직접 접근 — 이것이 정식 패턴입니다 (공식 코드에서 사용 중)
$db    = $GLOBALS["dx_handler_context"]["db"];
$auth  = $GLOBALS["dx_handler_context"]["auth"];
$board = $GLOBALS["dx_handler_context"]["board"];

// 로그인 체크 — 마음대로 처리
if (!$auth->isLoggedIn()) {
    dx_redirect(dx_base_url("auth/login"));
    exit;
}

// DB 쿼리 — 아무 방법이나 사용 가능
// 방법 1: QueryBuilder
$items = dx_db("my_items")->where("board_id", $board["id"])->get();

// 방법 2: 날것의 SQL (PDO 직접)
$items = $db->rows("SELECT * FROM dx_my_items WHERE board_id = ?",
                   array($board["id"]));

// 방법 3: 전역함수로 쿼리
$items = $db->findAll("my_items", array("board_id"=>$board["id"]));

// 출력 — 방법 마음대로
$dxth = DxTheme::getInstance();
$layoutFile = $dxth->resolve("layout/main.php");

ob_start();
?>
<h1>커스텀 페이지 — <?php echo $board["name"]; ?></h1>
<?php foreach ($items as $i): ?>
    <div><?php echo $i["title"]; ?></div>
<?php endforeach; ?>
<?php
$dx_content = ob_get_clean();
if ($layoutFile) {
    extract(array("type"=>"board","board"=>$board));
    require $layoutFile;
} else {
    echo $dx_content;  // 레이아웃 없으면 그냥 출력
}

✅ 결과: /my-board/custom URL이 자동 생성되고 페이지 정상 출력

→ actions/ 폴더에 파일만 만들면 라우트 등록 없이 URL이 자동 생성됩니다.
→ $GLOBALS["dx_handler_context"] 직접 접근은 공식 코드(ERP스킨)에서 사용 중입니다.
→ QueryBuilder / 날것 SQL / $db->findAll() — 세 가지 방법 모두 허용됩니다.


시나리오 5 — 스킨 완전 독립 핸들러 (CMS 핵심 로직 100% 우회)

boards/skins/my-skin/list/handler.php 파일을 만들면, 목록 조회부터 출력까지 모든 것을 자기 코드로 완전히 대체할 수 있습니다.

boards/skins/my-skin/list/handler.php — CMS 핵심 로직 완전 우회 
<?php
// boards/skins/my-skin/list/handler.php
// 이 파일이 있으면 CMS의 list 처리 로직을 100% 우회합니다.

$db    = $GLOBALS["dx_handler_context"]["db"];
$board = $GLOBALS["dx_handler_context"]["board"];
$auth  = $GLOBALS["dx_handler_context"]["auth"];

// DB를 내 마음대로 쿼리
$page    = max(1, (int)dx_get("page", 1));
$perPage = 10;
$offset  = ($page - 1) * $perPage;

// 날것 SQL이든 QueryBuilder든 원하는 대로
$posts = $db->rows(
    "SELECT p.*, m.nickname FROM dx_posts p",
    "LEFT JOIN dx_members m ON p.member_id = m.id",
    "WHERE p.board_id = ? AND p.status = 1",
    "ORDER BY p.is_notice DESC, p.id DESC",
    "LIMIT ? OFFSET ?",
    array($board["id"], $perPage, $offset)
);

// 레이아웃 감싸기
$dxth = DxTheme::getInstance();
$layoutFile = $dxth->resolve("layout/main.php");

ob_start();
?>
<!— 아무렇게나 HTML 짜기 —>
<div class="my-list">
    <h2 style="font-size:28px;font-weight:900"><?= $board["name"] ?></h2>
    <?php foreach ($posts as $p): ?>
        <div style="padding:12px;border-bottom:1px solid #eee">
            <a href="/<?= $board["board_key"] ?>/view/<?= $p["id"] ?>">
                <?= htmlspecialchars($p["title"]) ?>
            </a>
            <small style="color:gray"> — <?= $p["nickname"] ?></small>
        </div>
    <?php endforeach; ?>
</div>
<?php
$dx_content = ob_get_clean();
if ($layoutFile) require $layoutFile; else echo $dx_content;
✅ 결과: CMS의 list 처리를 완전히 대체한 커스텀 목록 페이지 출력

→ CMS 코어 코드를 한 줄도 수정하지 않고 게시판 목록 로직을 완전히 교체했습니다.
→ 날것 SQL, 즉석 JOIN, 인라인 스타일 — 전부 허용됩니다.
→ 이 파일이 exit을 안 해도 handler.php가 return으로 CMS 흐름을 막아줍니다


04. routes/ + controllers/ 날코딩

규칙 없이 원하는 대로 라우트와 컨트롤러 작성

routes/ — 클로저 라우트로 모든 로직을 한 파일에
routes/ 폴더의 PHP 파일들은 자동으로 로드됩니다. 컨트롤러 없이 클로저 하나에 모든 로직을 넣어도 됩니다.

routes/my_routes.php — 클로저 날코딩 예시
<?php
// routes/my_routes.php — 이 파일만 있으면 됩니다
if (!defined("DX_CMS")) exit;

// 클로저에 모든 로직을 때려 넣기 — 완전 허용
DxRouter::get("/my-page", function() {
    $auth = Auth::getInstance();
    if (!$auth->isLoggedIn()) {
        header("Location: /auth/login"); exit;
    }

    // DB 직접 쿼리 — 어떤 방법이든 OK
    $user  = $auth->user();
    $posts = dx_db("posts")
        ->where("member_id", $user["id"])
        ->orderBy("id", "desc")
        ->limit(10)
        ->get();

    // 날것 HTML 출력
    echo "<h1>내 글 목록 — " . htmlspecialchars($user["nickname"]) . "</h1>";
    foreach ($posts as $p) {
        echo "<p>" . htmlspecialchars($p["title"]) . "</p>";
    }
    exit;
});

// API 엔드포인트도 클로저로 — 구조 없이
DxRouter::post("/api/my-data", function() {
    // CSRF는 미들웨어로 처리
    dx_json(array("status"=>"ok", "time"=>time()));
})->middleware(array("auth", "csrf", "json"));

// 관리자 전용 페이지도 한 줄
DxRouter::get("/secret-page", function() {
    echo "<h1>관리자만 봄</h1>";
    exit;
})->middleware("admin");
controllers/ — 라라벨 문법 필요 없음, PHP 그대로

controllers/MyController.php — 자유로운 컨트롤러
<?php
// controllers/MyController.php
// 클래스 이름만 맞추면 됩니다. 나머지는 자유.
if (!defined("DX_CMS")) exit;

class MyController
{
    // 파라미터는 그냥 배열로 옵니다
    public function index($params = array())
    {
        // 아무렇게나 코딩
        $page   = isset($_GET["page"]) ? (int)$_GET["page"] : 1;
        $search = isset($_GET["q"]) ? trim($_GET["q"]) : "";

        // DB 쿼리 — 스타일 통일 안 해도 됨
        $db = Database::getInstance();
        if ($search) {
            $list = dx_db("posts")
                ->where("title", "LIKE", "%" . $search . "%")
                ->paginate(20);
        } else {
            // 날것 SQL도 OK
            $list["data"] = $db->rows(
                "SELECT * FROM dx_posts WHERE status=1",
                "ORDER BY id DESC LIMIT 20"
            );
        }

        // 레이아웃 포함해서 출력
        ob_start();
        include DX_ROOT . "/pages/my-page.php";
        $dx_content = ob_get_clean();

        $layout = DxTheme::getInstance()->resolve("layout/main.php");
        if ($layout) include $layout; else echo $dx_content;
        exit;
    }

    // POST 처리도 자유롭게
    public function store($params = array())
    {
        // CSRF는 미들웨어가 처리해줌
        $title = dx_post("title", "");
        $content = dx_post("content", "");

        if (!$title) {
            dx_json(array("success"=>false, "msg"=>"제목 필요"), 400);
        }

        $id = dx_db("my_table")->insert(array(
            "title"      => $title,
            "content"    => $content,
            "member_id"  => Auth::getInstance()->get("id"),
            "created_at" => date("Y-m-d H:i:s"),
        ));

        dx_json(array("success"=>true, "id"=>$id));
    }
}

→ 클래스 이름만 파일명과 맞추면 됩니다.
→ extends Controller 불필요. interface 구현 불필요.
→ $_GET/$_POST 직접 접근도 됩니다. dx_get()/dx_post() 안 써도 됩니다.
→ QueryBuilder와 날것 SQL을 같은 파일에 섞어 써도 됩니다.


05. 플러그인도 날코딩 허용

plugin.php에 코드를 마음대로 넣어도 됩니다

plugins/my-plugin/plugin.php — 날코딩 플러그인 예시
<?php
// plugins/my-plugin/plugin.php
// 복잡한 ServiceProvider 패턴? 필요 없습니다.
// 이 파일에 코드를 직접 쓰면 됩니다.
if (!defined("DX_CMS")) exit;

// 서비스 등록
dx_app()->singleton("slack", function() {
    return new SlackNotifier(dx_config("slack_webhook"));
});

// 훅 — 글이 저장될 때 슬랙 알림
dx_add_hook("after_post_save", function($data) {
    $post = isset($data["post"]) ? $data["post"] : array();
    if (empty($post)) return;

    // 날것의 HTTP 요청 — curl 직접 사용
    $payload = json_encode(array(
        "text" => "새 글: " . $post["title"] . " by " . $post["nickname"],
    ));
    $ch = curl_init(dx_config("slack_webhook"));
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/json"));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_exec($ch);
    curl_close($ch);
}, 10);

// 하단에 광고 배너 삽입 — 훅 하나로
dx_add_hook("dx_bottom", function() {
?>
<div style="position:fixed;bottom:0;left:0;right:0;background:#1e293b;
            color:#fff;text-align:center;padding:10px;font-size:13px">
    🎉 My Plugin이 작동 중입니다!
</div>
<?php
}, 99);

// 커스텀 라우트도 여기서 추가 가능
DxRouter::get("/plugin-test", function() {
    echo "<h1>플러그인 테스트 페이지</h1>";
    exit;
});

✅ 결과: 플러그인 활성화만 하면 위 코드가 전부 동작합니다.

→ curl 직접 사용 허용. Guzzle 같은 패키지 없어도 됩니다.
→ 훅에서 inline HTML echo 허용. Blade 디렉티브 불필요.
→ plugin.php 한 파일에 서비스 등록 + 훅 + 라우트를 모두 넣어도 됩니다.


06. extend/ — 파일만 넣으면 실행, 코딩 스타일 무관

가장 자유로운 확장 방법

extend/ 폴더는 훅 등록도, 클래스도, 규칙도 없습니다. PHP 파일을 폴더에 넣으면 자동으로 실행됩니다.
파일 위치 실행 시점 & 가능한 것
extend/top/ 01_check.php DB•세션•Auth 완료 직후. 점검모드•IP차단•글로벌 변수 주입 가능. $_SERVER, $GLOBALS, Auth, Database 모두 사용 가능.
extend/middle/ 01_log.php 라우트 확정 직후. $GLOBALS["dx_route"]로 현재 라우트 정보 접근. 방문자 로그, 접근 제어, A/B 테스트 가능.
extend/bottom/ 01_perf.php 렌더링 완료 후. 성능 로그, 캐시 저장, 정리 작업 가능. 출력 버퍼가 아직 열려 있어 추가 출력도 가능.

extend/top/01_maintenance.php — 점검 모드 (날코딩)
<?php
// extend/top/01_maintenance.php
// 규칙 없이 그냥 PHP 코드를 씁니다.

$mode = dx_config("maintenance_mode", 0);

if ((int)$mode === 1) {
    $auth = Auth::getInstance();

    // 관리자는 그냥 통과
    if ($auth->isAdmin()) return;

    // IP 화이트리스트 — 배열을 그냥 하드코딩해도 됨
    $allowed_ips = array("127.0.0.1", "192.168.1.100");
    if (in_array($_SERVER["REMOTE_ADDR"], $allowed_ips)) return;

    // 점검 페이지를 날것의 HTML로 출력
    http_response_code(503);
    header("Retry-After: 3600");
?>
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>점검 중</title></head>
<body style="text-align:center;font-family:sans-serif;padding:100px">
    <h1 style="font-size:48px">🔧</h1>
    <h2>현재 사이트 점검 중입니다</h2>
    <p style="color:gray">잠시 후 다시 방문해주세요.</p>
</body></html>
<?php
    exit;
}

→ 이 파일 하나로 전체 사이트 점검 모드가 완성됩니다.
→ 훅 등록 불필요. 클래스 불필요. config 설정 하나 + 파일 하나.
→ HTML도 그냥 PHP 파일 안에 섞어 씁니다.


07. 날코딩•막코딩 허용 범위 완전 요약표

어디서, 어떻게 해도 되는지 한눈에
 
코딩 방식 CMS 동작 에러 처리
스킨 list.php — 변수 체크 없음 ✅ 페이지 정상 출력 Notice → 로그, 화면 출력 계속
스킨 list.php — dx_esc() 생략 ✅ 출력됨 (XSS 주의) 출력 되지만 보안 취약, 나중에 수정
extend/ 파일 — 예외 발생 ✅ CMS 계속 실행 safeExec()가 예외 격리, 로그 기록
extend/ 파일 — 정의 안 된 함수 ✅ CMS 계속 실행 set_error_handler가 잡아서 로그만
actions/ — $GLOBALS 직접 접근 ✅ 정식 지원 패턴 erp/actions/inventory.php에서 사용중
list/handler.php — CMS 핵심 우회 ✅ 완전 독립 처리 include 후 return으로 흐름 막음
routes/ — 클로저에 모든 로직 ✅ 정상 동작 클로저 라우트 공식 지원
plugin.php — curl 직접 사용 ✅ 정상 동작 Composer 패키지 없이 표준 PHP
pages/home.php — 인라인 스타일 ✅ 정상 출력 HTML 자유롭게 작성 가능
controllers/ — PSR 무시 ✅ 정상 동작 클래스 이름만 파일명과 일치하면 됨
날것 SQL — $db->rows() 직접 ✅ PDO 바인딩 자동 SQL Injection은 ? 플레이스홀더가 방어
QueryBuilder + 날것 SQL 혼용 ✅ 동작 같은 파일에 두 방법 혼용 허용

한 줄 결론

DXCMS에서 틀린 코딩 방식은 없습니다.
에러가 나면 로그에 기록하고, CMS는 계속 돌아갑니다.
나중에 에러 로그를 보면서 고치면 됩니다.

단 하나의 원칙만 지키면 됩니다:
→ 파일 첫 줄에 if (!defined("DX_CMS")) exit; 를 넣어서 직접 접근을 차단하세요.


08. 디버그 모드 — 에러를 화면에서 바로 보는 법

개발 중에는 숨기지 않고 다 보여줍니다

data/config.php — 디버그 모드 설정
<?php
// data/config.php 에 추가
define('DX_DEBUG', true);

// 이 설정 하나로:
// - 모든 PHP 에러가 화면에 표시됩니다.
// - 스킨 에러가 에러 박스 형태로 표시됩니다.
// - extend/ 파일 에러가 화면에 출력됩니다.
// - data/error.log에도 동시 기록됩니다.

// 운영 서버에서는 이 줄을 제거하거나 false로 설정하세요.
define('DX_DEBUG', false);

개발 흐름:

1. DX_DEBUG = true 설정
2. 마음대로 날코딩
3. 에러가 나면 화면에서 바로 확인
4. 고치면서 개선
5. 배포 전 DX_DEBUG = false 또는 제거

에러가 무서워서 조심스럽게 짤 필요가 없습니다.
일단 돌아가게 만들고, 에러를 보면서 다듬으면 됩니다.

댓글0

로그인 후 댓글을 작성할 수 있습니다.
3.2 폴더 구조 assets/ — 정적 자원 2026.04.21 3.2 폴더 구조 plugins/ — 플러그인 시스템 2026.04.21 3.2 폴더 구조 themes/ — 테마 시스템 2026.04.21 3.2 폴더 구조 boards/ — 게시판 시스템 2026.04.21 3.2 폴더 구조 admin/ — 관리자 패널 2026.04.21 3.2 폴더 구조 core/ — CMS 엔진 2026.04.21 3.1 엔진 개요 실행 구조 개요 2026.04.21 3.1 엔진 개요 DX 엔진 구조 설명 2026.04.21 2. 시작 가이드 설치 시 보안 경로 구조 2026.04.20 2. 시작 가이드 서버별 설정 파일 상세 2026.04.20 2. 시작 가이드 기본 폴더 구조 설명 2026.04.20 2. 시작 가이드 설치 절차 2026.04.20 2. 시작 가이드 설치 환경 (PHP 버전, 서버 환경) 2026.04.20 비전 DXCMS 비전 2026.04.20 라이선스 DXCMS 오픈소스 및 제3자 소프트웨어 저작권 공지 2026.04.20 라이선스 DXCMS 라이선스 (LGPL 3.0) 2026.04.20 1. DX 철학 / 개념 생태계 확장 전략 2026.04.20 1. DX 철학 / 개념 DXCMS가 지향하는 방향 (플랫폼 vs 단순 CMS) 2026.04.20 1. DX 철학 / 개념 프레임워크 + CMS 통합 구조의 의미 2026.04.20 1. DX 철학 / 개념 기존 CMS와의 구조적 한계 2026.04.20
31
전체 회원
503
전체 게시글
770
전체 댓글
441
오늘 방문
33,173
전체 방문
2
현재 접속
인기글 7일 이내
최신글
최신댓글
목록