뉴스 섹션
지난 섹션에서는 정적 페이지를 참조하는 클래스를 작성하여 프레임워크의 몇 가지 기본 개념을 살펴보았습니다. 또한 커스텀 라우팅 규칙을 추가하여 URI를 깔끔하게 정리했습니다. 이제 동적 콘텐츠를 도입하고 데이터베이스를 사용하기 시작할 때입니다.
작업할 데이터베이스 생성
CodeIgniter 설치 시 요구 사항에 명시된 대로 적절한 데이터베이스를 설정했다고 가정합니다. 이 튜토리얼에서는 MySQL 데이터베이스를 위한 SQL 코드를 제공하며, 데이터베이스 명령을 실행하기 위한 적절한 클라이언트(mysql, MySQL Workbench 또는 phpMyAdmin)가 있다고 가정합니다.
이 튜토리얼에서 사용할 ci4tutorial 데이터베이스를 만든 다음, CodeIgniter에서 이를 사용하도록 설정해야 합니다.
데이터베이스 클라이언트를 사용하여 데이터베이스에 접속한 후 다음 SQL 명령을 실행합니다(MySQL):
CREATE TABLE news (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(128) NOT NULL,
slug VARCHAR(128) NOT NULL,
body TEXT NOT NULL,
PRIMARY KEY (id),
UNIQUE slug (slug)
);
또한 몇 개의 시드 레코드를 추가합니다. 지금은 테이블 생성에 필요한 SQL 문만 보여드리지만, CodeIgniter에 더 익숙해지면 이를 프로그래밍 방식으로 처리할 수 있음을 알아두세요. 나중에 마이그레이션 및 시드(Seeds)에 대해 읽어보고 더 유용한 데이터베이스 설정을 만들 수 있습니다.
참고 사항: 웹 퍼블리싱 맥락에서 “슬러그(slug)”는 리소스를 식별하고 설명하기 위해 URL에 사용되는 사용자 및 SEO 친화적인 짧은 텍스트를 의미합니다.
시드 레코드는 다음과 같을 수 있습니다:
INSERT INTO news VALUES
(1,'Elvis sighted','elvis-sighted','Elvis was sighted at the Podunk internet cafe. It looked like he was writing a CodeIgniter app.'),
(2,'Say it isn\'t so!','say-it-isnt-so','Scientists conclude that some programmers have a sense of humor.'),
(3,'Caffeination, Yes!','caffeination-yes','World\'s largest coffee shop open onsite nested coffee shop for staff only.');
데이터베이스 연결
CodeIgniter를 설치할 때 만든 로컬 설정 파일인 .env에서 데이터베이스 속성 설정의 주석을 해제하고 사용하려는 데이터베이스에 맞게 적절히 설정해야 합니다. 데이터베이스 구성에 설명된 대로 데이터베이스를 올바르게 설정했는지 확인하세요:
database.default.hostname = localhost
database.default.database = ci4tutorial
database.default.username = root
database.default.password = root
database.default.DBDriver = MySQLi
모델 설정하기
데이터베이스 작업을 컨트롤러에 직접 작성하는 대신, 나중에 쉽게 재사용할 수 있도록 쿼리를 모델에 배치해야 합니다. 모델은 데이터베이스나 다른 데이터 저장소에서 정보를 검색, 삽입 및 업데이트하는 곳입니다. 모델은 데이터에 대한 접근을 제공합니다. 이에 대한 자세한 내용은 CodeIgniter 모델 사용하기에서 읽어볼 수 있습니다.
NewsModel 생성
app/Models 디렉토리를 열고 NewsModel.php라는 새 파일을 만든 후 다음 코드를 추가합니다.
<?php
namespace App\Models;
use CodeIgniter\Model;
class NewsModel extends Model
{
protected $table = 'news';
}
이 코드는 이전에 사용한 컨트롤러 코드와 비슷해 보입니다. CodeIgniter\Model을 상속받아 새 모델을 생성하고 데이터베이스 라이브러리를 로드합니다. 이렇게 하면 $this->db 객체를 통해 데이터베이스 클래스를 사용할 수 있게 됩니다.
NewsModel::getNews() 메서드 추가
데이터베이스와 모델이 설정되었으므로 데이터베이스에서 모든 포스트를 가져오는 메서드가 필요합니다. 이를 위해 CodeIgniter에 포함된 데이터베이스 추상화 계층인 쿼리 빌더(Query Builder)가 CodeIgniter\Model에서 사용됩니다. 이를 통해 ‘쿼리’를 한 번만 작성하면 지원되는 모든 데이터베이스 시스템에서 작동하게 할 수 있습니다. 또한 모델 클래스를 사용하면 쿼리 빌더를 쉽게 다룰 수 있으며, 데이터 작업을 더 간단하게 만들어주는 몇 가지 추가 도구를 제공합니다. 모델에 다음 코드를 추가하세요.
/**
* @param false|string $slug
*
* @return array|null
*/
public function getNews($slug = false)
{
if ($slug === false) {
return $this->findAll();
}
return $this->where(['slug' => $slug])->first();
}
이 코드를 사용하면 두 가지 다른 쿼리를 수행할 수 있습니다. 모든 뉴스 레코드를 가져오거나, 슬러그를 통해 특정 뉴스 항목을 가져올 수 있습니다. 쿼리를 실행하기 전에 $slug 변수가 이스케이프되지 않았다는 점을 눈치채셨을 수도 있는데, 쿼리 빌더가 이를 대신 처리해 줍니다.
여기서 사용된 두 메서드인 findAll()과 first()는 CodeIgniter\Model 클래스에서 제공됩니다. 이들은 앞서 NewsModel 클래스에서 설정한 $table 속성을 기반으로 사용할 테이블을 이미 알고 있습니다. 이들은 쿼리 빌더를 사용하여 현재 테이블에서 명령을 실행하고 원하는 형식의 결과 배열을 반환하는 헬퍼 메서드입니다. 이 예제에서 findAll()은 배열의 배열을 반환합니다.
뉴스 표시하기
쿼리가 작성되었으므로 모델을 사용자에게 뉴스 항목을 표시할 뷰와 연결해야 합니다. 이는 이전에 만든 Pages 컨트롤러에서 수행할 수도 있지만, 명확성을 위해 새로운 News 컨트롤러를 정의하겠습니다.
라우팅 규칙 추가
app/Config/Routes.php 파일을 다음과 같이 수정합니다:
<?php
// ...
use App\Controllers\News; // Add this line
use App\Controllers\Pages;
$routes->get('news', [News::class, 'index']); // Add this line
$routes->get('news/(:segment)', [News::class, 'show']); // Add this line
$routes->get('pages', [Pages::class, 'index']);
$routes->get('(:segment)', [Pages::class, 'view']);
이렇게 하면 요청이 Pages 컨트롤러로 직접 가는 대신 News 컨트롤러에 도달하게 됩니다. 두 번째 $routes->get() 라인은 슬러그가 포함된 URI를 News 컨트롤러의 show() 메서드로 라우팅합니다.
News 컨트롤러 생성
app/Controllers/News.php에 새 컨트롤러를 생성합니다.
<?php
namespace App\Controllers;
use App\Models\NewsModel;
class News extends BaseController
{
public function index()
{
$model = model(NewsModel::class);
$data['news_list'] = $model->getNews();
}
public function show(?string $slug = null)
{
$model = model(NewsModel::class);
$data['news'] = $model->getNews($slug);
}
}
코드를 보면 이전에 만든 파일들과 몇 가지 유사점을 발견할 수 있습니다. 먼저, 이 컨트롤러는 CodeIgniter의 핵심 클래스인 Controller를 상속받는 BaseController를 확장합니다. 이는 몇 가지 헬퍼 메서드를 제공하며, 현재의 Request 및 Response 객체뿐만 아니라 정보를 디스크에 저장하기 위한 Logger 클래스에 접근할 수 있도록 보장합니다.
다음으로 두 개의 메서드가 있습니다. 하나는 모든 뉴스 항목을 보기 위한 것이고, 다른 하나는 특정 뉴스 항목을 위한 것입니다.
다음으로 model() 함수를 사용하여 NewsModel 인스턴스를 생성합니다. 이는 헬퍼 함수입니다. 이에 대한 자세한 내용은 전역 함수 및 상수에서 읽어볼 수 있습니다. 이 함수를 사용하지 않는다면 $model = new NewsModel();과 같이 작성할 수도 있습니다.
두 번째 메서드에서 $slug 변수가 모델의 메서드에 전달되는 것을 볼 수 있습니다. 모델은 이 슬러그를 사용하여 반환할 뉴스 항목을 식별합니다.
News::index() 메서드 완성하기
이제 컨트롤러가 모델을 통해 데이터를 가져왔지만, 아직 아무것도 표시되지 않습니다. 다음에 할 일은 이 데이터를 뷰에 전달하는 것입니다. index() 메서드를 다음과 같이 수정하세요:
<?php
namespace App\Controllers;
use App\Models\NewsModel;
class News extends BaseController
{
public function index()
{
$model = model(NewsModel::class);
$data = [
'news_list' => $model->getNews(),
'title' => 'News archive',
];
return view('templates/header', $data)
. view('news/index')
. view('templates/footer');
}
// ...
}
위의 코드는 모델에서 모든 뉴스 레코드를 가져와 변수에 할당합니다. 제목 값도 $data['title'] 요소에 할당되고 모든 데이터가 뷰로 전달됩니다. 이제 뉴스 항목을 렌더링할 뷰를 만들어야 합니다.
news/index 뷰 파일 생성
app/Views/news/index.php를 만들고 다음 코드를 추가합니다.
<h2><?= esc($title) ?></h2>
<?php if ($news_list !== []): ?>
<?php foreach ($news_list as $news_item): ?>
<h3><?= esc($news_item['title']) ?></h3>
<div class="main">
<?= esc($news_item['body']) ?>
</div>
<p><a href="/news/<?= esc($news_item['slug'], 'url') ?>">View article</a></p>
<?php endforeach ?>
<?php else: ?>
<h3>No News</h3>
<p>Unable to find any news for you.</p>
<?php endif ?>
참고
XSS 공격을 방지하기 위해 다시 한번 esc()를 사용하고 있습니다. 하지만 이번에는 두 번째 매개변수로 “url”을 전달했습니다. 이는 출력이 사용되는 맥락에 따라 공격 패턴이 다르기 때문입니다.
여기서 각 뉴스 항목은 루프를 통해 사용자에게 표시됩니다. PHP와 HTML이 섞인 템플릿을 작성한 것을 볼 수 있습니다. 만약 템플릿 언어를 선호하신다면, CodeIgniter의 View Parser나 서드파티 파서를 사용할 수 있습니다.
News::show() 메서드 완성하기
뉴스 개요 페이지는 완성되었지만, 개별 뉴스 항목을 표시할 페이지가 아직 없습니다. 이전에 만든 모델은 이러한 기능을 쉽게 사용할 수 있도록 만들어졌습니다. 컨트롤러에 코드를 약간 추가하고 새 뷰를 만들기만 하면 됩니다. News 컨트롤러로 돌아가서 show() 메서드를 다음과 같이 업데이트하세요:
<?php
namespace App\Controllers;
use App\Models\NewsModel;
use CodeIgniter\Exceptions\PageNotFoundException;
class News extends BaseController
{
// ...
public function show(?string $slug = null)
{
$model = model(NewsModel::class);
$data['news'] = $model->getNews($slug);
if ($data['news'] === null) {
throw new PageNotFoundException('Cannot find the news item: ' . $slug);
}
$data['title'] = $data['news']['title'];
return view('templates/header', $data)
. view('news/view')
. view('templates/footer');
}
}
PageNotFoundException 클래스를 임포트하기 위해 use CodeIgniter\Exceptions\PageNotFoundException;을 추가하는 것을 잊지 마세요.
매개변수 없이 getNews() 메서드를 호출하는 대신 $slug 변수를 전달하여 특정 뉴스 항목을 반환받습니다.
news/view 뷰 파일 생성
남은 작업은 app/Views/news/view.php에 해당하는 뷰를 만드는 것입니다. 이 파일에 다음 코드를 입력하세요.
<h2><?= esc($news['title']) ?></h2>
<p><?= esc($news['body']) ?></p>
브라우저에서 “뉴스” 페이지(localhost:8080/news)로 접속하면 뉴스 항목 목록이 표시되며, 각 항목에는 해당 기사만 표시하는 링크가 포함되어 있습니다.