· nambak80 Blog 로그인
CodeIgniter4

CodeIgniter 4로 블로그 만들기 #05 — posts 마이그레이션

CodeIgniter 4로 블로그 만들기 #05 — posts 마이그레이션

지난 회차까지 우리는 정적 페이지, 공통 레이아웃, 그리고 첫 테스트로 프로젝트의 뼈대를 세웠습니다. 여기까지는 화면과 요청 흐름에 관한 이야기였습니다. 이번 섹션 부터는 드디어 데이터를 다룹니다.

블로그의 중심은 "글"입니다. 글을 저장할 그릇, 즉 데이터베이스 테이블이 있어야 목록도 그리고 상세도 보여 줄 수 있습니다. 이번 회차의 목표는 딱 하나입니다. posts 테이블을 마이그레이션으로 정의하는 것입니다.

왜 마이그레이션인가

DB 테이블은 phpMyAdmin 같은 도구로 손으로 만들 수도 있습니다. 하지만 그렇게 하면 그 구조가 어디에도 코드로 남지 않습니다. 다른 사람이 이 프로젝트를 받아 실행하려면 "이런이런 컬럼으로 테이블을 만들어 주세요"라고 말로 전달해야 하죠.

마이그레이션은 테이블 구조를 PHP 코드로 남기는 방법입니다. 스키마가 저장소에 커밋되므로, 누구든 php spark migrate 한 줄로 똑같은 테이블을 재현할 수 있습니다. 잘못 만들었다면 php spark migrate:rollback으로 되돌릴 수도 있습니다. 이 "만들고 되돌리는" 대칭성이 마이그레이션의 핵심입니다.

이번 회차 목표

  • posts 테이블의 컬럼을 Forge로 정의한다.
  • 슬러그(slug)에 유니크 인덱스를 건다.
  • up()으로 생성하고 down()으로 삭제되도록 대칭을 맞춘다.

마이그레이션 파일 만들기

CI4는 마이그레이션 파일을 app/Database/Migrations/에 두고, 파일명 앞에 타임스탬프를 붙이는 규칙을 씁니다(YYYY-MM-DD-HHMMSS_Name.php). 이 타임스탬프 덕분에 마이그레이션이 만들어진 순서대로 실행됩니다.

app/Database/Migrations/2026-06-11-064056_CreatePostsTable.php 파일을 만들고, up() 메서드에서 컬럼을 정의합니다.

<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class CreatePostsTable extends Migration
{
    public function up()
    {
        $this->forge->addField([
            'id'         => [
                'type'           => 'INT',
                'constraint'     => 11,
                'unsigned'       => true,
                'auto_increment' => true,
            ],
            'user_id'    => [
                'type'       => 'INT',
                'constraint' => 11,
                'unsigned'   => true,
                'null'       => true,
            ],
            'title'      => [
                'type'       => 'VARCHAR',
                'constraint' => 255,
            ],
            'slug'       => [
                'type'       => 'VARCHAR',
                'constraint' => 255,
            ],
            'body'       => [
                'type' => 'TEXT',
            ],
            'created_at' => [
                'type' => 'DATETIME',
                'null' => true,
            ],
            'updated_at' => [
                'type' => 'DATETIME',
                'null' => true,
            ],
        ]);

        $this->forge->addPrimaryKey('id');
        // 슬러그는 URL 식별자로 쓰이므로 유일해야 한다
        $this->forge->addUniqueKey('slug');
        $this->forge->createTable('posts');
    }

    public function down()
    {
        $this->forge->dropTable('posts');
    }
}

컬럼 하나씩 뜯어보기

$this->forge는 CI4가 마이그레이션 안에서 제공하는 Forge 객체입니다. 데이터베이스 방언(MySQL, SQLite 등)에 상관없이 같은 코드로 테이블을 만들 수 있게 해 줍니다. 우리는 개발에서 MySQL/SQLite를, 테스트에서 SQLite 메모리를 쓰기 때문에 이 추상화가 특히 고맙습니다.

각 컬럼의 의도를 짚어 봅시다.

  • idauto_increment가 붙은 정수 기본키. 글마다 자동으로 1, 2, 3… 번호가 매겨집니다. unsigned를 줘서 음수를 배제합니다.
  • user_id — 글의 작성자를 가리킬 자리입니다. 그런데 아직 인증(Shield)을 도입하지 않았으므로 null을 허용해 둡니다. 사용자 테이블은 ep10에서 생기고, 그전까지 글에는 작성자가 없습니다. 미래를 위한 자리만 미리 비워 두는 셈입니다.
  • title — 글 제목. 255자 VARCHAR.
  • slug — URL에 쓰이는 식별자입니다. 예를 들어 posts/codeigniter4-blog-start처럼요. 사람이 읽을 수 있으면서도 주소로 안전한 문자열입니다.
  • body — 본문. 길이가 길 수 있으니 VARCHAR가 아니라 TEXT를 씁니다.
  • created_at / updated_at — 생성·수정 시각. 지금은 null을 허용해 두지만, ep07에서 모델의 $useTimestamps가 이 두 컬럼을 자동으로 채워 줄 것입니다.

컬럼 정의가 끝나면 세 가지 인덱스/제약을 겁니다.

$this->forge->addPrimaryKey('id');
$this->forge->addUniqueKey('slug');
$this->forge->createTable('posts');

addPrimaryKey('id')로 기본키를 지정하고, addUniqueKey('slug')슬러그가 중복될 수 없게 막습니다. slug는 URL 식별자라서 두 글이 같은 slug를 가지면 어느 글을 보여 줘야 할지 알 수 없습니다. 그래서 DB 레벨에서 유일성을 강제합니다. 마지막으로 createTable('posts')가 실제로 테이블을 만듭니다.

up()과 down()의 대칭

마이그레이션은 항상 짝으로 움직입니다. up()이 앞으로 가는 방향(테이블 생성)이라면, down()은 되돌리는 방향(테이블 삭제)입니다.

public function down()
{
    $this->forge->dropTable('posts');
}

up()에서 createTable을 했으니 down()에서는 dropTable을 합니다. 이 대칭이 잘 맞아야 migrate:rollback이 깔끔하게 동작합니다. 마이그레이션을 작성할 때는 늘 "이걸 어떻게 되돌리지?"를 함께 생각하는 습관을 들이는 게 좋습니다.

실행과 검증

작성했으면 실제로 돌려 봅니다.

php spark migrate

posts 테이블이 생성됩니다. 되돌리기가 제대로 동작하는지도 한 번 확인해 보세요.

php spark migrate:rollback   # posts 테이블이 사라짐
php spark migrate            # 다시 생성

up()down()이 대칭이면 이 왕복이 아무 문제 없이 반복됩니다.

마무리 — 커밋과 태그

이번 회차는 파일 하나만 추가했습니다. 하지만 이 파일이 앞으로 모든 데이터 작업의 토대가 됩니다.

git add .
git commit -m "feat: posts 마이그레이션"
git tag ep05

다음 회차

테이블은 생겼지만 아직 비어 있습니다. 다음 글에서는 시더로 더미 글을 채웁니다. 목록과 상세 화면을 다듬어 보려면 그럴듯한 데이터가 필요하니까요. 아직 PostModel이 없으므로, 이번에는 쿼리 빌더로 직접 데이터를 넣습니다.


이번 회차 요약

  • 다루는 파일: app/Database/Migrations/...._CreatePostsTable.php(생성)
  • 핵심: Forge로 컬럼을 정의하고 php spark migrate로 테이블 생성. user_id는 인증 도입 전이라 nullable, slug는 URL 식별자라 유니크 인덱스. up()/down()의 대칭으로 롤백까지 안전하게.

다음: 시더로 더미 데이터 생성

댓글 0

아직 댓글이 없습니다.

로그인 후 댓글을 남길 수 있습니다.

← 목록으로