1. 시작하기 — DB 인스턴스 얻기
DXCMS의 모든 DB 작업은 Database 싱글턴 클래스를 통해 이루어집니다. 어떤 PHP 파일(페이지, 스킨, 플러그인)에서든 아래 한 줄로 시작합니다.
$db = Database::getInstance();
// 테이블명 생성 — prefix(dx_) 자동 포함
$tbl = $db->table('posts'); // => "dx_posts"
$tbl = $db->table('members'); // => "dx_members"
$tbl = $db->table('studio_posts'); // => "dx_studio_posts"
※ 반드시 $db->table()을 사용하세요. 테이블명을 하드코딩하면 prefix 변경 시 모두 수정해야 합니다
2. BIGINT ID 핵심 주의사항
DXCMS는 posts, comments 등의 PK를 microtime 기반 16자리 BIGINT로 생성합니다. PHP 32bit 환경에서 (int) 캐스팅하면 값이 손상됩니다.
// ❌ 절대 금지 — 32bit PHP에서 값이 잘림
$id = (int)$_p['id']; // 1778651007020800 → 손상
$map[(int)$row['post_id']] = $row; // 키 손상
// ✓ 올바른 방법 — 문자열로 처리
$id = (string)$_p['id']; // "1778651007020800" 유지
$map[(string)$row['post_id']] = $row; // 키 정확히 저장
// 쿼리 바인딩은 문자열 그대로 전달
$db->row("SELECT * FROM `{$tbl}` WHERE id = ?", array($id));
※ PDO 설정에 ATTR_STRINGIFY_FETCHES=true 가 있어 DB에서 받은 값은 이미 문자열입니다. PHP 코드에서 (int) 캐스팅만 하지 않으면 됩니다.
3. SELECT — 데이터 조회
3-1. row() — 단일 행 조회
조건에 맞는 첫 번째 행을 연관 배열로 반환합니다. 결과 없으면 null 반환.
$db = Database::getInstance();
$tbl = $db->table('members');
$member = $db->row(
"SELECT * FROM `{$tbl}` WHERE id = ? AND status = 1",
array($memberId)
);
if ($member) {
echo $member['name'];
echo $member['email'];
}
3-2. rows() — 여러 행 조회
조건에 맞는 모든 행을 2차원 배열로 반환합니다. 결과 없으면 빈 배열 반환.
$db = Database::getInstance();
$tbl = $db->table('posts');
$posts = $db->rows(
"SELECT id, title, created_at
FROM `{$tbl}`
WHERE board_id = ? AND status = 1
ORDER BY id DESC LIMIT ?",
array($boardId, 10)
);
foreach ($posts as $p) {
echo $p['title'];
}
3-3. value() — 단일 값 조회
COUNT, SUM 등 집계 함수나 컬럼 하나의 값만 필요할 때 사용합니다.
$db = Database::getInstance();
$tbl = $db->table('members');
// 회원 수
$count = $db->value("SELECT COUNT(*) FROM `{$tbl}` WHERE status = 1");
// 특정 회원의 포인트
$point = $db->value(
"SELECT point FROM `{$tbl}` WHERE id = ?",
array($memberId)
);
3-4. find() — 편의 메서드 (단일 행)
SQL 없이 조건 배열로 단일 행을 조회합니다.
$db = Database::getInstance();
// 기본
$member = $db->find('members', array('id' => $memberId));
// 반환 컬럼 지정
$member = $db->find(
'members',
array('email' => $email, 'status' => 1),
'id, name, email'
);
3-5. findAll() — 편의 메서드 (여러 행)
SQL 없이 조건 배열로 여러 행을 조회합니다. 정렬과 LIMIT도 지정 가능합니다.
$db = Database::getInstance();
$rows = $db->findAll(
'posts', // 테이블명
array('board_id' => $boardId, 'status' => 1), // WHERE
'id, title, created_at', // 반환 컬럼
'id DESC', // ORDER BY
'10' // LIMIT
);
3-6. IN 절 — 여러 ID 한 번에 조회
복수 ID를 한 번의 쿼리로 조회할 때 IN 절을 사용합니다. BIGINT ID는 반드시 문자열로 처리합니다.
$db = Database::getInstance();
$tbl = $db->table('studio_posts');
// ID 배열 — 문자열 유지
$ids = array('1778651007020800', '1778766294011100');
// IN 절 플레이스홀더 생성
$ph = implode(',', array_fill(0, count($ids), '?'));
// 쿼리
$rows = $db->rows(
"SELECT post_id, youtube_url FROM `{$tbl}` WHERE post_id IN ({$ph})",
$ids
);
// string 키로 맵 생성 — (int) 절대 금지
$map = array();
foreach ($rows as $r) {
$map[(string)$r['post_id']] = $r;
}
// 사용
$info = isset($map[(string)$postId]) ? $map[(string)$postId] : array();
4. INSERT — 데이터 추가
4-1. insertRow() — 편의 메서드
컬럼 => 값 배열을 전달하면 INSERT SQL을 자동 생성합니다. AUTO_INCREMENT PK 테이블에 사용합니다.
$db = Database::getInstance();
$db->insertRow('download_log', array(
'file_id' => $fileId,
'member_id' => $memberId,
'ip' => dx_ip(),
'point_used' => 0,
'created_at' => date('Y-m-d H:i:s'),
));
// 반환값: 마지막 AUTO_INCREMENT ID
$newId = $db->insertRow('categories', array(
'name' => '공지사항',
'sort_order' => 1,
'created_at' => date('Y-m-d H:i:s'),
));
4-2. insertWithMicrotimeId() — BIGINT ID 자동 생성
posts, comments 등 microtime 기반 BIGINT PK 테이블에 삽입합니다. ID를 자동 생성하고 중복 검사 후 삽입합니다.
$db = Database::getInstance();
$postId = $db->insertWithMicrotimeId('posts', array(
'board_id' => $boardId,
'member_id' => $memberId,
'title' => '제목',
'content' => '내용',
'status' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
));
// 반환값은 문자열 — 절대 (int) 캐스팅 금지
echo $postId; // "1778651007020800"
4-3. insert() / query() — 직접 SQL
INSERT … ON DUPLICATE KEY UPDATE 등 복잡한 구문은 직접 SQL을 작성합니다.
$db = Database::getInstance();
$tbl = $db->table('studio_posts');
$now = date('Y-m-d H:i:s');
// 직접 INSERT
$db->query(
"INSERT INTO `{$tbl}`
(post_id, youtube_url, producer, creator, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?)",
array($postId, $youtubeUrl, $producer, $creator, $now, $now)
);
// ON DUPLICATE KEY UPDATE (UPSERT)
$db->query(
"INSERT INTO `{$db->table('settings')}` (setting_key, setting_value)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)",
array('site_name', 'DXCMS')
);
5. UPDATE — 데이터 수정
5-1. updateRow() — 편의 메서드
수정할 데이터와 WHERE 조건을 각각 배열로 전달합니다. 모든 조건은 AND로 연결됩니다.
$db = Database::getInstance();
// 단일 컬럼 수정
$db->updateRow(
'members',
array('point' => $newPoint), // SET
array('id' => $memberId) // WHERE
);
// 여러 컬럼 수정
$db->updateRow(
'studio_posts',
array(
'youtube_url' => $youtubeUrl,
'producer' => $producer,
'updated_at' => date('Y-m-d H:i:s'),
),
array('post_id' => $postId) // BIGINT — 문자열 그대로
);
5-2. query() — 직접 SQL UPDATE
증감 연산(+=, -=), CASE WHEN 등 복잡한 UPDATE는 직접 SQL을 작성합니다.
$db = Database::getInstance();
$tbl = $db->table('members');
// 포인트 차감 — 조건부 (0 미만 방지)
$db->query(
"UPDATE `{$tbl}` SET point = point - ? WHERE id = ? AND point >= ?",
array($dlPoint, $memberId, $dlPoint)
);
// 조회수 +1
$db->query(
"UPDATE `{$db->table('posts')}` SET view_count = view_count + 1 WHERE id = ?",
array($postId)
);
// 반환값: 영향받은 행 수
$affected = $db->query(
"UPDATE `{$tbl}` SET status = 0 WHERE last_login < ?",
array(date('Y-m-d', strtotime('-1 year')))
);
6. DELETE — 데이터 삭제
6-1. deleteRow() — 편의 메서드
WHERE 조건 배열을 전달하면 DELETE SQL을 자동 생성합니다.
$db = Database::getInstance();
// 단일 행 삭제
$db->deleteRow('studio_posts', array('post_id' => $postId));
// 여러 조건 (AND)
$db->deleteRow(
'download_log',
array('member_id' => $memberId, 'file_id' => $fileId)
);
6-2. query() — 직접 SQL DELETE
IN 절이나 복잡한 조건이 필요한 삭제는 직접 SQL을 작성합니다.
$db = Database::getInstance();
$tbl = $db->table('post_files');
// 게시글 파일 전체 삭제
$db->query(
"DELETE FROM `{$tbl}` WHERE post_id = ?",
array($postId)
);
// IN 절 삭제
$ids = array('1778651007020800', '1778766294011100');
$ph = implode(',', array_fill(0, count($ids), '?'));
$db->query("DELETE FROM `{$tbl}` WHERE id IN ({$ph})", $ids);
// 날짜 조건 삭제
$db->query(
"DELETE FROM `{$db->table('visit_log')}` WHERE created_at < ?",
array(date('Y-m-d', strtotime('-30 days')))
);
7. 트랜잭션 — 원자적 처리
여러 쿼리를 하나의 단위로 처리해야 할 때 사용합니다. 오류 발생 시 rollback()으로 모든 변경을 취소합니다.
$db = Database::getInstance();
try {
$db->begin(); // 트랜잭션 시작
// 1. 포인트 차감
$db->query(
"UPDATE `{$db->table('members')}` SET point = point - ? WHERE id = ?",
array($price, $memberId)
);
// 2. 구매 로그 기록
$db->insertRow('purchase_log', array(
'member_id' => $memberId,
'item_id' => $itemId,
'price' => $price,
'created_at' => date('Y-m-d H:i:s'),
));
// 3. 재고 감소
$db->query(
"UPDATE `{$db->table('items')}` SET stock = stock - 1 WHERE id = ?",
array($itemId)
);
$db->commit(); // 성공 시 커밋
} catch (Exception $e) {
$db->rollback(); // 실패 시 전체 롤백
dx_log('트랜잭션 실패: ' . $e->getMessage(), 'error');
}
8. 기타 편의 메서드
8-1. exists() — 존재 여부
$db = Database::getInstance();
// 이메일 중복 확인
$isDuplicate = $db->exists('members', array('email' => $email));
// 복합 조건
$isOwner = $db->exists('posts', array(
'id' => $postId,
'member_id' => $memberId,
));
8-2. count() — 행 수
$db = Database::getInstance();
$total = $db->count('members'); // 전체
$active = $db->count('members', array('status' => 1)); // 조건부
8-3. tableExists() — 테이블 존재 여부
$db = Database::getInstance();
if (!$db->tableExists($db->table('studio_posts'))) {
$db->query(
"CREATE TABLE IF NOT EXISTS `{$db->table('studio_posts')}` (
post_id BIGINT UNSIGNED NOT NULL,
youtube_url VARCHAR(500) NOT NULL DEFAULT '',
PRIMARY KEY (post_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
);
}
8-4. escape() — 문자열 이스케이프
$db = Database::getInstance();
// MATCH ... AGAINST 등 플레이스홀더 미지원 구문에만 사용
$kw = $db->escape($userInput);
$db->rows("SELECT * FROM `{$tbl}` WHERE MATCH(title) AGAINST('{$kw}')");
// 일반 쿼리는 반드시 플레이스홀더(?) 사용
$db->row("SELECT * FROM `{$tbl}` WHERE id = ?", array($id));
9. 실전 예제 — studio 데이터 조회
board_latest 스킨 파일 안에서 dx_studio_posts 테이블을 직접 조회하는 실제 코드입니다.
// 1. DB 인스턴스 + 테이블명
$db = Database::getInstance();
$tbl = $db->table('studio_posts'); // "dx_studio_posts"
// 2. posts 배열에서 ID 수집 — 문자열 유지 필수
$ids = array();
foreach ($posts as $p) {
if (!empty($p['id'])) $ids[] = $p['id'];
}
// 3. IN 절로 한 번에 조회
$map = array();
if (!empty($ids)) {
$ph = implode(',', array_fill(0, count($ids), '?'));
$rows = $db->rows(
"SELECT post_id, youtube_url, producer, creator
FROM `{$tbl}` WHERE post_id IN ({$ph})",
$ids
);
// string 키로 맵 생성 — (int) 절대 금지
foreach ($rows as $r) {
$map[(string)$r['post_id']] = $r;
}
}
// 4. 카드 렌더링에서 사용
foreach ($posts as $p) {
$row = isset($map[(string)$p['id']]) ? $map[(string)$p['id']] : array();
$ytUrl = isset($row['youtube_url']) ? $row['youtube_url'] : '';
// YouTube ID 추출
$vidId = '';
if ($ytUrl && preg_match('/[?&]v=([a-zA-Z0-9_-]{11})/', $ytUrl, $m)) {
$vidId = $m[1];
}
$thumb = $vidId
? 'https://img.youtube.com/vi/' . $vidId . '/hqdefault.jpg'
: '';
}