📚 PHP 중급 - 3주차: 게시판 CRUD 구현 (기본) - 02 글 목록, 글 작성, 글 보기, 글 수정, 글 삭제

2025. 7. 22. 10:28프로그램/PHP 중급

📚 PHP 중급 - 3주차: 게시판 CRUD 구현 (기본) - 02 글 목록, 글 작성, 글 보기, 글 수정, 글 삭제

📚 PHP 중급 - 3주차: 게시판 CRUD 구현 (기본) - 02 글 목록, 글 작성, 글 보기, 글 수정, 글 삭제



안녕하세요! 지난주에 이어 이번 주에는 #PHP #게시판 구현의 핵심인 #CRUD 기능을 함께 만들어 볼 거예요. #CRUD는 Create(생성), Read(읽기), Update(수정), Delete(삭제)의 약자로, 대부분의 #데이터베이스 기반 서비스에서 기본이 되는 기능이죠.

 


1. 개요 및 목표

 

이번 주차에서는 간단한 #게시판을 만들어서 #데이터베이스에 #글을 저장하고, 읽고, 수정하고, 삭제하는 과정을 #PHP 코드로 직접 구현해 볼 거예요. #프론트엔드 디자인보다는 #백엔드 #로직 구현에 초점을 맞춥니다.

 

주요 목표:

  • #글 목록(#Read 기능의 일부) 표시
  • #글 작성(#Create 기능) 폼 및 처리
  • #글 보기(#Read 기능) 상세 페이지
  • #글 수정(#Update 기능) 폼 및 처리
  • #글 삭제(#Delete 기능) 처리

 


2. 데이터베이스 준비

가장 먼저 #게시판 #글을 저장할 #데이터베이스 #테이블이 필요해요. #MySQL을 기준으로 설명할게요. board라는 이름의 #테이블을 만들고 다음처럼 필드를 구성해 보세요.

 

SQL
 
CREATE TABLE board (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    author VARCHAR(100) NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

 

  • id: 각 #글의 고유 번호 (기본 키, 자동 증가)
  • title: #글의 제목
  • content: #글의 내용
  • author: #작성자
  • created_at: #글이 작성된 시간
  • updated_at: #글이 마지막으로 수정된 시간

 


3. 데이터베이스 연결 설정

 

#PHP 스크립트에서 #데이터베이스에 접속하기 위한 설정 파일(db_config.php 등으로 저장)을 만들어 주세요.

 

PHP
 
<?php
// db_config.php
$servername = "localhost"; // 데이터베이스 호스트
$username = "your_db_username"; // 데이터베이스 사용자 이름
$password = "your_db_password"; // 데이터베이스 비밀번호
$dbname = "your_database_name"; // 사용할 데이터베이스 이름

try {
    $conn = new PDO("mysql:host=$servername;dbname=$dbname;charset=utf8", $username, $password);
    // PDO 오류 모드를 예외 처리로 설정
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    // 선택적: PDO 기본 페치 모드 설정 (연관 배열)
    $conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
    // echo "데이터베이스 연결 성공!"; // 테스트용
} catch(PDOException $e) {
    die("데이터베이스 연결 실패: " . $e->getMessage());
}
?>

 


4. 글 목록 보기 (list.php)

이제 #게시판의 #글 목록을 보여주는 페이지를 만들 거예요.

 

PHP
 
<?php
// list.php
include 'db_config.php'; // 데이터베이스 연결 설정 포함

// 페이지네이션을 위한 설정
$posts_per_page = 10; // 페이지당 보여줄 글 수
$current_page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$offset = ($current_page - 1) * $posts_per_page;

try {
    // 총 글 수 가져오기
    $total_posts_stmt = $conn->query("SELECT COUNT(*) FROM board");
    $total_posts = $total_posts_stmt->fetchColumn();
    $total_pages = ceil($total_posts / $posts_per_page);

    // 게시글 목록 가져오기 (최신 글부터, 페이지네이션 적용)
    $stmt = $conn->prepare("SELECT id, title, author, created_at FROM board ORDER BY created_at DESC LIMIT :offset, :limit");
    $stmt->bindParam(':offset', $offset, PDO::PARAM_INT);
    $stmt->bindParam(':limit', $posts_per_page, PDO::PARAM_INT);
    $stmt->execute();
    $posts = $stmt->fetchAll();

} catch(PDOException $e) {
    echo "오류: " . $e->getMessage();
    $posts = []; // 오류 시 빈 배열로 초기화
}
?>
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>게시판 글 목록</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        table { width: 80%; border-collapse: collapse; margin: 20px 0; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
        .pagination a { margin: 0 5px; text-decoration: none; }
    </style>
</head>
<body>
    <h1>게시판 글 목록</h1>
    <p><a href="write.php">새 글 작성</a></p>
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>제목</th>
                <th>작성자</th>
                <th>작성일</th>
            </tr>
        </thead>
        <tbody>
            <?php if (count($posts) > 0): ?>
                <?php foreach ($posts as $post): ?>
                    <tr>
                        <td><?php echo htmlspecialchars($post['id']); ?></td>
                        <td><a href="view.php?id=<?php echo htmlspecialchars($post['id']); ?>"><?php echo htmlspecialchars($post['title']); ?></a></td>
                        <td><?php echo htmlspecialchars($post['author']); ?></td>
                        <td><?php echo htmlspecialchars($post['created_at']); ?></td>
                    </tr>
                <?php endforeach; ?>
            <?php else: ?>
                <tr>
                    <td colspan="4">아직 작성된 글이 없습니다.</td>
                </tr>
            <?php endif; ?>
        </tbody>
    </table>

    <div class="pagination">
        <?php for ($i = 1; $i <= $total_pages; $i++): ?>
            <a href="?page=<?php echo $i; ?>" <?php echo ($i == $current_page) ? 'style="font-weight: bold;"' : ''; ?>><?php echo $i; ?></a>
        <?php endfor; ?>
    </div>
</body>
</html>

 


5. 글 작성 (write.php)

#새로운 #글을 작성할 수 있는 폼과 제출 시 #데이터베이스에 저장하는 #로직이에요.

 

PHP
 
<?php
// write.php
include 'db_config.php';

$message = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $title = trim($_POST['title'] ?? '');
    $content = trim($_POST['content'] ?? '');
    $author = trim($_POST['author'] ?? '');

    if (empty($title) || empty($content) || empty($author)) {
        $message = "모든 필드를 채워주세요.";
    } else {
        try {
            $stmt = $conn->prepare("INSERT INTO board (title, content, author) VALUES (:title, :content, :author)");
            $stmt->bindParam(':title', $title);
            $stmt->bindParam(':content', $content);
            $stmt->bindParam(':author', $author);
            $stmt->execute();

            $message = "글이 성공적으로 작성되었습니다!";
            header('Location: list.php'); // 글 목록으로 리다이렉트
            exit();
        } catch(PDOException $e) {
            $message = "글 작성 오류: " . $e->getMessage();
        }
    }
}
?>
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>새 글 작성</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        form div { margin-bottom: 10px; }
        label { display: block; margin-bottom: 5px; }
        input[type="text"], textarea { width: 50%; padding: 8px; border: 1px solid #ddd; }
        textarea { height: 150px; resize: vertical; }
        button { padding: 10px 15px; background-color: #007bff; color: white; border: none; cursor: pointer; }
        button:hover { background-color: #0056b3; }
        .message { color: red; margin-bottom: 10px; }
    </style>
</head>
<body>
    <h1>새 글 작성</h1>
    <?php if ($message): ?>
        <p class="message"><?php echo $message; ?></p>
    <?php endif; ?>
    <form action="write.php" method="POST">
        <div>
            <label for="title">제목:</label>
            <input type="text" id="title" name="title" required>
        </div>
        <div>
            <label for="author">작성자:</label>
            <input type="text" id="author" name="author" required>
        </div>
        <div>
            <label for="content">내용:</label>
            <textarea id="content" name="content" required></textarea>
        </div>
        <button type="submit">작성 완료</button>
        <button type="button" onclick="location.href='list.php'">목록으로</button>
    </form>
</body>
</html>

 


6. 글 보기 (view.php)

 

특정 #글의 내용을 상세하게 보여주는 페이지입니다.

 

PHP
 
<?php
// view.php
include 'db_config.php';

$post = null;
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;

if ($id > 0) {
    try {
        $stmt = $conn->prepare("SELECT id, title, content, author, created_at, updated_at FROM board WHERE id = :id");
        $stmt->bindParam(':id', $id, PDO::PARAM_INT);
        $stmt->execute();
        $post = $stmt->fetch();

        if (!$post) {
            echo "해당 글을 찾을 수 없습니다.";
            exit();
        }
    } catch(PDOException $e) {
        echo "오류: " . $e->getMessage();
        exit();
    }
} else {
    echo "잘못된 접근입니다.";
    exit();
}
?>
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>글 보기: <?php echo htmlspecialchars($post['title'] ?? '제목 없음'); ?></title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .post-info { margin-bottom: 15px; color: #555; font-size: 0.9em; }
        .post-content { border: 1px solid #eee; padding: 15px; min-height: 150px; line-height: 1.8; }
        .actions { margin-top: 20px; }
        .actions a, .actions button { margin-right: 10px; padding: 8px 12px; text-decoration: none; color: white; border: none; cursor: pointer; }
        .actions .edit { background-color: #28a745; }
        .actions .delete { background-color: #dc3545; }
        .actions .list { background-color: #6c757d; }
    </style>
</head>
<body>
    <h1><?php echo htmlspecialchars($post['title']); ?></h1>
    <div class="post-info">
        작성자: <?php echo htmlspecialchars($post['author']); ?> | 
        작성일: <?php echo htmlspecialchars($post['created_at']); ?> | 
        수정일: <?php echo htmlspecialchars($post['updated_at']); ?>
    </div>
    <div class="post-content">
        <?php echo nl2br(htmlspecialchars($post['content'])); // 줄 바꿈 적용 ?>
    </div>

    <div class="actions">
        <a href="edit.php?id=<?php echo htmlspecialchars($post['id']); ?>" class="edit">수정</a>
        <form action="delete.php" method="POST" style="display:inline;" onsubmit="return confirm('정말로 이 글을 삭제하시겠습니까?');">
            <input type="hidden" name="id" value="<?php echo htmlspecialchars($post['id']); ?>">
            <button type="submit" class="delete">삭제</button>
        </form>
        <a href="list.php" class="list">목록</a>
    </div>
</body>
</html>

 


7. 글 수정 (edit.php)

기존 #글의 내용을 가져와서 수정하고, #데이터베이스에 업데이트하는 #로직이에요.

 

PHP
 
<?php
// edit.php
include 'db_config.php';

$message = '';
$post = null;
$id = isset($_REQUEST['id']) ? (int)$_REQUEST['id'] : 0; // GET 또는 POST 모두 처리

if ($id > 0) {
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        // POST 요청 (수정 완료)
        $title = trim($_POST['title'] ?? '');
        $content = trim($_POST['content'] ?? '');
        $author = trim($_POST['author'] ?? ''); // 작성자는 보통 수정하지 않지만, 예시로 포함

        if (empty($title) || empty($content) || empty($author)) {
            $message = "모든 필드를 채워주세요.";
        } else {
            try {
                $stmt = $conn->prepare("UPDATE board SET title = :title, content = :content, author = :author, updated_at = NOW() WHERE id = :id");
                $stmt->bindParam(':title', $title);
                $stmt->bindParam(':content', $content);
                $stmt->bindParam(':author', $author);
                $stmt->bindParam(':id', $id, PDO::PARAM_INT);
                $stmt->execute();

                $message = "글이 성공적으로 수정되었습니다!";
                header('Location: view.php?id=' . $id); // 수정된 글 보기 페이지로 리다이렉트
                exit();
            } catch(PDOException $e) {
                $message = "글 수정 오류: " . $e->getMessage();
            }
        }
    }

    // GET 요청 또는 POST 요청 실패 시 기존 글 내용 불러오기
    try {
        $stmt = $conn->prepare("SELECT id, title, content, author FROM board WHERE id = :id");
        $stmt->bindParam(':id', $id, PDO::PARAM_INT);
        $stmt->execute();
        $post = $stmt->fetch();

        if (!$post) {
            echo "해당 글을 찾을 수 없습니다.";
            exit();
        }
    } catch(PDOException $e) {
        echo "오류: " . $e->getMessage();
        exit();
    }
} else {
    echo "잘못된 접근입니다.";
    exit();
}
?>
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>글 수정: <?php echo htmlspecialchars($post['title'] ?? '제목 없음'); ?></title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        form div { margin-bottom: 10px; }
        label { display: block; margin-bottom: 5px; }
        input[type="text"], textarea { width: 50%; padding: 8px; border: 1px solid #ddd; }
        textarea { height: 150px; resize: vertical; }
        button { padding: 10px 15px; background-color: #007bff; color: white; border: none; cursor: pointer; }
        button:hover { background-color: #0056b3; }
        .message { color: red; margin-bottom: 10px; }
    </style>
</head>
<body>
    <h1>글 수정</h1>
    <?php if ($message): ?>
        <p class="message"><?php echo $message; ?></p>
    <?php endif; ?>
    <form action="edit.php" method="POST">
        <input type="hidden" name="id" value="<?php echo htmlspecialchars($post['id']); ?>">
        <div>
            <label for="title">제목:</label>
            <input type="text" id="title" name="title" value="<?php echo htmlspecialchars($post['title']); ?>" required>
        </div>
        <div>
            <label for="author">작성자:</label>
            <input type="text" id="author" name="author" value="<?php echo htmlspecialchars($post['author']); ?>" required>
        </div>
        <div>
            <label for="content">내용:</label>
            <textarea id="content" name="content" required><?php echo htmlspecialchars($post['content']); ?></textarea>
        </div>
        <button type="submit">수정 완료</button>
        <button type="button" onclick="location.href='view.php?id=<?php echo htmlspecialchars($post['id']); ?>'">취소</button>
    </form>
</body>
</html>

 


8. 글 삭제 (delete.php)

 

#특정 #글을 #데이터베이스에서 제거하는 #로직이에요. 보안을 위해 #POST 방식으로 요청을 받도록 하는 것이 좋아요.

 

PHP
 
<?php
// delete.php
include 'db_config.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $id = isset($_POST['id']) ? (int)$_POST['id'] : 0;

    if ($id > 0) {
        try {
            $stmt = $conn->prepare("DELETE FROM board WHERE id = :id");
            $stmt->bindParam(':id', $id, PDO::PARAM_INT);
            $stmt->execute();

            echo "글이 성공적으로 삭제되었습니다.";
            header('Location: list.php'); // 글 목록으로 리다이렉트
            exit();
        } catch(PDOException $e) {
            echo "글 삭제 오류: " . $e->getMessage();
            // 오류 시에도 사용자에게 메시지를 보여주고 적절한 페이지로 리다이렉트
            echo "<p><a href='list.php'>목록으로 돌아가기</a></p>";
            exit();
        }
    } else {
        echo "잘못된 접근입니다.";
        echo "<p><a href='list.php'>목록으로 돌아가기</a></p>";
        exit();
    }
} else {
    echo "잘못된 요청 방식입니다."; // POST가 아닌 방식으로 접근 시
    echo "<p><a href='list.php'>목록으로 돌아가기</a></p>";
    exit();
}
?>

 


9. 마치며

 

이번 주에는 #PHP를 사용하여 #게시판의 기본적인 #CRUD 기능을 모두 구현해 보았어요. #데이터베이스 연결부터 #글 생성, 읽기, 수정, 삭제까지의 전체 #로직 흐름을 이해하는 데 도움이 되셨기를 바랍니다. 다음 주차에는 이 기본 #게시판을 좀 더 개선하고 페이징 적용 방법에 대해 다룰 예정이니 기대해 주세요!

 

 

가성비 VPN 루젠VPN

VPN이 필요 하다면

LuzenVPN(루젠VPN) #착한가격, #빠른서비스, #안정성 까지 갖춘, #클린아이피 제공 으로 #마케팅 부터 #게임 까지! 한번에 해결

https://vpn.luzensoft.com/

 

5,500원 / IP교체 1,100원 / 유동프록시 22,000원 | LuzenVPN 루젠VPN

국내최저가 고정IP서비스,유동프록시(IP4000개이상제공),PPTP,L2TP,IPSec,OpenVPNVPN,통신사VPN,VPN프로그램,고정IP,고정아이피,PPTP,저렴한VPN,리니지MVPN,리니지VPN,아이온VPN,던파VPN,유동프록시,유동PROXY,바이

vpn.luzensoft.com