코드이그나이터4 마크다운 블로그 MVP 만들기 - 4 - 기능 만들기

koeunyeon·2021년 4월 14일
1

이번 챕터의 글은 https://github.com/koeunyeon/ci4/tree/blog-controller 에 있습니다.

글 컨트롤러 파일 만들기

컨트롤러를 만들겠습니다. 우선 글 요건에 따라 어떤 기능이 필요한지 정의한 엔드포인트만 먼저 생성합니다. 미리 할 일 목록만 만들어두면 추후 문서 등에 의존하지 않고 코드만 보고도 할 일을 유추할 수 있습니다.
app/Controllers/Post.php

<?php
namespace App\Controllers;

use CodeIgniter\Controller;

class Post extends Controller
{
    // 생성
    public function create(){
        
    }

    // 조회
    public function show($post_id){

    }   

    // 수정
    public function edit(){

    }

    // 삭제
    public function delete(){

    }

    // 목록
    public function index($page=1){

    }

}

글 쓰기 기능 만들기

기능을 하나씩 채워나가겠습니다.

글쓰기 엔드포인트 수정하기

Post 컨트롤러의 create() 메소드를 아래와 같이 변경합니다.
app/Controllers/Post.php

// 생성
public function create()
{
    if ($this->request->getMethod() === "get") { // (1)
        return view("/post/create"); // (2)
    }

    // (3)
    $model = new PostsModel();
    $post_id = $model->insert($this->request->getPost()); // (4)
    if ($post_id) { //(5)
        $this->response->redirect("/post/show/$post_id"); // (6)
    } else {
        return view("/post/create", [ // (7)
            'post_data' => $this->request->getPost(),
            'errors' => $model->errors()
        ]);
    }
}

코드를 확인해 보겠습니다.
(1) /post/create 엔드포인트는 두 가지 역할을 합니다. 하나는 생성 화면을 보여주는 것이고, 다른 하나는 생성 화면의 입력을 받아서 데이터를 생성하죠.
그렇다면 어떨 때 생성 화면을 보여줘야 할까요? 바로 HTTP 메소드가 GET일 때입니다. HTTP 메소드가 GET이라는 의미는 조회, 즉 데이터의 변경이 없는 리소스를 요청한다는 뜻입니다.
코드이그나이터4에서 HTTP 메소드 이름은 $this->request->getMethod()로 컨트롤러에서 가져올 수 있습니다. 이 메소드는 무조건 소문자로 된 이름을 반환합니다. 만일 대문자로 가져오고 싶다면 getMethod(true)로 인수를 전달할 수 있습니다.

(2) 생성 화면을 보여주고 엔드포인트 실행을 끝냅니다.

(3) HTTP 메소드가 GET일 경우에는 이미 **(2)**에서 처리했기 때문에 (3) 아래로는 모두 POST 메소드일 때 처리입니다. 브라우저는 GET, POST 두 개의 메소드밖에 처리하지 못하니까요.

(4) 모델을 이용해 글 정보를 저장합니다. 어떤 검증 없이 곧바로 POST 요청을 모델로 전달하는 것을 볼 수 있습니다.

(5) 입력이 성공했는지 결과를 통해 확인합니다.

(6) 데이터 저장 성공이라면 데이터를 확인할 수 있는 상세 페이지로 이동합니다.

(7) 오류가 났다면 오류 메세지와 함께 다시 글 입력 화면을 보여줍니다. 이 때 post_data는 사용자가 입력한 결과를 다시 보여주기 위해, errors는 오류 메세지를 표기하기 위해 사용되었습니다.


(3) 에서 PostsModel()을 사용했으므로 use를 추가하는 것도 잊지 마세요. PHPStorm에서는 자동으로 추가해 주지만, 다른 IDE를 사용한다면 추가되었는지 확인해야 합니다.

use App\Models\PostsModel;

글쓰기 화면 만들기

글쓰기 화면을 만들어 보겠습니다.
app/Views/post/create.php

<h1>글쓰기</h1>
<form method="POST">
    <p>
    <h3>제목</h3>
    <input type="text" name="title" value="<?= $post_data['title'] ?? "" ?>" />
    </p>
    <p>
    <h3>내용</h3>
    <textarea name="content"><?= $post_data['content'] ?? "" ?></textarea>
    </p>
    <p><input type="submit" value="저장"></p>
    <?php
    if (isset($errors)) {
        echo "<ul>";
        foreach ($errors as $val) {
            echo "<li>$val</li>";
        }
        echo "</ul>";
    }
    ?>
</form>

특별한 내용은 없기 때문에 설명은 생략하겠습니다. 이번 챕터의 목적은 기술적인 부분이 아니라 웹 서비스가 어떤 과정을 통해 만들어지는지 보여주는 것이니까요.
다만 제목, 내용의 값(value)이 있다면 채워주는 코드가 있다는 것만 유의하세요. 만약 저장이 실패했을 경우 사용자가 입력한 내용을 다시 보여줘야 하기 때문입니다.

브라우저로 확인해 봅시다.

직접 브라우저에서 확인해 봅시다. http://localhost:8080/post/create 에 접속해서 제목과 본문을 입력해 보세요.

데이터를 저장하고 나면 빈 화면만 나올 거에요. 우리는 아직 글 상세를 볼 수 있는 엔드포인트만 선언해두고 내용을 안 채웠으니까요. 괜찮습니다. 어찌됐든간에 오류는 나지 않으니까요.

데이터베이스를 확인해 봅시다.

데이터가 정말 들어갔는지 확인해 봅시다.

훌륭합니다. 어서 집에 가서 맥주라도 한 잔 하는 게 좋겠어요.

글 보기 기능 만들기

글 보기 기능도 크게 다르지 않습니다. 빠르게 만들어 보겠습니다.

글 보기 엔드포인트 기능 만들기

만들어놓은 show 엔드포인트를 아래와 같이 수정합니다.
app/Controllers/Post.php

// 조회
public function show($post_id)
{
    $model = new PostsModel();
    $post = $model->find($post_id); // (1)
    if (!$post) { // (2)
        return $this->response->redirect("/post");
    }

    return view('/post/show',[ // (3)
        'post' => $post
    ]);
}

코드를 확인해 보겠습니다.
(1) 글을 조회합니다.
(2) 글이 없다면 글 목록으로 보냅니다.
(3) 글이 있다면 글을 보여줍니다.

글 보기 화면 만들기

app/Views/post/show.php

<h3><?= esc($post['title']) ?></h3> <!-- (1) -->
<article>  <!-- (2) -->
    <?= nl2br(esc($post['content'])) ?>  <!-- (3) -->
</article>

코드를 확인해 보겠습니다.
(1) 제목을 보여줍니다.
esc "함수"는 코드이그나이터4에 설정된 전역 함수로 XSS 공격을 방지하기 위해 웹 페이지에 포함할 데이터를 걸러내는 역할을 합니다. 완전히 동일한 것은 아니지만 PHP의 htmlentities 함수를 생각하시면 됩니다.

(2) article 태그는 문서를 독립적으로 구분할 수 있는 "단락" 태그입니다. div 태그가 "영역"을 구분하고, p 태그가 "문단"을 구분한다면, article 태그는 "단락"을 구분합니다.

(3) 글 내용을 보여줍니다.
nl2br 함수는 PHP의 내장 함수로 텍스트 문서의 줄바꿈 기호(\n,\r,\r\n)을 HTML의 줄바꿈 태그인 <br />로 변경해 줍니다.

브라우저로 확인해 봅시다.

직접 브라우저에서 확인해 봅시다. http://localhost:8080/post/show/1에 접속해서 아까 우리가 쓴 글이 잘 나오는지 확인해 봅시다.

글 수정 기능 만들기

글 수정 엔드포인트 기능 만들기

수정 기능은 글 보기 + 글 쓰기 기능입니다. 글을 조회하고 글이 있다면 보여주거나(GET), 글을 수정합니다(POST).
만들어놓은 edit 엔드포인트를 아래와 같이 수정합니다.
app/Controllers/Post.php

// 수정
public function edit($post_id)
{
    $model = new PostsModel();
    $post = $model->find($post_id); // (1)
    if (!$post) {
        return $this->response->redirect("/post"); // (2)
    }

    if ($this->request->getMethod() === "get") { // (3)
        return view("/post/create",[
            'post_data' => $post
        ]);
    }

    $model->update($post_id, $this->request->getPost()); // (4)

    $this->response->redirect("/post/show/$post_id"); // (5)
}

코드를 확인해 보겠습니다.
(1) 글을 조회합니다.

(2) 글이 없다면 글 목록으로 보냅니다. 글이 없다면 글 수정 화면도, 글 수정 기능도 의미가 없기 때문에 글 조회 기능이 먼저 나옵니다.

(3) 글이 있다면 글을 보여줍니다.
뷰 파일을 새로 만들지 않고 생성 화면을 그대로 썼습니다. 이는 생성과 수정이 같은 뷰를 공유할 수 있다는 것을 보여줍니다.
이미 생성 화면에서 제목, 내용의 값(value)이 있다면 채워주는 코드가 있으므로 우리는 뷰에 전달하는 키 이름만 동일하게 post_data로 전달하면 됩니다.

(4) 모델을 통해 글을 갱신합니다.

(5) 상세보기 화면으로 이동합니다.

브라우저로 확인해 봅시다.

직접 브라우저에서 확인해 봅시다. http://localhost:8080/post/edit/1에 접속해서 아까 우리가 쓴 글이 잘 나오는지, 수정은 잘 되는지 확인해 봅시다.


글 삭제 기능 만들기

글 삭제 엔드포인트 만들기

글 삭제 엔드포인트 코드는 아래와 같습니다. 기존의 delete 메소드를 수정합시다.
app/Controllers/Post.php

// 삭제
public function delete()
{
    if ($this->request->getMethod() !== "post"){ // (1)
        return $this->response->redirect("/post");
    }

    $post_id = $this->request->getPost('post_id'); // (2)
    $model = new PostsModel();
    $post = $model->find($post_id);
    if (!$post) {
        return $this->response->redirect("/post");
    }

    $model->delete($post_id); // (3)
    return $this->response->redirect("/post"); // (4)
}

코드를 확인해 보겠습니다.
(1) 글 삭제는 데이터를 변경합니다. 따라서 HTTP POST 메소드만 허용합니다. 만약 POST가 아니라면 글 목록 페이지로 이동합니다.

(2) HTTP POST 만 허용하므로 글 번호 파라미터 post_idPOST값에서 가져옵니다. 만약 post_id 를 조회나 수정처럼 /(post_id URL 형태로 입력받으면, 누구나 글 번호만 알면 삭제 페이지에 접근할 수 있다는 뜻이 됩니다. 이는 바람직하지 않으므로 글 번호는 HTTP POST로 전달받습니다.

(3) 모델을 이용해서 글을 삭제합니다.

(4) 글을 삭제하고 나면 더이상 보여줄 글이 없으므로 글 목록으로 페이지를 이동합니다.

http client로 확인해 봅시다.

글 삭제 기능은 HTTP POST만 가능하므로 브라우저의 주소 입력만으로는 삭제 기능을 테스트할 수 없습니다. 따라서 http client에 아래의 내용을 입력해서 확인해 봅시다.

POST http://localhost:8080/post/delete
Content-Type: application/x-www-form-urlencoded

post_id=1

응답 코드가 아래와 같이 404 Not Found가 나오면 성공입니다.

HTTP/1.1 404 Not Found

404 Not Found가 나온 이유는 데이터를 삭제하고 나서 페이지가 목록 페이지로 이동하기 때문에 http client가 리다이렉트되는 주소를 따라가기 때문입니다.

실제 데이터베이스를 확인해 보면 deleted_at 열이 null이 아님을 확인할 수 있습니다.

curl로 글 삭제해 보기

curl을 이용하려면 아래와 같이 하시면 됩니다.

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'post_id=1' http://localhost:8080/post/delete/1

글 목록 만들기

글 목록 기능 만들기

글 목록 엔드포인트는 아래와 같습니다. 기존의 index 메소드를 수정합시다.

// 목록
public function index()
{
    $model = new PostsModel();
    $post_query = $model->orderBy("created_at", "desc");
    $post_list = $model->paginate(10); // (1)
    $pager = $post_query->pager;
    $pager->setPath("/post");

    return view("post/index", [
        'post_list' => $post_list,
        'pager' => $pager
    ]);
}

(1) 개발할 때 별 거 아닌데 만들기 귀찮은 것이 목록 페이징 기능인데요. 코드이그나이터4는 paginate라는 메소드를 제공합니다.
첫번째 파라미터 10은 페이지 당 보여줄 글 갯수인 $perPage입니다.
만약 GET 파라미터 page 가 있을 경우 page 의 값을 현재 페이지로 사용하게 됩니다. 없으면 기본값은 1입니다.

글 목록 뷰 만들기

app/Views/post/index.php
소스코드

<?php
function show_content($content) // (1)
{
    $content = strip_tags($content); // (2)
    if (mb_strlen($content) > 100) { // (3)
        $content = mb_substr($content, 0, 100); // (4)
    }
    return $content;
}
?>
<ul>
<?php foreach ($post_list as $post) : ?>
    <a href="<?= site_url("/post/show/{$post->post_id}") ?>"><?= $post->title ?></a>  <!-- (5) -->
    <div><?= show_content($post->html_content) ?></div>
    <a href="<?= site_url("/post/show/{$post->post_id}/#content") ?>">Read more</a>
<?php endforeach ?>
</ul>
<?= $pager->links() ?> <!-- (6) -->

(1) 글 목록에서 글 내용을 간단하게 보여주기 위해 show_content라는 함수를 만듭니다.

(2) 만약 글 내용에 태그가 있을 경우 HTML이 꺠지는 경우가 생기므로 HTML 태그를 strip_tags로 제거합니다.

(3)(4) 글 내용 길이가 100글자가 넘어가면 100글자까지만 보여주도록 처리하겠습니다. mb_ 계열 PHP 내장 함수를 사용해서 멀티바이트 스트링에도 문제 없도록 대비합니다.

(5) site_url 함수는 상대 경로를 절대 경로로 바꿔줍니다. 예를 들어 '/post/show/1' 주소를 파라미터로 전달하면 'http://localhost:8080/post/show/1'을 반환합니다.

(6) 코드이그나이터의 $pager는 페이징에서 자동으로 페이지 목록이 보여지게 하는 기능을 가지고 있습니다. 우리는 단순히 $pager->links() 만 호출하면 자동으로 페이지 목록이 보여집니다.

profile
스타트업에 관심이 많은 10 + n년차 웹 개발자. 자바 스프링 (혹은 부트), 파이썬 플라스크, PHP를 주로 다룹니다.

0개의 댓글