이번 챕터의 코드는 https://github.com/koeunyeon/ci4/commits/blog-markdown 에서 찾을 수 있습니다.
글에 마크다운 기능을 붙여보겠습니다. 우선 PHP 내장 함수로는 마크다운을 처리할 수 없으므로 php-markdown 라이브러리를 사용해 보겠습니다.
최신의 PHP는 composer
를 이용해 라이브러리를 손쉽게 설치할 수 있습니다.
터미널에서 아래 명령어로 설치해 보겠습니다.
php composer.phar require michelf/php-markdown
참고로 composer
실행은 PHP 웹 루트 src/sample
에서 해야 합니다. 만약 디렉토리 구조가 달라서 composer
는 /src/composer.phar
경로에 존재하고, 실제 웹 루트는 /src/sample
이라면, /src/sample
디렉토리로 이동 후 아래와 같이 실행하면 됩니다. 즉, composer.phar
파일 경로를 명시적으로 지정하면 가능합니다.
php ../composer.phar require michelf/php-markdown
이런 메시지가 나오면 성공입니다.
Using version ^1.9 for michelf/php-markdown
./composer.json has been updated
Running composer update michelf/php-markdown
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
- Locking michelf/php-markdown (1.9.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
- Downloading michelf/php-markdown (1.9.0)
- Installing michelf/php-markdown (1.9.0): Extracting archive
Generating autoload files
1 package you are using is looking for funding.
Use the `composer fund` command to find out more!
설치가 완료되고 나면 composer.json
파일이 업데이트됩니다. 기존에 없던 "michelf/php-markdown": "^1.9"
항목이 의존성(require
) 부분에 추가된 것을 확인할 수 있습니다.
"require": {
"php": ">=7.2",
"codeigniter4/framework": "^4",
"michelf/php-markdown": "^1.9"
},
/vender/michelf
디렉토리도 생겼습니다.
마크다운 텍스트를 입력받아서 보여줄 때 HTML로 변경하는 것이 목표입니다. 이 때 변경할 수 있는 방법이 두 개 있는데요.
프로그램에서 뭔가를 변경한다면 늘 비용(cost)을 생각해야 합니다. 글 데이터의 특성상 많이 읽히고, 적게 변경됩니다. 따라서 1. 마크다운 원본만 저장하고 읽을때마다 마크다운을 변환해서 보여주기보다는 2. 마크다운을 미리 변경해두는 것 이 더 적은 CPU 비용이 듭니다. 대신 더 많은 저장 비용이 들지요.
-- | 저장비용 | CPU 비용 |
---|---|---|
원본만 저장 | 낮음 | 높음 |
변환본도 저장 | 높음 | 낮음 |
저는 2. 변환된 HTML도 저장해두는 방법을 선택하겠습니다. 마크다운은 거의 스펙 변화가 없는 특성상 사용자가 직접 수정하지 않는 한 변환된 데이터를 다시 데이터 변환(마이그레이션 - 코드이그나이터의 마이그레이션과는 다릅니다.)을 할 일은 거의 없기 때문입니다. 컴퓨터의 저장공간은 아직도 실시간으로 발전하고 있으나 CPU의 발전은 어느정도에서 멈춰있다는 것도 한 몫 합니다. 클라우드 서비스에서 스토리지 비용은 상대적으로 저렴하지만 CPU 비용이 비싼 것도 이 때문입니다.
데이터를 저장할 컬럼을 새로 추가하겠습니다.
직접 데이터베이스 툴에서 추가해도 되지만, 이번에는 마이그레이션을 이용해 보겠습니다.
터미널에서 마이그레이션 파일을 만듭니다.
php spark migrate:create posts
생성된 마이그레이션 파일을 아래와 같이 변경합니다.
app/Database/Migrations/날짜_posts.php
<?php namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class Posts extends Migration
{
public function up()
{
$this->forge->dropTable('posts', true); // (1)
$this->forge->addField([
'post_id' => [
'type' => 'BIGINT',
'unsigned' => true,
'auto_increment' => true,
],
'title' => [
'type' => 'VARCHAR',
'constraint' => '100',
],
'author' => [
'type' => 'VARCHAR',
'constraint' => '100',
],
'content' => [
'type' => 'LONGTEXT' // (2)
],
'html_content' => [
'type' => 'LONGTEXT' // (3)
],
'created_at' => [
'type' => 'VARCHAR',
'constraint' => '25',
],
'updated_at' => [
'type' => 'VARCHAR',
'constraint' => '25',
],
'deleted_at' => [
'type' => 'VARCHAR',
'constraint' => '25',
'null' => true,
]
]);
$this->forge->addKey('post_id', true);
$this->forge->createTable('posts');
}
//--------------------------------------------------------------------
public function down()
{
$this->forge->dropTable('posts');
}
}
(1) 현재 코드이그나이터4의 마이그레이션 메소드 down
은 스키마를 변경할 때 실행되지 않습니다. 대신 이전 버전으로 되돌릴 때만 사용됩니다. 다시 말해 마이그레이션이 첫번째 버전일 때는 실행되지 않으므로 up
에서 강제로 테이블을 삭제합니다. 두번째 파라미터는 isExist
로, 데이터베이스 테이블이 있을 때만 삭제합니다.
(2) 가만히 생각해보니, 컨텐츠 내용은 512글자로 모자란 것 같아 4G까지 담을 수 있는 LONGTEXT
로 설정했습니다. 다행히도 MySQL은 최대치를 설정할 뿐, 만약에 1kb만 담겨있다면 용량은 1kb만 차지하므로 행 하나당 4G라는 무서운 생각은 안하셔도 됩니다.
실제로 개발하다보면 데이터베이스의 컬럼 타입이나 길이를 조절하는 일은 굉장히 많습니다. 이 때 손으로 데이터베이스에 맞추는 것보다는 스키마를 관리하는 것이 더 편리하고 명시적입니다.
(3) HTML을 담을 수 있는 html_content
컬럼을 신규로 추가했습니다.
마이그레이션을 실행해서 posts
테이블을 생성합니다.
php spark migrate
콘솔 결과는 아래와 비슷합니다.
Running all new migrations...
Running: (App) 2021-03-29-051323_App\Database\Migrations\Rich
Running: (App) 2021-04-02-011357_App\Database\Migrations\SampleEntity
Running: (App) 2021-04-05-011531_App\Database\Migrations\Relation
Running: (App) 2021-10-22-083505_App\Database\Migrations\Posts
주의해야 할 점이 있다면, $this->forge->dropTable('posts', true)
로 테이블을 drop
후 create
했기 때문에 데이터가 모두 삭제됩니다. 따라서 테스트로 넣어둔 데이터도 삭제되죠.
Post
컨트롤러에 아래와 같은 메소드를 추가합니다.
app/Controllers/Post.php
// $content가 있다면 마크다운으로 변환한 데이터도 함께 배열에 담는다.
private function add_input_markdown(){ // (1)
$data = $this->request->getPost();
if (array_key_exists("content", $data)){ // (2)
$content = $data['content'];
$content = str_replace(PHP_EOL, " " . PHP_EOL, $content); // (3)
$data['html_content'] = Markdown ::defaultTransform($content); // (4)
}
return $data;
}
add_input_markdown
메소드는 주석에 써 있는 대로 입력값에 content
가 있다면 마크다운으로 변환한 html_content
항목을 추가해서 리턴합니다.
(1) 메소드가 private
임을 볼 수 있습니다. 메소드 접근제한자가 private
인 경우 코드이그나이터4의 엔드포인트로 인식되지 않습니다. 바꿔 말하면 그저 내부 함수일 뿐 외부에서 접근할 수 없습니다.
(2) content
키가 있는지 검사합니다.
(3) 마크다운에서 줄바꿈은 끝에 공백 2칸(' '
)을 붙이거나, 엔터를 두 번 입력하는 방법으로 합니다. 두가지는 미묘한 차이가 있는데, 공백 2칸은 줄바꿈 기호 <br/>
로 변환되고, 엔터 두 번은 단락으로 인지되어 <p>
로 변환됩니다.
어느쪽이든 간에, 사람이 글을 쓸 때 마지막 글자를 인위적으로 띄어주는 것은 부자연스럽고, 엔터를 두 번 치면 너무 공백이 많아져 이상해 보이므로 각 줄바꿈 기호를 " "
+ PHP_EOL
로 변경하겠습니다.
참고로 PHP_EOL
기호는 PHP의 상수로, 설치된 OS의 개행 문자(Carrage return)을 나타냅니다. 예를 들어 윈도우즈에서 사용하고 있다면 \r\n
을, 리눅스에서는 \n
기호를 뜻합니다. 맥 OS 9 까지는 \r
입니다. (현재는 \n
)
(4) 마크다운을 HTML로 변경하려면 Markdown::defaultTransform(마크다운);
형식으로 사용합니다. HTML로 변환된 내용은 html_content
에 담깁니다.
이제 데이터를 생성/수정할 때 add_input_markdown
메소드를 통해서 하도록 변경해 볼께요.
create()
메소드는 아래처럼 바꿉니다.
app/Controllers/Post.php
// $post_id = $model->insert($this->request->getPost()); // 이부분은 주석처리하세요.
$data = $this->add_input_markdown();
$post_id = $model->insert($data);
edit()
메소드도 바꾸는 방법은 동일합니다.
// $isSuccess = $model->update($post_id, $this->request->getPost()); // 이부분은 주석처리하세요.
$data = $this->add_input_markdown();
$isSuccess = $model->update($post_id, $data);
html_content
열이 추가되었으므로 모델의 allowedFields
에도 추가해 줍니다. 이부분을 빼먹으면 데이터가 저장되지 않습니다.
app/Models/PostsModel.php
protected $allowedFields = ['title', 'content', 'author', 'html_content'];
http://localhost:8080/post/create에서 데이터를 저장해 봅시다.
내용은 마크다운 문법만 사용했다면 뭐든 상관없습니다. 저는 이렇게 적었습니다.
소스코드
# 만나서 반가워요.
## CI4 강좌에요.
저는 고은연입니다.
`<?php echo "hello ci4"; `
http://localhost:8080/post/edit/1에서 바꿔보는 것도 해야죠.
마지막으로 phpmyadmin을 통해 직접 데이터베이스를 확인해 봅시다.
상세보기 뷰에서 HTML 데이터를 보여주겠습니다. 기존에 본문을 보여주는 코드는 아래와 같습니다.
app/Views/post/show.php
<?= nl2br(esc($post['content'])) ?>
아래와 같이 바꾸겠습니다.
<?= $post['html_content'] ?>
http://localhost:8080/post/show/1 에 접속해보면 정상 동작을 확인할 수 있습니다.
목록 페이지에 가 보면, 마크다운 문법이 그대로 보이기 때문에 이상하게 느껴질 수 있습니다.
심지어는 푸터도 옆으로 붙어버렸군요.
따라서 HTML 태그를 제거해 버리고 문자만 남겨놓겠습니다. 또한 글자가 너무 길면 목록이 이상해지므로 100글자까지만 남겨놓고 나머지는 삭제하겠습니다.
목록 뷰 페이지를 수정합니다. 먼저 컨텐츠를 가공할 함수를 하나 만듭니다.
app/Views/post/index.php
function show_content($content){
$content = strip_tags($content); // (1)
if (mb_strlen($content) > 100){ // (2)
$content = mb_substr($content, 0,100); // (3)
}
return $content;
}
일반적으로는 뷰에 함수를 만들지 않지만, 원본 데이터를 뷰에서 가공해야 할 경우에는 뷰에 함수를 사용하기도 합니다. 즉, 비즈니스 로직과는 무관한 화면의 영역에는 뷰에서 함수를 사용하는 것도 방법입니다.
(1) strip_tags
함수는 PHP의 내장 함수로 HTML을 모두 제거합니다.
(2) 길이가 100글자 이상인지 검사합니다.
PHP에서 앞에 mb_
가 붙는 함수들은 Multi Byte
의 약자로, 한글처럼 멀티바이트를 쓰는 언어들을 처리하는 함수입니다. 만약 한글이 섞여있는 문자열에 mb_strlen
이 아니라 strlen
을 사용하면 함수는 문자열이 ascii
인코딩이라고 판단해서 정상적으로 글자 길이를 세지 않습니다.
(3) 문자열을 앞의 100글자만 잘라냅니다.
이제 show_content
함수를 사용하도록 뷰 영역을 바꿉니다. 기존 영역은 아래와 같았습니다.
<div class="intro"><?= $post['content'] ?></div>
변경된 내용은 아래와 같습니다.
<div class="intro"><?= show_content($post['html_content']) ?></div>
원하는대로 작동하는지 한번 확인해 봅시다. http://localhost:8080/post/
어후 마크 다운 패키지가 있는거 첨알았는데.. 신기하네요
저는 마크다운 에디터 넣은작업 하고있어요