12. PHP 게시판 만들기, list 제작 2

2015. 4. 16. 00:22
저자 : Kurien

주의: 본 게시판은 보안을 생각하지 않고 만들어졌으므로 실제로 사용되어서는 안되는 코드입니다.

공부할 때 게시판이 이처럼 동작한다는 정도로만 이해해주세요.


오늘은 페이징에 대해서 알아보겠습니다.

오늘은 조금 복잡하니 잘 따라오세요!

먼저 오늘 수정된 파일을 받아봅시다.


20150415_project.zip


코드를 설명하기에 앞서 페이징에 대해서 설명드리겠습니다.

페이징이라는건 주로 게시판 목록에서 하단에 존재하는 1 ~ 10, 11 ~ 20과 같이 각 페이지를 볼 수 있게하는 기능을 말합니다.

이런 기능이 필요한 이유가 뭘까요?



먼저 한 목록에 모든 페이지가 나오게 되면 스크롤이 길어집니다.

사용자가 가독성이 떨어지게 되고 글을 찾기도 힘들죠.


그리고 요청 데이터가 많은 만큼 페이지의 로딩 속도(모바일의 경우 데이터 문제도 있음)도 많이 느려지겠죠.


하지만 가장 중요한 부분은 페이징 기능을 사용함으로써 DB에 대한 부하가 줄어듭니다.

만약 게시판에 10000개의 글이 존재하는 게시판에 만명의 사용자가 들어왔는데, 모든 글을 출력해준다고 하면 어떻게 될까요?

모두에게 10000개의 글을 보여주기 전에 DB 서버가 과부하가 걸려서 멈출겁니다.


페이징을 사용하게 되면 적당한 양의 글을 나눠서 보여줄 수 있습니다.

이제 코드를 보며 페이징의 작동 방식을 알아봅시다.


아래의 코드는 index.php 부분입니다.


<?php

require_once("../dbconfig.php");

/* 페이징 시작 */

//페이지 get 변수가 있다면 받아오고, 없다면 1페이지를 보여준다.

if(isset($_GET['page'])) {

$page = $_GET['page'];

} else {

$page = 1;

}

$sql = 'select count(*) as cnt from board_free order by b_no desc';

$result = $db->query($sql);

$row = $result->fetch_assoc();

$allPost = $row['cnt']; //전체 게시글의 수

$onePage = 15; // 한 페이지에 보여줄 게시글의 수.

$allPage = ceil($allPost / $onePage); //전체 페이지의 수

if($page < 1 || ($allPage && $page > $allPage)) {

?>

<script>

alert("존재하지 않는 페이지입니다.");

history.back();

</script>

<?php

exit;

}

$oneSection = 10; //한번에 보여줄 총 페이지 개수(1 ~ 10, 11 ~ 20 ...)

$currentSection = ceil($page / $oneSection); //현재 섹션

$allSection = ceil($allPage / $oneSection); //전체 섹션의 수

$firstPage = ($currentSection * $oneSection) - ($oneSection - 1); //현재 섹션의 처음 페이지

if($currentSection == $allSection) {

$lastPage = $allPage; //현재 섹션이 마지막 섹션이라면 $allPage가 마지막 페이지가 된다.

} else {

$lastPage = $currentSection * $oneSection; //현재 섹션의 마지막 페이지

}

$prevPage = (($currentSection - 1) * $oneSection); //이전 페이지, 11~20일 때 이전을 누르면 10 페이지로 이동.

$nextPage = (($currentSection + 1) * $oneSection) - ($oneSection - 1); //다음 페이지, 11~20일 때 다음을 누르면 21 페이지로 이동.

$paging = '<ul>'; // 페이징을 저장할 변수

//첫 페이지가 아니라면 처음 버튼을 생성

if($page != 1) { 

$paging .= '<li class="page page_start"><a href="./index.php?page=1">처음</a></li>';

}

//첫 섹션이 아니라면 이전 버튼을 생성

if($currentSection != 1) { 

$paging .= '<li class="page page_prev"><a href="./index.php?page=' . $prevPage . '">이전</a></li>';

}

for($i = $firstPage; $i <= $lastPage; $i++) {

if($i == $page) {

$paging .= '<li class="page current">' . $i . '</li>';

} else {

$paging .= '<li class="page"><a href="./index.php?page=' . $i . '">' . $i . '</a></li>';

}

}

//마지막 섹션이 아니라면 다음 버튼을 생성

if($currentSection != $allSection) { 

$paging .= '<li class="page page_next"><a href="./index.php?page=' . $nextPage . '">다음</a></li>';

}

//마지막 페이지가 아니라면 끝 버튼을 생성

if($page != $allPage) { 

$paging .= '<li class="page page_end"><a href="./index.php?page=' . $allPage . '">끝</a></li>';

}

$paging .= '</ul>';

/* 페이징 끝 */

$currentLimit = ($onePage * $page) - $onePage; //몇 번째의 글부터 가져오는지

$sqlLimit = ' limit ' . $currentLimit . ', ' . $onePage; //limit sql 구문

$sql = 'select * from board_free order by b_no desc' . $sqlLimit; //원하는 개수만큼 가져온다. (0번째부터 20번째까지

$result = $db->query($sql);

?>

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<title>자유게시판 | Kurien's Library</title>

<link rel="stylesheet" href="./css/normalize.css" />

<link rel="stylesheet" href="./css/board.css" />

</head>

<body>

<article class="boardArticle">

<h3>자유게시판</h3>

<div id="boardList">

<table>

<caption class="readHide">자유게시판</caption>

<thead>

<tr>

<th scope="col" class="no">번호</th>

<th scope="col" class="title">제목</th>

<th scope="col" class="author">작성자</th>

<th scope="col" class="date">작성일</th>

<th scope="col" class="hit">조회</th>

</tr>

</thead>

<tbody>

<?php

while($row = $result->fetch_assoc())

{

$datetime = explode(' ', $row['b_date']);

$date = $datetime[0];

$time = $datetime[1];

if($date == Date('Y-m-d'))

$row['b_date'] = $time;

else

$row['b_date'] = $date;

?>

<tr>

<td class="no"><?php echo $row['b_no']?></td>

<td class="title">

<a href="./view.php?bno=<?php echo $row['b_no']?>"><?php echo $row['b_title']?></a>

</td>

<td class="author"><?php echo $row['b_id']?></td>

<td class="date"><?php echo $row['b_date']?></td>

<td class="hit"><?php echo $row['b_hit']?></td>

</tr>

<?php

}

?>

</tbody>

</table>

<div class="btnSet">

<a href="./write.php" class="btnWrite btn">글쓰기</a>

</div>

<div class="paging">

<?php echo $paging ?>

</div>

</div>

</article>

</body>

</html>


지금까지와는 다르게 프로그램 부분이 매우 길게 느껴지네요.

주석을 달아놓긴 했지만, 차근차근 나눠서 자세한 설명을 진행하겠습니다.


/* 페이징 시작 */

//페이지 get 변수가 있다면 받아오고, 없다면 1페이지를 보여준다.

if(isset($_GET['page'])) {

$page = $_GET['page'];

} else {

$page = 1;

}


먼저 페이징 시작 부분입니다.

page라는 get 변수를 이용해서 페이징을 구현할텐데, 먼저 $_GET['page']가 있다면 $page에 넣어줍니다.

만약 $_GET['page']의 값이 없다면 $page에는 1을 넣어줍니다.


1을 넣어주는 이유는 만약 http://kurien.co.kr/project/board/ 이 주소로 접속을 했을 때는 get 변수가 존재하지 않게 되는데,

$_GET['page']가 없을 때 $page가 1이라면 위의 주소와 같은 경우에도 첫 페이지를 정상적으로 볼 수 있게 됩니다.


$sql = 'select count(*) as cnt from board_free';

$result = $db->query($sql);

$row = $result->fetch_assoc();


$allPost = $row['cnt']; //전체 게시글의 수


$onePage = 15; // 한 페이지에 보여줄 게시글의 수.

$allPage = ceil($allPost / $onePage); //전체 페이지의 수


if($page < 1 || ($allPage && $page > $allPage)) {

?>

<script>

alert("존재하지 않는 페이지입니다.");

history.back();

</script>

<?php

exit;

}


여기서 $sql 부분은 select 문을 이용해서 board_free 테이블 내의 모든 행의 개수를 가져옵니다.

여기서 행 = 게시 글이므로 모든 게시 글의 수를 말합니다.


그리고 그 값($row['cnt'])를 $allPost에 넣습니다.

$allPost에는 전체 게시글 수가 들어있겠죠?


이번엔 $onePage를 선언하는데요.

$onePage 변수에는 한 페이지에 보여질 게시물의 수를 넣어줍니다.

주로 이용되는 게시판은 10~20개의 글이 보여지는게 일반적인 것으로 알고 있습니다.

저는 15개를 보여지게 할 생각이므로 15를 넣었습니다.


다음은 $allPage입니다.

$allPost는 전체 게시글 수 였다면 $allPage는 전체 페이지의 수입니다.

연산을 보면 $allPost(전체 게시글 수) / $onePage(한 페이지에 보일 게시글 수)인데, 이 값을 ceil 함수가 감싸고 있습니다.

ceil 함수는 올림, 반올림, 내림에서 올림을 나타냅니다.


여기서 전체 게시글이 400개라고 가정했을 때 전체 페이지는 400 / 15 = 26.666을 올림 한 27이 됩니다.

올림을 하는 이유는 400개의 글까지는 26 페이지까지 다 보이겠지만 27페이지가 없다면 나머지 10개의 글을 볼 수 없기 때문이죠.


$allPage까지 구했다면 $page가 1보다 작거나 $allPage보다 큰지 확인을 해봅니다.

$page가 1보다 작거나 $allPage보다 클 수는 없으므로 이 경우는 사용자가 잘못된 경로를 이용한다는 뜻입니다.

잘못된 경로이니 경고를 출력하고 이전 화면으로 돌려보내줍니다.


$oneSection = 10; //한번에 보여줄 총 페이지 개수(1 ~ 10, 11 ~ 20 ...)

$currentSection = ceil($page / $oneSection); //현재 섹션

$allSection = ceil($allPage / $oneSection); //전체 섹션의 수


$firstPage = ($currentSection * $oneSection) - ($oneSection - 1); //현재 섹션의 처음 페이지


if($currentSection == $allSection) {

$lastPage = $allPage; //현재 섹션이 마지막 섹션이라면 $allPage가 마지막 페이지가 된다.

} else {

$lastPage = $currentSection * $oneSection; //현재 섹션의 마지막 페이지

}


$prevPage = (($currentSection - 1) * $oneSection); //이전 페이지, 11~20일 때 이전을 누르면 10 페이지로 이동.

$nextPage = (($currentSection + 1) * $oneSection) - ($oneSection - 1); //다음 페이지, 11~20일 때 다음을 누르면 21 페이지로 이동.


여기서는 Section 변수를 선언하네요.

Section과 관련된 변수는 다른 분들은 Block으로 쓰는데, 저는 그냥 Section이 맘에 드니 Section으로 하겠습니다.


먼저 Section이란건 1 ~ 10, 11 ~ 20, 21 ~ 30 ... 과 같은 페이지 번호가 있을 때 1 ~ 10을 1 섹션으로 정했습니다.

11 ~ 20의 경우에는 2 섹션, 21 ~ 30은 3 섹션이죠.


이렇게 10개의 페이지 번호가 1 섹션임을 나타내는 변수가 $oneSection입니다.

값이 10으로 되어있죠?


그리고 $currentSection은 현재 Section을 나타내는 변수입니다.

$page(현재 페이지)를 $oneSection(10)으로 나눈 값을 올림한 값입니다.

현재 페이지가 22페이지라면 현재 Section은 3이 되겠죠.

이 변수는 뒤에 나올 변수를 연산하는데 쓰입니다.


$allSection은 $allPage(전체 페이지 수 27)를 $oneSection(10)으로 나눴으므로 2.7이 되고, 이 부분도 마찬가지로 올림을 합니다.

여기선 3이란 값을 갖겠네요.


$firstPage는 현재 섹션의 처음 페이지, $currentSection이 3이라면 21 ~ 30의 페이지를 출력해야 하는데,

여기서 21이란 값을 구하기 위한 부분입니다.

$currentSection(현재 섹션 값 3) * $oneSection(10)을 계산하고, 거기에서 $oneSection - 1(9)를 빼줍니다.

그러므로 30 - 9 = 21, 즉 3 섹션의 첫 번째 페이지인 21이란 값이 나오게 되는거죠.


$lastPage는 반대로 현재 섹션의 마지막 페이지를 나타냅니다.

그런데 이 부분은 if와 else로 나눠져 있죠?

여기서는 조금 더 생각을 해야하는게, 3 섹션이라고 하더라도 마지막 페이지는 반드시 30이 아닙니다.

글이 400개일 때 총 페이지는 27이므로 마지막 페이지는 페이지 27겠죠?


이 부분 때문에 $allSection을 구한건데요,

만약 $currentSection(현재 섹션)과 $allSection(마지막 섹션)이 같다면, $lastPage는 $allPage로 대체합니다.

$currentSection과 $allSection이 다르다면 마지막 페이지를 구해주는데, $currentSection * $onePage가 마지막 페이지가 됩니다.


페이지 변수 선언 부분의 마지막으로 $prevPage와 $nextPage 부분이 있습니다.

$prevPage는 이전, $nextPage는 다음을 나타내는데 이전을 눌렀을 때는 이전 섹션의 마지막 페이지(21 ~ 30 기준 20)로 이동하고,

다음을 눌렀을 때는 다음 섹션의 첫 번째 페이지(21 ~ 30 기준 31)로 이동합니다.


연산은 이전은 현재 섹션에 -1을 했고, 다음은 +1을 했습니다.

다음일 경우에는 +1 만으로는 안되기 때문에 ($onePage-1) 값(9)을 더 빼줬습니다.


$paging = '<ul>'; // 페이징을 저장할 변수


//첫 페이지가 아니라면 처음 버튼을 생성

if($page != 1) { 

$paging .= '<li class="page page_start"><a href="./index.php?page=1">처음</a></li>';

}

//첫 섹션이 아니라면 이전 버튼을 생성

if($currentSection != 1) { 

$paging .= '<li class="page page_prev"><a href="./index.php?page=' . $prevPage . '">이전</a></li>';

}


for($i = $firstPage; $i <= $lastPage; $i++) {

if($i == $page) {

$paging .= '<li class="page current">' . $i . '</li>';

} else {

$paging .= '<li class="page"><a href="./index.php?page=' . $i . '">' . $i . '</a></li>';

}

}


//마지막 섹션이 아니라면 다음 버튼을 생성

if($currentSection != $allSection) { 

$paging .= '<li class="page page_next"><a href="./index.php?page=' . $nextPage . '">다음</a></li>';

}


//마지막 페이지가 아니라면 끝 버튼을 생성

if($page != $allPage) { 

$paging .= '<li class="page page_end"><a href="./index.php?page=' . $allPage . '">끝</a></li>';

}

$paging .= '</ul>';

/* 페이징 끝 */


여기부터는 실제로 태그로 출력할 내용을 변수에 저장하는 부분입니다.

저는 ul과 li 태그를 이용해서 리스트를 작성했기 때문에 먼저 $paging에 <ul>이라는 값을 넣어주면서 초기화 시킵니다.


그 다음 처음 버튼을 만드는데 처음 버튼이 존재 해야 할 상황은 $page(현재 페이지)의 값이 1이 아닐 때입니다.

그러므로 $page != 1일 때 처음 버튼을 만들어 주는거죠.


이전 버튼은 $currentSection이 1이 아닐 때 만들어줍니다.

1~10일 때 이전 버튼이 있어버리면 있지도 않은 0페이지로 이동해버리겠죠?

이전 버튼의 $paging .= 부분을 보시면 위에서 만든 $prevPage 변수가 사용되는 것을 보실 수 있습니다.


다음 버튼과 끝 버튼 전에 for문이 하나 나오는데요,

여기는 1 ~ 10, 11 ~ 20, 21 ~ 30 ...과 같은 페이지들을 만들어 주는 부분입니다.

먼저 $i는 위에서 만든 $firstPage 변수로 초기화 하고, $lastPage와 같아질 때까지 반복해줍니다.

여기서도 if문이 있는데, 만약 $i의 값이 $page(현재 페이지)와 같을 경우엔 current라는 클래스를 넣어주고, 앵커 태그(<a>)가 없애줬습니다.

$i가 같지 않다면 current는 없고 앵커 태그(<a>)가 있겠죠?


for를 이용해 페이지를 만들었다면 다음 버튼을 만들 차례입니다.

다음 버튼은 이전 버튼과 비슷한데, 주의할 부분은 조건 부분의 1이 $allSection으로 바뀌고 $prevPage가 $nextPage로 변경 된 정도겠네요.


끝 버튼도 처음 버튼과 비슷합니다.

조건 부분의 1이 $allPage로 바뀌고 $paging .=의 1이 $allPage로 바뀌었습니다.


페이징 부분을 모두 작성 했으니 </ul>태그로 닫아줍니다.

여기까지 페이징 기능은 끝입니다.


이제 페이징에 맞게 sql을 변경하고 출력하는 부분입니다.


$currentLimit = ($onePage * $page) - $onePage; //몇 번째의 글부터 가져오는지

$sqlLimit = ' limit ' . $currentLimit . ', ' . $onePage; //limit sql 구문


$sql = 'select * from board_free order by b_no desc' . $sqlLimit; //원하는 개수만큼 가져온다. (0번째부터 20번째까지

$result = $db->query($sql);


이 부분은 list 제작 1에서 만들었던 부분을 가지고 수정했습니다.

비교해보시면 아시겠지만 코드 뿐만 아니라 위치도 파란색으로 표시된 부분에서 상단으로 조금 옮겼습니다.


여기서 $currentLimit는 현재 페이지에 따라 가져올 부분을 정해줍니다.

$sqlLimit 변수에 들어가는 변수인데, sql 전체를 함께 설명드리겠습니다.


select * from board_free order by b_no desc limit 0, 15;와 같은 sql이 있다고 할 때 이 부분을 해석하자면,

board_free 테이블에서 b_no을 기준으로 내림차순 정렬된 데이터 중 0번부터 15개의 행을 출력하라.

라는 의미입니다.


"0번째 부터 15개의 행을 출력하라." 라는 부분이 $sqlLimit이고, 여기서 "0번째"가 $currentLimit 부분입니다.

$currentLimit는 1 페이지일 때 ($onePage(15) * $page(1)) - $onePage(15) 이므로 15 - 15 = 0,

1 페이지일 때 0번째 글부터 출력하게 되고, 2페이지일 때는 (15 * 2) - 15가 되므로 15번째 글부터 출력하는거죠.


이런식으로 몇 번째부터 글을 출력하느냐가 페이징의 핵심입니다.


<div class="paging">

<?php echo $paging ?>

</div>


마지막으로 $paging에 저장 했던 페이징 태그들을 이와같이 출력해줍니다.



이렇게 페이징 기능을 구현해서 모든 글을 여러 페이지로 나누어 보여줄 수 있습니다.


거의 2~3시간에 걸쳐서 작성한 것 같네요.

이것 보다 더 줄여서 만들 수도 있을 것 같은데 아무래도 줄이기가 힘드네요.

더 좋은 코드나 지적사항, 오탈자 지적이나 문의 사항 등 모두 댓글에 남겨주시면 시간 나는대로 답변 달아드리겠습니다.


어려운 분들은 여러번 읽어보고 직접 구현해보셔야해요.

그냥 쳐다보기만 해서는 이해하기 힘듭니다.

꼭 직접 구현해보세요!


현재 진행 상황은 http://kurien.dothome.co.kr에서 확인하실 수 있습니다!