이 문서가 답하는 질문
"DXCMS는 정해진 방식대로만 코딩해야 하는가?"→ 아닙니다. 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 또는 제거
에러가 무서워서 조심스럽게 짤 필요가 없습니다.
일단 돌아가게 만들고, 에러를 보면서 다듬으면 됩니다.