PHP

PHP

    ›웹 개발 실전

    eBook

    • eBook 다운로드

    개발 환경 만들기

    • PHP 시작하기
    • 윈도우용 APM 설치
    • 리눅스용 APM 설치
    • 에디터 설치하기
    • APM 환경 테스트

    기초 문법

    • PHP 동작 방식
    • PHP는 무엇인가?
    • PHP 모드와 HTML 모드
    • 변수 - 타입과 문자열
    • 변수 - 배열, 객체, 널
    • 식별자
    • 변수의 범위
    • 상수
    • 연산자
    • 연산자 우선순위
    • 제어 구조 - if, match
    • 제어 구조 - 반복문
    • 제어 구조 - include, require, 예외 처리
    • 함수

    웹 개발 실전

    • 미리 정의된 변수와 외부 입력 처리
    • HTML 폼과 PHP
    • 쿠키와 세션
    • 파일 입출력
    • 방명록 만들기
    • 게시판 만들기
    • 계층형 게시판 만들기
    • 게시판 개선하기
    • 디버깅과 에러 처리

    게시판 만들기

    게시판이란?

    게시판은 웹 개발의 대표적인 CRUD 예제입니다.
    CRUD는 Create(글쓰기), Read(읽기), Update(수정), Delete(삭제) 의 앞 글자를 딴 말입니다.

    이 네 가지 동작을 구현할 수 있으면, 웹 서비스에서 데이터를 다루는 기본기가 갖춰진 겁니다.
    회원 관리, 상품 관리, 댓글… 모두 CRUD의 응용입니다.


    데이터베이스 설계

    CREATE TABLE board (
        id      INT UNSIGNED   NOT NULL AUTO_INCREMENT,
        name    VARCHAR(20)    NOT NULL,
        email   VARCHAR(100)   NULL,
        pass    VARCHAR(255)   NOT NULL,
        title   VARCHAR(100)   NOT NULL,
        content TEXT           NOT NULL,
        wdate   DATETIME       NOT NULL DEFAULT CURRENT_TIMESTAMP,
        ip      VARCHAR(45)    NOT NULL,
        view    INT UNSIGNED   NOT NULL DEFAULT 0,
        PRIMARY KEY (id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    

    ip VARCHAR(45)는 IPv6 주소까지 저장할 수 있는 길이입니다.


    DB 연결 (db.php)

    모든 파일에서 공통으로 require해서 씁니다.

    <?php
    declare(strict_types=1);
    
    $dsn = 'mysql:host=localhost;dbname=mydb;charset=utf8mb4';
    
    try {
        $pdo = new PDO($dsn, '아이디', '비밀번호', [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $e) {
        error_log($e->getMessage());
        exit('데이터베이스 연결에 실패했습니다.');
    }
    

    글 목록 (list.php)

    <?php
    declare(strict_types=1);
    
    require 'db.php';
    
    $pageSize     = 10;
    $pageListSize = 10;
    $page         = max(1, (int) ($_GET['page'] ?? 1));
    $offset       = ($page - 1) * $pageSize;
    
    $total = (int) $pdo->query("SELECT COUNT(*) FROM board")->fetchColumn();
    $totalPage = (int) ceil($total / $pageSize);
    
    $stmt = $pdo->prepare(
        "SELECT * FROM board ORDER BY id DESC LIMIT :limit OFFSET :offset"
    );
    $stmt->bindValue(':limit',  $pageSize, PDO::PARAM_INT);
    $stmt->bindValue(':offset', $offset,   PDO::PARAM_INT);
    $stmt->execute();
    $rows = $stmt->fetchAll();
    ?>
    <!DOCTYPE html>
    <html lang="ko">
    <head><meta charset="UTF-8"><title>게시판</title></head>
    <body>
    <table border="1" width="580">
        <tr>
            <th width="40">번호</th>
            <th>제목</th>
            <th width="80">글쓴이</th>
            <th width="80">날짜</th>
            <th width="50">조회</th>
        </tr>
        <?php foreach ($rows as $row): ?>
        <tr>
            <td align="center"><?= $row['id'] ?></td>
            <td>
                <a href="read.php?id=<?= $row['id'] ?>&page=<?= $page ?>">
                    <?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?>
                </a>
            </td>
            <td align="center"><?= htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8') ?></td>
            <td align="center"><?= substr($row['wdate'], 0, 10) ?></td>
            <td align="center"><?= $row['view'] ?></td>
        </tr>
        <?php endforeach; ?>
    </table>
    
    <!-- 페이지 네비게이션 -->
    <div>
    <?php
    $startPage = (int) (floor(($page - 1) / $pageListSize) * $pageListSize + 1);
    $endPage   = min($startPage + $pageListSize - 1, $totalPage);
    
    if ($startPage > 1) {
        echo "<a href='?page=" . ($startPage - 1) . "'>◀</a> ";
    }
    for ($i = $startPage; $i <= $endPage; $i++) {
        if ($i === $page) {
            echo " <strong>{$i}</strong> ";
        } else {
            echo " <a href='?page={$i}'>{$i}</a> ";
        }
    }
    if ($endPage < $totalPage) {
        echo " <a href='?page=" . ($endPage + 1) . "'>▶</a>";
    }
    ?>
    </div>
    
    <a href="write.php">글쓰기</a>
    </body>
    </html>
    

    글쓰기 폼 (write.php)

    <?php declare(strict_types=1); ?>
    <!DOCTYPE html>
    <html lang="ko">
    <head><meta charset="UTF-8"><title>글쓰기</title></head>
    <body>
    <form action="insert.php" method="post">
        <table border="1" width="580">
            <tr><td colspan="2" align="center"><b>글 쓰 기</b></td></tr>
            <tr>
                <td width="80">이름</td>
                <td><input type="text" name="name" maxlength="20" required></td>
            </tr>
            <tr>
                <td>이메일</td>
                <td><input type="text" name="email" maxlength="100"></td>
            </tr>
            <tr>
                <td>비밀번호</td>
                <td>
                    <input type="password" name="pass" maxlength="20" required>
                    <small>(수정·삭제 시 필요)</small>
                </td>
            </tr>
            <tr>
                <td>제목</td>
                <td><input type="text" name="title" size="60" maxlength="100" required></td>
            </tr>
            <tr>
                <td>내용</td>
                <td><textarea name="content" cols="65" rows="15" required></textarea></td>
            </tr>
            <tr>
                <td colspan="2" align="center">
                    <input type="submit" value="저장">
                    <input type="button" value="취소" onclick="history.back()">
                </td>
            </tr>
        </table>
    </form>
    </body>
    </html>
    

    글 저장 (insert.php)

    <?php
    declare(strict_types=1);
    
    require 'db.php';
    
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        header('Location: list.php');
        exit;
    }
    
    $name    = trim($_POST['name']    ?? '');
    $email   = trim($_POST['email']   ?? '');
    $pass    = trim($_POST['pass']    ?? '');
    $title   = trim($_POST['title']   ?? '');
    $content = trim($_POST['content'] ?? '');
    
    if ($name === '' || $pass === '' || $title === '' || $content === '') {
        exit('필수 항목을 모두 입력해주세요.');
    }
    
    $stmt = $pdo->prepare(
        "INSERT INTO board (name, email, pass, title, content, ip)
         VALUES (:name, :email, :pass, :title, :content, :ip)"
    );
    $stmt->execute([
        ':name'    => $name,
        ':email'   => $email,
        ':pass'    => password_hash($pass, PASSWORD_DEFAULT),
        ':title'   => $title,
        ':content' => $content,
        ':ip'      => $_SERVER['REMOTE_ADDR'],
    ]);
    
    header('Location: list.php');
    exit;
    

    글 읽기 (read.php)

    <?php
    declare(strict_types=1);
    
    require 'db.php';
    
    $id   = (int) ($_GET['id']   ?? 0);
    $page = (int) ($_GET['page'] ?? 1);
    
    if ($id === 0) {
        header('Location: list.php');
        exit;
    }
    
    // 조회수 증가
    $pdo->prepare("UPDATE board SET view = view + 1 WHERE id = :id")
        ->execute([':id' => $id]);
    
    $stmt = $pdo->prepare("SELECT * FROM board WHERE id = :id");
    $stmt->execute([':id' => $id]);
    $row = $stmt->fetch();
    
    if (!$row) {
        exit('존재하지 않는 글입니다.');
    }
    
    // 이전/다음 글
    $prev = $pdo->prepare("SELECT id, title FROM board WHERE id > :id ORDER BY id ASC  LIMIT 1");
    $next = $pdo->prepare("SELECT id, title FROM board WHERE id < :id ORDER BY id DESC LIMIT 1");
    $prev->execute([':id' => $id]);
    $next->execute([':id' => $id]);
    $prevRow = $prev->fetch();
    $nextRow = $next->fetch();
    
    function h(string $s): string {
        return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="ko">
    <head><meta charset="UTF-8"><title><?= h($row['title']) ?></title></head>
    <body>
    <table border="1" width="580">
        <tr>
            <th colspan="4"><?= h($row['title']) ?></th>
        </tr>
        <tr>
            <td>글쓴이</td><td><?= h($row['name']) ?></td>
            <td>날짜</td><td><?= $row['wdate'] ?></td>
        </tr>
        <tr>
            <td>이메일</td><td><?= h($row['email']) ?></td>
            <td>조회수</td><td><?= $row['view'] ?></td>
        </tr>
        <tr>
            <td colspan="4"><?= nl2br(h($row['content'])) ?></td>
        </tr>
        <tr>
            <td colspan="4">
                <a href="list.php?page=<?= $page ?>">[목록]</a>
                <a href="write.php">[글쓰기]</a>
                <a href="edit.php?id=<?= $id ?>&page=<?= $page ?>">[수정]</a>
                <a href="delete.php?id=<?= $id ?>&page=<?= $page ?>">[삭제]</a>
                &nbsp;&nbsp;
                <?php if ($prevRow): ?>
                    <a href="read.php?id=<?= $prevRow['id'] ?>&page=<?= $page ?>">[이전]</a>
                <?php else: ?>[이전]<?php endif; ?>
                <?php if ($nextRow): ?>
                    <a href="read.php?id=<?= $nextRow['id'] ?>&page=<?= $page ?>">[다음]</a>
                <?php else: ?>[다음]<?php endif; ?>
            </td>
        </tr>
    </table>
    </body>
    </html>
    

    글 수정 (edit.php / update.php)

    edit.php — 수정 폼

    <?php
    declare(strict_types=1);
    
    require 'db.php';
    
    $id   = (int) ($_GET['id']   ?? 0);
    $page = (int) ($_GET['page'] ?? 1);
    
    $stmt = $pdo->prepare("SELECT * FROM board WHERE id = :id");
    $stmt->execute([':id' => $id]);
    $row = $stmt->fetch();
    
    if (!$row) exit('존재하지 않는 글입니다.');
    
    function h(string $s): string {
        return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="ko">
    <head><meta charset="UTF-8"><title>글 수정</title></head>
    <body>
    <form action="update.php" method="post">
        <input type="hidden" name="id"   value="<?= $id ?>">
        <input type="hidden" name="page" value="<?= $page ?>">
        <table border="1" width="580">
            <tr><td colspan="2" align="center"><b>글 수 정</b></td></tr>
            <tr>
                <td width="80">이름</td>
                <td><input type="text" name="name" value="<?= h($row['name']) ?>" required></td>
            </tr>
            <tr>
                <td>이메일</td>
                <td><input type="text" name="email" value="<?= h($row['email']) ?>"></td>
            </tr>
            <tr>
                <td>비밀번호</td>
                <td><input type="password" name="pass" required> <small>(확인용)</small></td>
            </tr>
            <tr>
                <td>제목</td>
                <td><input type="text" name="title" size="60" value="<?= h($row['title']) ?>" required></td>
            </tr>
            <tr>
                <td>내용</td>
                <td><textarea name="content" cols="65" rows="15" required><?= h($row['content']) ?></textarea></td>
            </tr>
            <tr>
                <td colspan="2" align="center">
                    <input type="submit" value="저장">
                    <input type="button" value="취소" onclick="history.back()">
                </td>
            </tr>
        </table>
    </form>
    </body>
    </html>
    

    update.php — 수정 처리

    <?php
    declare(strict_types=1);
    
    require 'db.php';
    
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        header('Location: list.php');
        exit;
    }
    
    $id      = (int) ($_POST['id']      ?? 0);
    $page    = (int) ($_POST['page']    ?? 1);
    $name    = trim($_POST['name']    ?? '');
    $email   = trim($_POST['email']   ?? '');
    $pass    = trim($_POST['pass']    ?? '');
    $title   = trim($_POST['title']   ?? '');
    $content = trim($_POST['content'] ?? '');
    
    $stmt = $pdo->prepare("SELECT pass FROM board WHERE id = :id");
    $stmt->execute([':id' => $id]);
    $row = $stmt->fetch();
    
    if (!$row || !password_verify($pass, $row['pass'])) {
        exit('비밀번호가 틀렸습니다.');
    }
    
    $pdo->prepare(
        "UPDATE board SET name=:name, email=:email, title=:title, content=:content WHERE id=:id"
    )->execute([
        ':name'    => $name,
        ':email'   => $email,
        ':title'   => $title,
        ':content' => $content,
        ':id'      => $id,
    ]);
    
    header("Location: read.php?id={$id}&page={$page}");
    exit;
    

    글 삭제 (delete.php)

    <?php
    declare(strict_types=1);
    
    require 'db.php';
    
    $id   = (int) ($_GET['id']   ?? 0);
    $page = (int) ($_GET['page'] ?? 1);
    
    if ($id === 0) {
        header('Location: list.php');
        exit;
    }
    
    // 비밀번호 확인 폼
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    ?>
    <!DOCTYPE html>
    <html lang="ko">
    <head><meta charset="UTF-8"><title>삭제 확인</title></head>
    <body>
    <form method="post" action="delete.php?id=<?= $id ?>&page=<?= $page ?>">
        비밀번호: <input type="password" name="pass" required>
        <input type="submit" value="확인">
        <input type="button" value="취소" onclick="history.back()">
    </form>
    </body>
    </html>
    <?php
        exit;
    }
    
    // 비밀번호 검증 후 삭제
    $stmt = $pdo->prepare("SELECT pass FROM board WHERE id = :id");
    $stmt->execute([':id' => $id]);
    $row = $stmt->fetch();
    
    if (!$row || !password_verify($_POST['pass'], $row['pass'])) {
        exit('비밀번호가 틀렸습니다.');
    }
    
    $pdo->prepare("DELETE FROM board WHERE id = :id")->execute([':id' => $id]);
    
    header("Location: list.php?page={$page}");
    exit;
    

    파일 구조 정리

    board/
    ├── db.php        — DB 연결
    ├── list.php      — 글 목록
    ├── write.php     — 글쓰기 폼
    ├── insert.php    — 글 저장
    ├── read.php      — 글 읽기
    ├── edit.php      — 수정 폼
    ├── update.php    — 수정 처리
    └── delete.php    — 삭제
    

    방명록을 만들어봤다면 게시판은 사실 그리 어렵지 않습니다.
    제목과 조회수, 이전/다음 글 네비게이션이 추가됐을 뿐입니다.
    CRUD 흐름이 손에 익으면, 어떤 데이터를 다루든 같은 방식으로 접근할 수 있습니다.

    Last updated on 2026-4-19 by Myeongjin Cho
    ← 방명록 만들기계층형 게시판 만들기 →
    • 게시판이란?
    • 데이터베이스 설계
    • DB 연결 (db.php)
    • 글 목록 (list.php)
    • 글쓰기 폼 (write.php)
    • 글 저장 (insert.php)
    • 글 읽기 (read.php)
    • 글 수정 (edit.php / update.php)
    • 글 삭제 (delete.php)
    • 파일 구조 정리
    커뮤니티
    PHP 공식 웹사이트한국 PHP 개발자 커뮤니티
    유용한 정보
    책 소스 코드
    Copyright © 2026 EZPHP.NET