MVC Design Pattern 뼈대 만들기

이번 포스팅에서는 MVC Design Pattern을 직접 만들어 보도록 하겠습니다.

MVC Design Pattern 이란?

MVC Design Pattern

1. 폴더 및 파일 구조

2. 소스코드

/.htaccess

RewriteEngine On
RewriteRule ^([^.]*)/?$ index.php?param=$1 [L]
  • RewriteRule에 의해 주소에 들어오는 문자열을 $_GET['param']에 저장합니다.
  • 예를 들어
    [ http://127.0.0.1/test ] 는 RewriteRule에 의해
    [ http://127.0.0.1/index.php?param=test ] 이렇게 변환됩니다.

/index.php

<?php
    define('_ROOT',dirname(__FILE__)."/");
    define('_APP',_ROOT."application/");
    define('_PUBLIC',_ROOT."public/");
    define('_MODEL',_APP."model/");
    define('_CONFIG',_APP."config/");
    define('_CONTROLLER',_APP."controller/");
    define('_VIEW',_APP."view/");
    $url = str_replace("index.php","","http://{$_SERVER['HTTP_HOST']}{$_SERVER['PHP_SELF']}");
    define('_URL',$url);
    define('_IMG',_URL.'public/img/');
    define('_CSS',_URL.'public/common/css/');
    define('_JS',_URL.'public/common/js/');

    require_once(_CONFIG."lib.php");
    new Application();

상수 정의는 굳이 설명하지 않아도 이해하리라 생각합니다.

index.php에서 눈여겨 봐야할 부분은
new Application();
입니다.
정의된 Application 객체가 없는 상태에서 객체를 생성하였습니다. 이 때 __autoload() 라는 함수를 실행하게 되는 데, 이것에 대한 처리는 lib.php에서 하게 됩니다.

/application/config/lib.php

<?php
    //alert
    function alert($str){
        echo "<script>alert('{$str}');</script>";
    }

    //move
    function move($str = false){
        echo "<script>";
            echo $str ? "document.location.replace('{$str}');" : "history.back();";
        echo "</script>";
        exit;
    }

    //access
    function access($bool,$msg,$url = false){
        if(!$bool){
            alert($msg);
            move($url);
        }
    }

    //autoload
    function __autoload($className){
        $className = strtolower($className);
        $className2 = preg_replace('/(model|application)(.*)/',"$1",$className);
        switch($className2){
            case 'application' : $dir = _APP; break;
            case 'model' : $dir = _MODEL; break;
            default : $dir = _CONTROLLER; break;
        }
        require_once("{$dir}{$className}.php");
    }
  • alert() : 경고창을 띄웁니다.
  • move() : 페이지 이동을 합니다.
  • access() : 조건에 대하여 경고창/페이지이동 등의 명령을 실행합니다.
  • __autoload() : 정의 되지 않은 객체를 만들었을 때 실행하는 함수입니다.
    • 객체 이름에 model과 application이 포함되어 있으면 해당 폴더로, 나머지는 무조건 controller 폴더에 있는 객체명과 일치하는 php 파일을 불러옵니다.
      • new Application(); ==> /application/application.php를 require_once
      • new Model(); ==> /application/model/model.php를 require_once
      • new Model_main(); ==> /application/model/model_main.php를 require_once
      • new Model_board; ==> /application/model/model_board.php를 require_once
      • new Model_member; ==> /application/model/model_member.php를 require_once
      • new Controller(); ==> /application/controller/controller.php를 require_once
      • new Main(); ==> /application/controller/main.php를 require_once
      • new Board(); ==> /application/controller/board.php를 require_once
      • new Member(); ==> /application/controller/member.php를 require_once

index.php에서 new Application(); 에 대한 결과로 /application/application.php를 require_once 합니다.

/application/application.php

<?php
    Class Application {
        //변수
        var $param;

        //생성자
        function __construct(){
            $this->getParam();
            new $this->param->page_type($this->param);
        }

        //get param
        function getParam(){
            if(isset($_GET['param'])){
                $get = explode("/",$_GET['param']);
            }
            $param = [];
            $param['page_type'] = isset($get[0]) && $get[0] != '' ? $get[0] : 'main';
            $param['action'] = isset($get[1]) && $get[1] != '' ? $get[1] : NULL;
            $param['idx'] = isset($get[2]) && $get[2] != '' ? $get[2] : NULL;
            $param['page_num'] = isset($get[2]) && $get[2] != '' ? $get[2] : 1;
            $param['include_file'] = isset($param['action']) ? $param['action'] : $param['page_type'];
            $param['get_page'] = _URL."{$param['page_type']}";
            $this->param = (object)$param;
        }
    }

$this->getParam() : 주소에 할당되는 문자열 ($_GET['param']) 을 쪼개어 객체로 할당

  • 메인페이지 :  / 
    • $this->param->page_type : main
    • $this->param->action : NULL
    • $this->param->idx : NULL
    • $this->param->page_num : 1
    • $this->param->include_file : main
    • $this->param->get_page : /main
  • 게시판 : /board
    • $this->param->page_type : board
    • $this->param->action : NULL
    • $this->param->idx : NULL
    • $this->param->page_num : 1
    • $this->param->include_file : board
    • $this->param->get_page : /board
  • 1번 게시글 보기 : /board/view/1
    • $this->param->page_type : board
    • $this->param->action : view
    • $this->param->idx : 1
    • $this->param->page_num : 1
    • $this->param->include_file : view
    • $this->param->get_page : /board
  • 게시물 작성 : /board/write
    • $this->param->page_type : board
    • $this->param->action : write
    • $this->param->idx : NULL
    • $this->param->page_num : 1
    • $this->param->include_file : write
    • $this->param->get_page : /board
  • 1번 게시물 수정 : /board/write/1
    • $this->param->page_type : board
    • $this->param->action : write
    • $this->param->idx : 1
    • $this->param->page_num : 1
    • $this->param->include_file : write
    • $this->param->get_page : /board
  • 1번 게시물 삭제 : /board/delete/1
    • $this->param->page_type : board
    • $this->param->action : delete
    • $this->param->idx : 1
    • $this->param->page_num : 1
    • $this->param->include_file : write
    • $this->param->get_page : /board

new $this->param->page_type($this->param)  : page_type에 할당된 문자열과 일치하는 객체를 생성하고, 생성자에 주소정보를 넘겨준다.

  • 메인페이지 : [/ ] 
    • $this->param->page_type : main
    • 생성 객체 : new Main();
  • 게시판 : [ /board ] 
    • $this->param->page_type : board
    • 생성 객체 : new Board();
  • 생성자에 주소정보($this->param)을 넘겨줌

/application/controller/main.php

<?php
    Class Main extends Controller {
        function baisc(){

        }
    }

사실상 아무 내용이 없다.
Controller를 Extends 할 때 Controller를 호출한다. 즉, __autoload() 가 실행된다.
따라서 /application/controller/controller.php 가 require_once되고, Controller 객체가 생성된다.

/application/controller/controller.php

<?php
    Class Controller {
        //변수
        var $param;
        var $db;
        var $title;
        var $setAjax;

        //생성자
        function __construct($param){
            header("Content-type:text/html;charset=utf8");
            $this->param = $param;
            $modelName = "Model_{$this->param->page_type}";
            $this->db = new $modelName($this->param);
            $this->setAjax = false;
            $this->index();
        }

        //index
        function index(){
            $method = isset($this->param->action) ? $this->param->action : 'basic';
            if(method_exists($this,$method)) $this->$method();
            $this->getTitle();
            $this->header();
            $this->content();
            $this->footer();
        }

        //header
        function header(){
            $this->setAjax || require_once(_VIEW."header.php");
        }

        //footer
        function footer(){
            $this->setAjax || require_once(_VIEW."footer.php");
        }

        //content
        function content(){
            $this_arr = (array)$this;
            extract($this_arr);
            $dir = _VIEW."{$this->param->page_type}/{$this->param->include_file}.php";
            if(file_exists($dir)) require_once($dir);
        }

        //getTitle
        function getTitle(){
            $this->title = 'MVC Model';
        }
    }
  • __construct() : 생성자
    • 문자셋 지정 (utf-8)
    • 주소 정보 객체 반환
    • Model 객체 생성
    • ajax를 일단 false로 초기화
    • $this->index(); 메소드 실행
    • Model 객체 뒤에는 page_type이 붙는다.
    • 메인페이지 [ http://127.0.0.1 ] 에서
      $this->param->page_type 에는 main 이 할당되어 있으므로
      Model_main 객체가 만들어진다.
  • header() : 페이지 상단 호출
  • footer() : 페이지 하단 호출
  • content() : 컨텐츠 호출
  • getTitle() : 타이틀 지정.
    Controller 객체를 상속하는 경우, 개별 getTitle()을 만들어 title을 따로 지정할 수 있음.
  • $this->$method() : $this->param->action에 값이 있을 경우 action값으로, 아니면 $this->basic()이 실행된다.
    따라서 각 Controller 마다 $method에 해당하는 method를 실행하여 데이터를 렌더링 하거나 기초 세팅을 한다.

     

    • 메인페이지 [ / ]
      • $this->param->page_type : main
      • $this->param->action : NULL
      • action에 NULL이 할당되어 있으므로 new Main() 객체의 basic() 메소드가 실행된다.
    • 게시판 [ /board ]
      • $this->param->page_type : board
      • $this->param->action : NULL
      • action에 NULL이 할당되어 있으므로 new Board() 객체의 basic() 메소드가 실행된다.
    • 1번 게시글 보기 [ /board/view/1 ]
      • $this->param->page_type : board
      • $this->param->action : view
      • new Board() 객체의 view() 메소드가 실행된다.
    • 게시글 작성 [ /board/write ]
      • $this->param->page_type : board
      • $this->param->action : write
      • new Board() 객체의 write() 메소드가 실행된다.
    • 1번 게시글 수정 [ /board/write/1 ]
      • $this->param->page_type : board
      • $this->param->action : write
      • new Board() 객체의 write() 메소드가 실행된다.
    • 1번 게시글 삭제 [ /board/delete/1 ]
      • $this->param->page_type : board
      • $this->param->action : delete
      • new Board() 객체의 delete() 메소드가 실행된다.
  • index() : View로 렌더링

__construct(); 에서 메인페이지의 경우 Model_main  객체를 생성한다.
즉, /application/model/model_main.php 파일이 호출된다.

/application/model/mode_main.php

<?php
    Class Model_main extends Model {
        
    }

Model 객체를 참조한다. 따라서 Model 객체가 호출된다.
즉, /application/model/model.php 파일이 호출된다.

/application/model/mode.php

<?php
    Class Model {
        //변수
        var $db;
        var $column;
        var $table;
        var $param;
        var $action;
        var $sql;

        //생성자
        function __construct($param){
            $this->column = NULL;
            $this->param = $param;
            $this->db = new PDO("mysql:host=localhost;dbname=0519;charset=utf8","root","MySQL");
            $this->db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
            if(isset($_POST['action'])){
                $this->action = $_POST['action'];
                $this->action();
            }
        }

        //query
        function query($sql = false){
            $sql && $this->sql = $sql;
            $res = $this->db->prepare($this->sql);
            if($res->execute($this->column)){
                return $res;
            } else {
                echo "<pre>";
                echo $this->sql;
                print_r($this->column);
                print_r($this->db->errorInfo());
                echo "</pre>";
            }
        }

        //fetch
        function fetch($sql = false){
            $sql && $this->sql = $sql;
            return $this->query($this->sql)->fetch();
        }

        //fetchAll
        function fetchAll($sql = false){
            $sql && $this->sql = $sql;
            return $this->query($this->sql)->fetchAll();
        }

        //cnt
        function cnt($sql = false){
            $sql && $this->sql = $sql;
            return $this->query($this->sql)->rowCount();
        }

        //column
        function getColumn($arr,$cancel){
            $column = '';
            $cancel = explode("/",$cancel);
            foreach ($arr as $key => $value) {
                if(!in_array($key,$cancel)){
                    $column .= ", {$key} = :{$key}\n";
                    $this->column[$key] = $value;
                }
            }
            return $column = substr($column,2);
        }

        //queryResult
        function combine($column){
            switch($this->action){
                case 'insert' : $sql = " INSERT INTO {$this->table} set \n"; break;
                case 'update' : $sql = " UPDATE {$this->table} set \n"; break;
                case 'delete' : $sql = " DELETE FROM {$this->table} \n"; break;
            }
            return $sql .= $column;
        }
    }
  • __construct() : 생성자
    • DB에 연결한다.
    • column, param 등의 객체 변수를 초기화한다.
    • $_POST['action']이 존재할 경우 $this->action() 메소드를 실행한다.
    • $this->action은 상속될 Model 객체가 가지고 있다.
    • $this->action 에서 insert / delete / update 등의 작업을 처리한다.

  • query() : 쿼리문을 실행한다.
    • 객체 변수 column에 값이 있을 경우, 렌더링 한다.
    • 객체 변수 column에 값이 없을 경우, 그냥 실행한다.

    • 오류 발생 시 오류를 출력한다.
  • fetch() : SELECT 된 하나의 데이터를 반fetchAll() : SELECT 된 모든 데이터를 반환
  • cnt() : SELECT 된 행의 갯수를 반환
  • getColumn() : 배열을 query문에 사용될 column 문자열로 반환
  • combine() : 쿼리문과 column을 결합

/application/view/header.php

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><?php echo $this->title?></title>
<link rel="stylesheet" href="<?php echo _CSS?>common.css">
<script src="<?php echo _JS?>jquery-1.8.3.min.js"></script>
<script src="<?php echo _JS?>common.js"></script>
</head>
<body>
<header id="header">
    <div>
        <div id="logo">
            <h3><a href="<?php echo _URL?>">MVC MODEL HOMPAGE</a></h3>
        </div>
    </div>
    <nav id="gnb">
        <ul>
            <li><a href="<?php echo _URL?>board">게시판</a></li>
            <li><a href="<?php echo _URL?>schedule">일정관리</a></li>
            <li><a href="<?php echo _URL?>chat">채팅</a></li>
        </ul>
    </nav>
</header>

/application/view/main/main.php

<section id="main-content">
    <h1>Main Page</h1>
    <section class="board">
        <h2>게시판</h2>
        <div></div>
    </section>
    <section class="schedule">
        <h2>일정관리</h2>
        <div></div>
    </section>
    <section class="chat">
        <h2>채팅</h2>
        <div></div>
    </section>
</section>
<footer id="footer">
    copyright (c) 2017 Junil-hwang all right reserved.
</footer>
</body>
</html>

/public/common/css/common.css

@charset "UTF-8";

/* 태그 */
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%}

/* layer popup */
.auto-center,#header>div,#main-content{width:1200px;margin:0 auto}
#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:"공사중"}

/* 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}

3. 정리

마지막으로 호출되는 흐름을 살펴보도록 하겠습니다.

  • 메인페이지 [ / ]
    1. /index.php
    2. /application/application.php
    3. /application/controller/main.php
    4. /application/controller/controller.php
    5. /application/model/model_main.php
    6. /application/model/model.php
    7. /application/view/header.php
    8. /application/view/main/main.php
    9. /application/view/footer.php
  • 게시판 [ /board ]
    1. /index.php
    2. /application/application.php
    3. /application/controller/board.php
    4. /application/controller/controller.php
    5. /application/model/model_board.php
    6. /application/model/model.php
    7. /application/view/header.php
    8. /application/view/board/board.php
    9. /application/view/footer.php
  • 게시글 작성 [ /board ]
    1. /index.php
    2. /application/application.php
    3. /application/controller/board.php
    4. /application/controller/controller.php
    5. /application/model/model_board.php
    6. /application/model/model.php
    7. /application/view/header.php
    8. /application/view/board/write.php
    9. /application/view/footer.php
  • 1번 게시글 수정 [ /board/1 ]
    1. /index.php
    2. /application/application.php
    3. /application/controller/board.php
    4. /application/controller/controller.php
    5. /application/model/model_board.php
    6. /application/model/model.php
    7. /application/view/header.php
    8. /application/view/board/write.php
    9. /application/view/footer.php
  • 1번 게시글 보기 [ /board/1 ]
    1. /index.php
    2. /application/application.php
    3. /application/controller/board.php
    4. /application/controller/controller.php
    5. /application/model/model_board.php
    6. /application/model/model.php
    7. /application/view/header.php
    8. /application/view/board/view.php
    9. /application/view/footer.php
  • 1번 게시글 삭제 [ /board/1 ]
    1. /index.php
    2. /application/application.php
    3. /application/controller/board.php
    4. /application/controller/controller.php
    5. /application/model/model_board.php
    6. /application/model/model.php
    7. /application/view/header.php
    8. /application/view/board/delete.php
    9. /application/view/footer.php

다음 포스팅에서는 이번 포스팅에서 만든 MVC Design Pattern을 이용하여 게시판을 만들어 보도록 하겠습니다.