よくある掲示板を1から積み上げて作る〜関数分割〜

## 進行中のプロジェクトに関する概要はこちら

- [【導入編】](https://qiita.com/pgsait0928/items/a28b9629af871d75216f)
- [【github.com/sait0928/bbs】](https://github.com/sait0928/bbs)


## 開発環境

- CentOS 7
- Apache 2.4
- MySQL 8.0
- PHP 7.4


## 今回の作業

- 投稿を削除する機能を追加
- 同じ処理をまとめる
- ペジネーション実装
- いくつかの処理を関数に分割


## 投稿を削除する機能を追加

現状( [index.php](https://github.com/sait0928/bbs/blob/98c5299a436fa47bdc19ba20b702cde65990e20c/index.php) )では、
投稿データを`SELECT`してくる処理も
テキストを`INSERT`する処理も
全て1つのファイルのみで行っておりますが、
何でもかんでも同じファイルに書いてしまうと
だんだんコードが読みにくくなってくるため、
投稿と削除について
それぞれPOSTの送り先を変えて
別のファイルで処理を行います。

```index.php
<!-- 投稿用のフォーム -->

<div id="form">
	<!-- insert.php にPOST通信で text を送る -->
	<form action="insert.php" method="POST">
		<textarea name="text" id="" cols="50" rows="5"></textarea>
		<input type="submit" value="投稿">
	</form>
</div>


<!-- 削除用のフォーム -->

<?php foreach($posts as $post) : ?>
<tr>
	<td><?php echo $post['id']; ?></td>
	<td><?php echo nl2br(htmlspecialchars($post['post'])); ?></td>
	<td>
		<!-- delete.php にPOST通信で $post['id'] を送る -->
		<form action="delete.php" method="POST">
			<input type="hidden" name="id" value="<?php echo $post['id']; ?>">
			<input type="submit" value="削除">
		</form>
	</td>
</tr>
<?php endforeach; ?>
```

そして `index.php` に書いていた投稿と削除の処理は
フォームの`action`で指定したそれぞれのファイル内で行います。
ここでPOST通信かどうかの判定も行います。

```insert.php
<?php

$dsn = 'mysql:dbname=bbs;host=localhost';
$user = 'root';
$pass = '';

if($_SERVER['REQUEST_METHOD'] === 'POST') {
	$dbh = new PDO($dsn, $user, $pass);
	$text = $_POST['text'];
	$stmt = $dbh->prepare('INSERT INTO posts (post) VALUES (:post)');
	$stmt->bindParam(':post', $text, PDO::PARAM_STR);
	$stmt->execute();
}

header('Location: index.php');
```

```delete.php
<?php

$dsn = 'mysql:dbname=bbs;host=localhost';
$user = 'root';
$pass = '';

if($_SERVER['REQUEST_METHOD'] === 'POST') {
	$dbh = new PDO($dsn, $user, $pass);
	$id = $_POST['id'];
	$stmt = $dbh->prepare('DELETE FROM posts WHERE id=:id');
	$stmt->bindParam(':id', $id, PDO::PARAM_INT);
	$stmt->execute();
}

header('Location: index.php');
```

この時点では
`header()`によるリダイレクト終了後に
`exit;`が記述されておりませんが、
後々修正が入ります。

## 同じ処理をまとめる

ここで、
複数のファイルで
全く同じ処理が行われている部分があることに
気がつきました。
データベース接続のところですね。

`connect.php`というファイルに
まとめてしまいましょう。

```connect.php
$dsn = 'mysql:dbname=bbs;host=localhost';
$user = 'root';
$pass = '';

$dbh = new PDO($dsn, $user, $pass);
```

これに伴い、
他のファイルでは
`connect.php`を [include](https://www.php.net/manual/ja/function.include.php) するように
書き換えていきます。

```index.php
include 'connect.php';

$stmt = $dbh->query('SELECT * FROM posts ORDER BY id DESC');
$posts = $stmt->fetchAll();
```

他のファイルの書き換えについては割愛いたします。


## ペジネーション実装

参考記事 - [ページネーション(ページ切り替え)の実装方法いろいろ](https://qiita.com/kumasun/items/3932382c267d04413b4e)

まずは`COUNT`を使用して`posts`テーブルのレコード数を取得し、
これを1ページあたりの投稿表示数で割って端数は切り上げます。
(このプロジェクトでは1ページあたり3つの投稿を表示します)

```index.php
$stmt = $dbh->query('SELECT COUNT(*) FROM posts');
$count = $stmt->fetchColumn();
$pages = ceil($count / 3);
```

ちなみにこの記事を書いてる時点で
[マジックナンバー](https://qiita.com/RyoheiHashimoto/items/38ec132bd2852238295e)を使っていることに気づいたので
修正しておきます。

これで必要なページ数(`$pages`)が取得できました。
このページ数を元に
ページリンク部分の`HTML`を書きます。

```index.php
<div id="pagination">
	<?php for($i = 1; $i <= $pages; $i++) : ?>
	<a href="?page=<?php echo $i; ?>"><?php echo $i; ?></a>
	<?php endfor; ?>
</div>
```

今何ページ目にいるかは
`GET`で取得します。
ページが指定されていない場合のデフォルト値は
`null`にしておきます。

```index.php
$page = $_GET['page'] ?? null;
```

取得したページ番号が
`null`かそうでないかで分岐させて
投稿内容を取得してきます。

```index.php
if($page === null) {
	$stmt = $dbh->query('SELECT * FROM posts ORDER BY id DESC LIMIT 3');
	$posts = $stmt->fetchAll();
} else {
	$start = ($page - 1) * 3;
	$stmt = $dbh->prepare('SELECT * FROM posts ORDER BY id DESC LIMIT :start, 3');
	$stmt->bindValue(':start', $start, PDO::PARAM_INT);
	$stmt->execute();
	$posts = $stmt->fetchAll();
}
```

`$start = ($page - 1) * 3;`
1ページ目のときは1〜3番目の投稿、
2ページ目のときは4〜6番目の投稿を取ってくるので、
その初期位置を`start`と定義します。

完成したのがこちらです。
[index.php](https://github.com/sait0928/bbs/blob/cfd871c63ced8fcfdc461cfaef230dda3bdc947b/index.php)


## いくつかの処理を関数に分割

現状では全ての処理をベタ書きしているため
パッと見で何をやっているか分かりづらいです。
そこで思いついたところから
どんどん関数にまとめていくことで、
処理のまとまりに名前をつけて
分かりやすいコードにしていこうと思います。

これらの関数は
現時点では特に分類はせず
`functions.php`に羅列していきます。

まず、先ほど分けたデータベース接続処理を
関数化してみましょう。

```functions.php
/**
 * データベースに接続
 *
 * @param string $dsn
 * @param string $user
 * @param string $pass
 * @return PDO
 */
function connect(string $dsn, string $user, string $pass): PDO
{
	return $dbh = new PDO($dsn, $user, $pass);
}
```

`return`の後に変数`dbh`を定義していますが、
ただ`PDO`を返せばよいだけなのでこの記述は不要でした。
後々修正が入ります。

また、この時点ではデータベース情報を引数に取っていますが、
結局同じデータベースしか使わないので、

``` functions.php
function connect(): PDO
{
	return new PDO(
		'mysql:dbname=bbs;host=localhost',
		'root',
		''
	);
}
```

後々このようになります。

なお、元々この部分が記述されていた
`connect.php`は不要になりましたので
削除いたします。

続いて投稿の操作に関わる4つの処理部分について
関数化してしまいます。

```functions.php
/**
 * フォームに入力された内容をテーブルに挿入
 *
 * @param PDO $dbh
 * @param string $text
 */
function insert(PDO $dbh, string $text): void
{
	$stmt = $dbh->prepare('INSERT INTO posts (post) VALUES (:post)');
	$stmt->bindParam(':post', $text, PDO::PARAM_STR);
	$stmt->execute();
}

/**
 * レコードを削除
 *
 * @param PDO $dbh
 * @param int $id
 */
function delete(PDO $dbh, int $id): void
{
	$stmt = $dbh->prepare('DELETE FROM posts WHERE id=:id');
	$stmt->bindParam(':id', $id, PDO::PARAM_INT);
	$stmt->execute();
}

/**
 * テーブルの内容を出力
 *
 * @param PDO $dbh
 * @param int|null $page
 * @return array
 */
function select(PDO $dbh, ?int $page): array
{
	if($page === null) {
		$stmt = $dbh->query('SELECT * FROM posts ORDER BY id DESC LIMIT 3');
		return $posts = $stmt->fetchAll();
	} else {
		$start = ($page - 1) * 3;
		$stmt = $dbh->prepare('SELECT * FROM posts ORDER BY id DESC LIMIT :start, 3');
		$stmt->bindParam(':start', $start, PDO::PARAM_INT);
		$stmt->execute();
		return $posts = $stmt->fetchAll();
	}
}

/**
 * ページ総数のカウント
 *
 * @param PDO $dbh
 * @return int
 */
function countPages(PDO $dbh): int
{
	$stmt = $dbh->query('SELECT COUNT(*) FROM posts');
	$count = $stmt->fetchColumn();
	return $pages = ceil($count / 3);
}
```

こちらも`return`の行で変数を定義してしまっておりますが
不要な記述でしたので後々修正が入ります。

この記事はあくまでコミットログの振り返りなので、
こういったことが度々あります。ご了承ください。

ところで、
`insert.php` や `delete.php` は

- データベースに接続する
- POST通信かどうか判定する
- 投稿を操作する
- リダイレクトする

という流れで進んでいきますが、
これらを全て関数に突っ込んでしまうと
何を行う処理なのかが分かりにくいため
「投稿を操作する」部分だけを関数化しています。

以上の関数を用いて書き換えたのがこちらです
[github.com/sait0928/bbs](https://github.com/sait0928/bbs/tree/163aba22685a0beaebb013122f880240ccf8cd9e)
今回の振り返りはここまでとなります。


## あとがき

自分で書いたコードを
ちゃんと説明できるか確認するのも
理解が深まる作業になって楽しいですね。

私の振り返り用(というか就活用)で投稿していて、
ある程度分かっていること前提で
話を進めてしまっているところがあるので、
そのうち細かいところにフォーカスして解説する記事も
後から足していけたらなあ、と思っていたりします。
実現できなかったらすみません。笑