PHP MVC 게시판 만들기

mvc

이번 장에서는 이전 장에 만든 MVC Design Pattern에 게시판 을 만들어 보도록 하겠습니다.

이전장 : MVC Design Pattern 뼈대 만들기

1. 게시판 기본 흐름도 및 주소 정보

  • 게시판 기본 URL : http://127.0.0.1/board
  • 해당 페이지로 접근 시 호출 순서
    1. /index.php
    2. /application/config/lib.php
    3. /application/application.php
    4. /application/controller/board.php
    5. /application/controller/controller.php
    6. /application/model/model_board.php
    7. /application/model/model.php
    8. /application/view/header.php
    9. /application/view/board/board.php
    10. /application/view/footer.php
  • 주소 변수 할당
    1. $this->param->page_type : board
    2. $this->param->action : NULL
    3. $this->param->idx : NULL
    4. $this->param->page_num : 1
    5. $this->param->include_file : board
    6. $this->param->get_page : /board

즉, 주소에 board가 붙으면
Application에서 Board Controller를 호출합니다.
그 다음 Board Controller는 Model을 호출하고, 해당 페이지에 대한 특정 method를 실행 하여 해당 페이지에 대한 데이터를 받아오며 View 에 렌더링을 합니다.
기본은 Basic() method 이며, 주소가
http://127.0.0.1/board/write
이렇게 될 경우, 마찬가지로 Board Controller를 호출하고, 해당 페이지의 method는 write()가 됩니다.

2. 게시판 database table 구성

해당 쿼리문을 실행시켜 봅시다.

Create table board (
    `idx` int not null auto_increment,
    `name` varchar(255),
    `pw` varchar(255),
    `subject` varchar(255),
    `content` text,
    `date` datetime,
    primary key (`idx`)
)
  • idx : 기본키, 게시물 번호
  • name : 작성자
  • pw : 게시물 비밀번호
  • subject : 제목
  • content : 내용
  • date : 작성일

3. 소스코드

/application/controller/board.php

<?php
    Class Board extends Controller {
        var $list;
        var $data;
        var $listNum;
        var $subject;
        var $name;
        var $content;

        //basic : list
        function basic(){
            $this->list = $this->db->getList();
            $this->listNum = $this->db->getListNum();
        }

        //view
        function view(){
            $this->data = $this->db->getView();
        }

        //write
        function write(){
            if(isset($this->param->idx)){
                $this->data = $this->db->getView();
            }
            $this->action = isset($this->param->idx) ? 'update' : 'insert';
            $this->subject = isset($this->data->subject) ? $this->data->subject : NULL;
            $this->name = isset($this->data->name) ? $this->data->name : NULL;
            $this->content = isset($this->data->content) ? $this->data->content : NULL;
        }

        //delete
        function delete(){
            
        }
    }

/application/model/model_board.php

<?php
    Class Model_board extends Model {

        //getList
        function getList(){
            $this->sql = "SELECT * FROM board order by `date` desc";
            return $this->fetchAll();
        }

        //getListNum
        function getListNum(){
            return $this->cnt();
        }

        //getView
        function getView(){
            $this->sql = "SELECT * FROM board where idx='{$this->param->idx}'";
            return $this->fetch();
        }

        //board create
        function create(){
            $this->sql = "
                CREATE TABLE `board` (
                    `idx` int not null AUTO_INCREMENT,
                    `subject` varchar(255),
                    `name` varchar(255),
                    `pw` varchar(255),
                    `content` text,
                    `date` datetime,
                    PRIMARY KEY (`idx`)
                );
            ";
            $this->query();
        }

        //action
        function action(){
            header("Content-type:text/html;charset=utf8");
            $this->table = 'board';
            $cancel = $add_sql = "";
            $msg = "완료되었습니다.";
            $url = $this->param->get_page;
            isset($_POST['pw']) && md5($_POST['pw']);
            switch($_POST['action']){
                case 'insert' :
                    $add_sql .= ", date=now()";
                break;
                case 'update' :
                    $url .= "/view/{$this->param->idx}";
                case 'delete' :
                    access($this->cnt("SELECT * FROM board where pw='{$_POST['pw']}' and idx='{$this->param->idx}'"),"비밀번호가 일치하지 않습니다.");
                    $add_sql .= " where idx='{$this->param->idx}';";
                    unset($_POST['pw']);
                break;
            }
            $cancel .= "action/table/idx/";
            $column = $this->getColumn($_POST,$cancel).$add_sql;
            $this->sql = $this->combine($column);
            access(!$this->query(),$msg,$url);
        }
    }

/application/view/board/board.php

<div class="board_list auto-center">
   <h3>총 게시물 수 : <?php echo $listNum?></h3>
   <table width="100%">
      <colgroup>
        <col width="10%">
        <col width="60%">
        <col width="15%">
        <col width="15%">
      </colgroup>
      <thead>
        <tr>
           <th>번호</th>
           <th>제목</th>
           <th>작성자</th>
           <th>작성일</th>
        </tr>
      </thead>
      <tbody>
        <?php foreach ($list as $key => $data): ?>
        <tr>
           <td> </td>
           <td class="al_l"><a href="<?php echo ">param->get_page}/view/{$data->idx}"?>"><?php echo $data->subject ?></a></td>
           <td> </td>
           <td> </td>
        </tr>
        <?php endforeach ?>
      </tbody>
   </table>
   <div class="btn_group"><a class="btn-default" href="<?php echo $this->param->get_page?>/write">작성</a></div>
</div>
게시판 : 수정

/application/view/board/view.php

<div class="board_view auto-center">
    <h3>글보기</h3>
    <div class="table">
        <div class="tr">
            <div class="lbl">작성자</div>
            <div class="desc"><?php echo $data->name?></div>
        </div>
        <div class="tr">
            <div class="lbl">제목</div>
            <div class="desc"><?php echo $data->subject?></div>
        </div>
        <div class="tr">
            <div class="lbl">내용</div>
            <div class="desc content"><?php echo nl2br($data->content)?></div>
        </div>
    </div>
    <div class="btn_group">
        <a class="btn-default" href="<?php echo $this->param->get_page?>">목록</a>
        <a class="btn-submit" href="<?php echo $this->param->get_page?>/write/<?php echo $this->param->idx?>">수정</a>
        <a class="btn-submit" href="<?php echo $this->param->get_page?>/delete/<?php echo $this->param->idx?>">삭제</a>
    </div>
</div>
게시판 : 보기

/application/view/board/write.php

<div class="board_write auto-center">
    <form action="" method="post">
    <fieldset><legend>글작성</legend>
        <input type="hidden" name="action" value="<?php echo $action?>">
        <h3>글작성</h3>
        <div class="table">
            <div class="tr">
                <div class="lbl"><label for="board_name">작성자</label></div>
                <div class="desc"><input type="text" id="board_name" name="name" size="20" title="작성자" required autofocus value="<?php echo $name?>"></div>
            </div>
            <div class="tr">
                <div class="lbl"><label for="board_pw">비밀번호</label></div>
                <div class="desc"><input type="password" id="board_pw" name="pw" size="20" title="비밀번호" required></div>
            </div>
            <div class="tr">
                <div class="lbl"><label for="board_subject">제목</label></div>
                <div class="desc"><input type="text" id="board_subject" name="subject" size="80" title="제목" required value="<?php echo $subject?>"></div>
            </div>
            <div class="tr">
                <div class="lbl"><label for="board_content">내용</label></div>
                <div class="desc"><textarea id="board_content" name="content" cols="80" rows="10" title="내용" required><?php echo $content?></textarea></div>
            </div>
        </div>
        <div class="btn_group">
            <a class="btn-default" href="<?php echo $this->param->get_page?>">취소</a>
            <button class="btn-submit" type="submit">완료</button>
        </div>
    </fieldset>
    </form>
</div>
게시판 : 작성

/application/view/board/delete.php

<div class="board_write auto-center">
    <form action="" method="post">
    <fieldset><legend>글삭제</legend>
        <input type="hidden" name="action" value="delete">
        <h3>글삭제</h3>
        <div class="table">
            <div class="tr">
                <div class="lbl"><label for="board_pw">비밀번호</label></div>
                <div class="desc"><input type="password" id="board_pw" name="pw" size="20" title="비밀번호" required autofocus></div>
            </div>
        </div>
        <div class="btn_group">
            <a class="btn-default" href="#" onclick="history.back(); return false;">취소</a>
            <button class="btn-submit" type="submit">완료</button>
        </div>
    </fieldset>
    </form>
</div>
게시판 : 삭제

/public/common/css/common.css

@charset "UTF-8";
/* config */
@media (min-width: 1200px) { .auto-center, #header > div, #main-content { width: 1200px; margin: 0 auto; } }

/* 초기화 */
html, body { margin: 0; padding: 0; }

html { font-size: 25px; }

body { font-size: 0.52rem; font-family: 'Nanum Gothic','Malgun Gothic','Myriad','arial','宋體','simsun','Dotum'; }

h1, h2, h3, h4, h5, h6, form, fieldset, img { margin: 0; padding: 0; border: 0; font-family: inherit; }

article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; margin: 0; padding: 0; }

ul, li, dl, dt, dd { margin: 0; padding: 0; list-style: none; }

legend { position: absolute; margin: 0; padding: 0; font-size: 0; line-height: 0; text-indent: -9999em; overflow: hidden; }

label, input, button, select, img { vertical-align: middle; font-size: 13px; transition: 0.5s; font-family: inherit; }

input[type='submit'], input[type='button'], input[type='reset'], button { cursor: pointer; padding: 0 0.8rem; letter-spacing: -1px; font-weight: bold; height: 1.28rem; box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3); font-family: inherit; }

input[type="text"], input[type="password"], input[type="number"], input[type="tel"] { height: 1.2rem; line-height: 1.2rem; font-size: 0.6rem; padding: 0 5px; border: 1px solid #bebebe; transition: 0.3s; font-family: inherit; }

input[type="text"]:focus, input[type="password"]:focus, input[type="number"]:focus, input[type="tel"]:focus { border-color: #fff; box-shadow: 0px 0px 3px #000; background: #eff; }

select { line-height: 1.2rem; height: 1.2rem; font-size: 0.6rem; }

textarea { font-size: 0.6rem; padding: 5px; vertical-align: middle; font-family: inherit; }

p { margin: 0; padding: 0; }

a { color: inherit; text-decoration: none; transition: .3s; }

a:hover { text-decoration: underline; }

a[class*="btn"], button[class*="btn"] { display: inline-block; text-align: center; box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3); text-decoration: none; }

img { max-width: 100%; max-height: 100%; }

@media (max-width: 479px) { html { font-size: 15px; } }
@media (min-width: 480px) and (max-width: 519px) { html { font-size: 15px; } }
@media (min-width: 520px) and (max-width: 559px) { html { font-size: 16px; } }
@media (min-width: 560px) and (max-width: 599px) { html { font-size: 17px; } }
@media (min-width: 600px) and (max-width: 639px) { html { font-size: 18px; } }
@media (min-width: 640px) and (max-width: 679px) { html { font-size: 19px; } }
@media (min-width: 680px) and (max-width: 719px) { html { font-size: 20px; } }
@media (min-width: 720px) and (max-width: 759px) { html { font-size: 21px; } }
@media (min-width: 760px) and (max-width: 799px) { html { font-size: 22px; } }
@media (min-width: 800px) and (max-width: 839px) { html { font-size: 23px; } }
@media (min-width: 840px) and (max-width: 879px) { html { font-size: 24px; } }
@media (min-width: 880px) { html { font-size: 25px; } }
/* layer popup */
#layer { width: 100%; height: 100%; position: fixed; left: 0; top: 0; text-align: center; z-index: 200; display: none; overflow: auto; }
#layer > .bg { position: fixed; width: 100%; height: 100%; left: 0; top: 0; background: #000; opacity: .2; cursor: pointer; }
#layer > .box { background: #fff; box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3); z-index: 100; position: relative; padding: 1.2rem; margin: 1.2rem 0; text-align: left; min-width: 600px; }
#layer > .box > a.close { position: absolute; right: 0; top: 0; background: #666; height: 1.2rem; display: inline-block; width: 1.2rem; text-align: center; color: #fff; font-weight: bold; font-size: 1rem; text-decoration: none; transition: .3s; }
#layer > .box > a.close:hover { background: #e80000; }
#layer > .box > .title { font-size: 1.2rem; color: #bf303f; padding-bottom: 0.4rem; letter-spacing: -1px; border-bottom: 1px dotted #ddd; margin-bottom: 0.6rem; text-align: center; color: #e80000; text-align: left; }
#layer div.table { width: 100%; border-top: 2px solid #666; }
#layer div.table .tr { border-bottom: 1px solid #ddd; }
#layer div.table .tr > div { display: inline-block; vertical-align: top; box-sizing: border-box; padding: 0.2rem 0.8rem; line-height: 1.2rem; }
#layer div.table .tr > div.lbl { width: 20%; font-weight: bold; }
#layer div.table .tr > div.desc { width: 80%; }
#layer div.table .tr > div.desc input, #layer div.table .tr > div.desc textarea { max-width: 98%; }
#layer .btn_group { text-align: center; margin-top: 0.8rem; display: block; }
#layer .verify { width: 600px; }
#layer .verify > .img_wrap { margin-bottom: 0.8rem; }
#layer .text { line-height: 200%; }
@media (max-width: 1199px) { #layer > .box { min-width: 50vw; }
  #layer div.table > .tr > div.lbl { width: 30%; }
  #layer div.table > .tr > div.desc { width: 70%; }
  #layer .verify { width: 90%; } }

#logo { text-align: center; line-height: 50px; }
#logo a { color: #06f; font-size: 24px; padding: 15px 0; text-decoration: none; }

#gnb { border: solid #ddd; border-width: 1px 0; text-align: center; line-height: 50px; }
#gnb > ul { display: inline-block; vertical-align: bottom; }
#gnb > ul:after { content: ""; display: block; clear: both; }
#gnb > ul > li { float: left; }
#gnb > ul > li > a { display: block; font-size: 13px; padding: 0 50px; text-decoration: none; transition: .3s; font-weight: bold; color: #444; }
#gnb > ul > li > a:hover { background: #09f; color: #fff; }

#footer { border-top: 1px solid #eee; text-align: center; text-transform: uppercase; color: #666; line-height: 80px; }

#main-content { border: solid #ddd; border-width: 0 1px; padding-top: 30px; }
#main-content > h1 { font-size: 30px; background: #f5f5f5; margin: 30px 0 30px -60px; width: 1320px; box-sizing: border-box; padding: 30px 60px; }
#main-content > section { padding: 30px; }
#main-content > section > h2 { font-size: 25px; border-bottom: 1px dotted #ddd; line-height: 200%; }
#main-content > section > div { min-height: 300px; padding: 30px 0; }
#main-content > section > div:before { content: "공사중"; }

.board_list { padding: 50px 0; }
.board_list h3 { padding-bottom: 10px; font-size: 23px; }
.board_list table { border-top: 2px solid #666; border-spacing: 0; border-collapse: collapse; text-align: center; }
.board_list th, .board_list td { border-bottom: 1px solid #ddd; line-height: 30px; padding: 5px 10px; }
.board_list th { background: #f5f5f5; }

.board_write { padding: 50px 0; }
.board_write h3 { padding-bottom: 10px; font-size: 23px; }

.board_view { padding: 50px 0; }
.board_view h3 { padding-bottom: 10px; font-size: 23px; }

/* class */
.al_l { text-align: left; }

.al_c { text-align: center; }

.al_r { text-align: right; }

.fl { float: left; }

.fr { float: right; }

.fn { float: none; }

span.middle { display: inline-block; height: 100%; width: 0; font-size: 0; vertical-align: middle; }
span.middle + * { display: inline-block; vertical-align: middle; }
span.middle.all ~ * { display: inline-block; vertical-align: middle; }

.btn_group { margin-top: 20px; text-align: right; }
.btn_group.center { text-align: center; }
.btn_group.left { text-align: left; }

.btn-default, .btn-submit { display: inline-block; padding: 0 20px; background: #09f; font-size: 13px; color: #fff; font-weight: bold; border: none; vertical-align: middle; height: 30px; line-height: 30px; box-sizing: border-box; text-decoration: none; }

.btn-default { background: #444; }

.btn-submit { background: #09f; }

.table { border-top: 2px solid #666; }
.table > .tr { border-bottom: 1px solid #ddd; line-height: 30px; }
.table > .tr:after { content: ""; display: block; clear: both; }
.table > .tr > div { float: left; box-sizing: border-box; padding: 5px 10px; }
.table > .tr > .lbl { width: 20%; font-weight: bold; }
.table > .tr > .lbl label { display: block; vertical-align: middle; cursor: pointer; }
.table > .tr > .desc { width: 80%; }
.table > .tr > .desc.content { min-height: 300px; }

/*# sourceMappingURL=common.css.map */

이번 장은 이전 장을 이해하지 못하면 아무리 봐도 할 수 없는 내용입니다.
이전 장을 확실히 이해한 후 시도하도록 해봅시다.