CodeIgniter 4로 블로그 만들기 #13 — 글 작성 폼과 작성자 연결
지난 회차에서 글 저장의 백엔드를 완성했습니다. 라우트, 검증 규칙, create()의 저장 로직, 그리고 이를 못 박는 테스트까지 초록이 됐습니다. 다만 정작 사람이 글을 쓸 화면은 아직 없습니다. Posts::new()가 view('posts/create')를 부르지만 그 뷰 파일이 없었죠.
이번 회차에서 그 폼을 채웁니다. 그리고 지난 회차에서 임시로 비워 둔 하나, **"이 글은 누가 썼는가"**를 채웁니다. 저장되는 글에 로그인한 사용자를 작성자로 묶고, 헤더에 "글쓰기" 링크를 노출합니다.
준비물 / 목표
- 이전 회차까지 완성된 상태 (저장 백엔드·검증·테스트 초록).
- 이번 회차에서 하는 일:
posts/create.php작성 폼 추가 (old(), 에러 표시,csrf_field())create()에user_id = auth()->id()로 작성자 연결- 헤더에 로그인 시 "글쓰기" 링크 추가
1. 작성 폼 — posts/create.php
app/Views/posts/create.php를 만듭니다. 공통 레이아웃을 상속받는 구조는 앞선 뷰들과 같습니다.
<?= $this->extend('layouts/default') ?>
<?= $this->section('title') ?>새 글 작성<?= $this->endSection() ?>
<?= $this->section('content') ?>
<h1 class="page-title">새 글 작성</h1>
<?php // 검증 실패 시 컨트롤러가 flashdata 로 넘긴 에러를 보여 준다. ?>
<?php $errors = session('errors') ?? []; ?>
<?php if ($errors !== []): ?>
<ul class="form-errors">
<?php foreach ($errors as $error): ?>
<li><?= esc($error) ?></li>
<?php endforeach ?>
</ul>
<?php endif ?>
<form class="form" action="<?= site_url('posts') ?>" method="post">
<?= csrf_field() ?>
<div>
<label for="title">제목</label>
<?php // old() 로 직전 입력값을 되살린다(withInput 과 짝). ?>
<input type="text" name="title" id="title" value="<?= esc(old('title')) ?>">
</div>
<div>
<label for="body">본문</label>
<textarea name="body" id="body" rows="12"><?= esc(old('body')) ?></textarea>
</div>
<button type="submit" class="btn">저장</button>
<a class="btn btn-ghost" href="<?= site_url('posts') ?>">취소</a>
</form>
<?= $this->endSection() ?>
지난 회차에서 심어 둔 백엔드 장치들이 이 폼에서 하나씩 짝을 맞춥니다.
에러 표시. 이전 회차의 create()는 검증 실패 시 with('errors', $model->errors())로 에러를 flashdata에 넘겼습니다. 폼에서는 session('errors')로 그걸 꺼내, 있으면 <ul class="form-errors">로 나열합니다. 에러 메시지도 사용자에게 보여지는 문자열이므로 esc()로 감쌉니다.
old()로 입력값 되살리기. ep12의 withInput()이 세션에 저장해 둔 직전 입력값을, 폼에서 old('title')·old('body')로 되살립니다. 검증 실패로 폼에 돌아왔을 때 제목·본문이 그대로 남아 있어, 사용자가 처음부터 다시 쓰지 않아도 됩니다. withInput()(백엔드)과 old()(폼)는 한 쌍이라는 점을 이 폼에서 눈으로 확인할 수 있습니다.
csrf_field(). ep10에서 CSRF 보호를 session 기반으로 켰기 때문에, 실제 브라우저에서 이 폼을 제출하려면 CSRF 토큰이 필요합니다. csrf_field()가 숨은 토큰 입력을 자동으로 넣어 줍니다. 이게 없으면 실제 POST가 거부됩니다. (테스트가 토큰 없이도 통과했던 이유는 ep12에서 다뤘듯 테스트 환경이 CSRF를 우회하기 때문입니다.)
폼 액션. action은 site_url('posts'), method는 post입니다. 즉 이 폼은 ep12에서 등록한 POST posts → Posts::create로 제출됩니다. 폼 디자인(.form·.btn·.btn-ghost)은 ep03의 app.css가 자동으로 입혀 줍니다. 여기서는 Editor 목업의 톤(폼과 버튼)만 차용하고, 마크다운 textarea 같은 편집기 기능은 ep26에서 다룹니다.
2. 작성자 연결 — user_id = auth()->id()
이전 회차의 create()에는 일부러 비워 둔 게 하나 있었습니다. 저장되는 글에 작성자가 없었습니다. 이제 채웁니다.
// allowedFields 에 든 값만 추려서 받는다.
$data = $this->request->getPost(['title', 'body']);
// 현재 로그인한 사용자를 작성자로 묶는다.
$data['user_id'] = auth()->id();
// 임시 slug. ...
$data['slug'] = 'post-' . bin2hex(random_bytes(6));
auth()->id()는 현재 로그인한 사용자의 ID를 돌려줍니다. 이 값을 저장 데이터의 user_id에 넣어, "이 글은 이 사람이 썼다"를 DB에 기록합니다.
여기서 중요한 설계 원칙 하나. 작성자 ID는 폼에서 받지 않고 서버에서 채웁니다. 만약 user_id를 폼의 hidden 필드로 받았다면, 악의적인 사용자가 그 값을 조작해 남의 이름으로 글을 쓸 수 있습니다. 반면 auth()->id()는 서버가 아는 현재 세션의 사용자이므로 위조할 수 없습니다. "클라이언트가 보낸 값 중 신원 정보는 믿지 않는다"는 원칙입니다.
이렇게 작성자를 묶어 두면, 나중에 "본인 글만 수정·삭제"나 상세 페이지의 작성자 표시 같은 기능의 토대가 됩니다.
3. 헤더에 "글쓰기" 링크
마지막으로, 로그인한 사용자가 작성 폼으로 갈 입구를 헤더에 만듭니다. ep11에서 만든 로그인 분기 안, 로그인 상태 블록에 링크 한 줄을 더합니다.
<?php if (auth()->loggedIn()): ?>
<a class="btn" href="<?= site_url('posts/new') ?>">글쓰기</a>
<span class="nav-user"><?= esc(auth()->user()->username) ?>님</span>
<a class="nav-link" href="<?= site_url('logout') ?>">로그아웃</a>
<?php else: ?>
<a class="nav-link" href="<?= site_url('login') ?>">로그인</a>
<a class="btn btn-ghost" href="<?= site_url('register') ?>">회원가입</a>
<?php endif ?>
auth()->loggedIn()이 참일 때만 "글쓰기" 버튼이 보입니다. 비로그인 사용자에게는 애초에 이 링크가 나타나지 않습니다. 물론 링크를 숨기는 것만으로 보안이 되는 건 아닙니다. 진짜 방어는 이전 회차의 session 필터 그룹이 담당합니다. 링크 숨김은 UI 편의, 필터는 실제 차단, 둘의 역할이 다릅니다.
이로써 헤더는 기본 → 글 링크 → 로그인 분기 → 글쓰기 링크로 점진적으로 완성됩니다.
핵심 개념 — 폼과 백엔드는 한 쌍
이번 회차의 폼은 그 자체로 대단할 게 없어 보이지만, ep12의 백엔드와 정확히 짝을 이룹니다.
| 백엔드(ep12) | 폼(ep13) |
| --- | --- |
| withInput() | old('title') / old('body') |
| with('errors', ...) | session('errors')로 목록 표시 |
| CSRF session 보호(ep10) | csrf_field() |
| POST posts 라우트 | <form action=".../posts" method="post"> |
폼을 짤 때 "이 값은 백엔드의 무엇과 대응하는가"를 항상 의식하면, 화면과 서버가 어긋나는 실수가 줄어듭니다. 그리고 작성자처럼 신원과 관련된 값은 폼이 아니라 서버(auth()->id())에서 채운다는 원칙은, 앞으로 권한을 다루는 회차 내내 반복됩니다.
마무리 — 커밋과 태그
이번 회차에서 한 일:
posts/create.php작성 폼 추가 (old(),session('errors')표시,csrf_field())create()에user_id = auth()->id()로 작성자 연결- 헤더에 로그인 시 "글쓰기" 링크 추가
git add .
git commit -m "feat: 글 작성 폼과 작성자 연결"
git tag ep13
이제 브라우저에서 로그인한 뒤 헤더의 "글쓰기"를 눌러 실제로 글을 써 보세요. 빈 제목으로 저장하면 에러가 뜨고 입력값이 살아남는지, 정상 입력이면 목록으로 돌아가며 글이 추가되는지 확인하면 이번 회차가 완성된 것입니다.
다음 회차
다음 글에서는 글 수정을 만듭니다. 작성 폼과 거의 같은 .form을 재사용하되, 이번엔 기존 값을 폼에 바인딩하는 것이 관건입니다. 그리고 PostUpdateTest로 수정 흐름을 테스트로 고정합니다.
이번 회차 요약
- 다루는 파일:
app/Views/posts/create.php(추가) /app/Controllers/Posts.php·app/Views/partials/header.php(수정)- 핵심: 폼은 백엔드와 한 쌍.
old()로 입력값 복원,session('errors')로 에러 표시,csrf_field()로 토큰. 작성자 같은 신원 값은 폼이 아니라 서버에서auth()->id()로 채운다. 글쓰기 링크는 로그인 시에만(UI 편의), 실제 차단은session필터.
다음: 글 수정
댓글 0
아직 댓글이 없습니다.