PHPで検索フォームを作るには、まずHTMLでフォームを用意し、入力されたキーワードをPHPで受け取り、データベースや配列などの検索対象から一致するデータを取得して表示します。
基本的な流れは、次の通りです。
- HTMLで検索フォームを作る
- PHPで検索キーワードを受け取る
- 入力値を整える
- データベースや配列から検索する
- 検索結果を画面に表示する
検索フォーム自体はシンプルに作れますが、実務では SQLインジェクション対策 や XSS対策 も必要です。
この記事では、PHPで検索フォームを作る基本から、MySQLを使った検索、複数キーワード検索、ページネーション、WordPressでの検索フォームまで詳しく解説します。
PHP検索フォームの基本
検索フォームの基本構造
まずは、HTMLで検索フォームを作ります。
<form action="search.php" method="get">
<input type="search" name="keyword" placeholder="キーワードを入力">
<button type="submit">検索</button>
</form>
このフォームでは、入力されたキーワードが keyword という名前で search.php に送信されます。
例えば、ユーザーが「PHP」と入力して検索すると、URLは次のようになります。
search.php?keyword=PHP
PHP側では、この keyword を受け取って検索処理を行います。
検索フォームではGETを使うのが一般的
検索フォームでは、基本的に GET メソッドを使います。
<form action="search.php" method="get">
GET を使うと、検索条件がURLに表示されます。
例えば、以下のようなURLになります。
search.php?keyword=WordPress
検索フォームで GET がよく使われる理由は、検索結果ページをブックマークしたり、URLを共有したりできるからです。
一方、ログインフォームや問い合わせフォームのように、送信内容をURLに表示したくない場合は POST を使います。
| メソッド | 特徴 | 主な用途 |
|---|---|---|
| GET | URLにパラメータが表示される | 検索、絞り込み、ページネーション |
| POST | URLにパラメータが表示されない | ログイン、会員登録、問い合わせ |
通常のサイト内検索や記事検索であれば、GET を使うのが自然です。
配列を使った簡単な検索フォーム
データベースなしで検索フォームを作る
まずは、MySQLなどのデータベースを使わず、PHPの配列から検索する簡単な例を見てみましょう。
<?php
$items = [
'PHP入門',
'JavaScript基礎',
'WordPressテーマ制作',
'HTMLとCSSの基本',
'Laravel入門',
'MySQLデータベース講座'
];
$keyword = $_GET['keyword'] ?? '';
if (!is_string($keyword)) {
$keyword = '';
}
$keyword = trim($keyword);
$results = [];
if ($keyword !== '') {
foreach ($items as $item) {
if (mb_stripos($item, $keyword, 0, 'UTF-8') !== false) {
$results[] = $item;
}
}
}
function h($value) {
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>検索フォーム</title>
</head>
<body>
<h1>検索フォーム</h1>
<form action="" method="get">
<label for="keyword">検索キーワード</label>
<input
id="keyword"
type="search"
name="keyword"
value="<?php echo h($keyword); ?>"
placeholder="キーワードを入力"
>
<button type="submit">検索</button>
</form>
<hr>
<?php if ($keyword === ''): ?>
<p>キーワードを入力してください。</p>
<?php else: ?>
<h2>「<?php echo h($keyword); ?>」の検索結果</h2>
<?php if (count($results) > 0): ?>
<ul>
<?php foreach ($results as $result): ?>
<li><?php echo h($result); ?></li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p>検索結果が見つかりませんでした。</p>
<?php endif; ?>
<?php endif; ?>
</body>
</html>
$_GETで検索キーワードを受け取る
検索キーワードは、PHPの $_GET で受け取ります。
$keyword = $_GET['keyword'] ?? '';
?? は、値が存在しない場合に空文字を入れるための書き方です。
例えば、URLに keyword がない場合でもエラーにならず、空文字として扱えます。
$keyword = $_GET['keyword'] ?? '';
ただし、悪意あるアクセスによって keyword[]=test のように配列が送信される場合もあります。
そのため、より安全にするなら次のように文字列かどうかを確認します。
if (!is_string($keyword)) {
$keyword = '';
}
その後、trim() で前後の余分な空白を取り除きます。
$keyword = trim($keyword);
mb_stripos()で部分一致検索をする
配列の中からキーワードを探す場合は、mb_stripos() を使うと便利です。
if (mb_stripos($item, $keyword, 0, 'UTF-8') !== false) {
$results[] = $item;
}
mb_stripos() は、文字列の中に指定したキーワードが含まれているかを調べる関数です。
例えば、検索キーワードが「PHP」の場合、次のようなデータに一致します。
PHP入門
また、mb_stripos() は大文字・小文字を区別しません。
php
PHP
Php
このような表記の違いがあっても検索できます。
日本語では英語ほど大文字・小文字の違いを意識する場面は多くありませんが、英数字を含む検索では便利です。
htmlspecialchars()でXSS対策をする
検索フォームで入力された値を画面に表示するときは、必ずエスケープします。
function h($value) {
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
画面に出力するときは、以下のように使います。
echo h($keyword);
これはXSS対策のためです。
例えば、ユーザーが以下のような文字列を入力したとします。
<script>alert('test')</script>
この値をそのままHTMLに出力すると、JavaScriptが実行される危険があります。
そのため、HTMLに表示する値は htmlspecialchars() でエスケープする必要があります。
検索フォームでは、ユーザーが入力したキーワードだけでなく、データベースから取得したタイトルや本文もエスケープして表示するのが基本です。
MySQLを使ったPHP検索フォーム
MySQL検索フォームの仕組み
実際のWebサイトでは、検索対象のデータをMySQLなどのデータベースに保存しているケースが多いです。
PHPでMySQL検索フォームを作る場合は、次のような流れになります。
- 検索フォームからキーワードを送信する
- PHPでキーワードを受け取る
- PDOでMySQLに接続する
- SQLの
LIKEを使って検索する - 検索結果をHTMLに表示する
ここでは、記事データを検索する例で解説します。
サンプルのテーブル構成
以下のような articles テーブルがあるとします。
CREATE TABLE articles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
body TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
サンプルデータは次の通りです。
INSERT INTO articles (title, body) VALUES
('PHP入門', 'PHPの基本文法について解説します。'),
('WordPressテーマ制作', 'WordPressテーマを自作する方法を紹介します。'),
('MySQLの基本', 'MySQLでデータを管理する方法を解説します。'),
('JavaScript入門', 'JavaScriptの基礎を学びます。'),
('Laravel入門', 'Laravelを使ったWebアプリ開発を紹介します。');
このテーブルの title と body を対象に検索します。
PDOでデータベースに接続する
PHPからMySQLに接続する場合は、PDOを使うのが一般的です。
以下のように db.php を作成します。
<?php
$host = 'localhost';
$dbname = 'sample_db';
$user = 'root';
$password = '';
try {
$pdo = new PDO(
"mysql:host={$host};dbname={$dbname};charset=utf8mb4",
$user,
$password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
} catch (PDOException $e) {
error_log($e->getMessage());
exit('データベース接続に失敗しました。');
}
ポイントは、以下の3つです。
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
SQLエラーが起きたときに例外として扱えます。
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
取得結果を連想配列として扱えます。
PDO::ATTR_EMULATE_PREPARES => false
可能な限りMySQL側のネイティブプリペアドステートメントを使う指定です。
また、文字コードには utf8mb4 を指定しています。
charset=utf8mb4
日本語だけでなく、絵文字なども扱いやすくなります。
MySQL検索フォームの完成例
基本的な検索フォームのコード
以下は、MySQLの articles テーブルから記事を検索するサンプルです。
<?php
require_once 'db.php';
function h($value) {
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function escapeLike($value) {
return str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $value);
}
$keyword = $_GET['keyword'] ?? '';
if (!is_string($keyword)) {
$keyword = '';
}
$keyword = trim($keyword);
$error = '';
$articles = [];
if (mb_strlen($keyword, 'UTF-8') > 100) {
$error = '検索キーワードは100文字以内で入力してください。';
} elseif ($keyword !== '') {
$likeKeyword = '%' . escapeLike($keyword) . '%';
$stmt = $pdo->prepare(
"SELECT id, title, body, created_at
FROM articles
WHERE title LIKE :keyword ESCAPE '\\\\'
OR body LIKE :keyword ESCAPE '\\\\'
ORDER BY created_at DESC"
);
$stmt->bindValue(':keyword', $likeKeyword, PDO::PARAM_STR);
$stmt->execute();
$articles = $stmt->fetchAll();
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>記事検索</title>
</head>
<body>
<h1>記事検索</h1>
<form action="" method="get">
<label for="keyword">検索キーワード</label>
<input
id="keyword"
type="search"
name="keyword"
value="<?php echo h($keyword); ?>"
placeholder="キーワードを入力"
>
<button type="submit">検索</button>
</form>
<hr>
<?php if ($error !== ''): ?>
<p><?php echo h($error); ?></p>
<?php elseif ($keyword === ''): ?>
<p>検索キーワードを入力してください。</p>
<?php else: ?>
<h2>「<?php echo h($keyword); ?>」の検索結果</h2>
<?php if (count($articles) > 0): ?>
<?php foreach ($articles as $article): ?>
<article>
<h3><?php echo h($article['title']); ?></h3>
<p><?php echo nl2br(h($article['body'])); ?></p>
<small><?php echo h($article['created_at']); ?></small>
</article>
<hr>
<?php endforeach; ?>
<?php else: ?>
<p>検索結果が見つかりませんでした。</p>
<?php endif; ?>
<?php endif; ?>
</body>
</html>
LIKEを使って部分一致検索をする
MySQLで部分一致検索をする場合は、LIKE を使います。
WHERE title LIKE :keyword
検索キーワードの前後に % を付けることで、文字列の一部に一致するデータを探せます。
$likeKeyword = '%' . $keyword . '%';
例えば、キーワードが「PHP」の場合、SQLとしては次のようなイメージになります。
WHERE title LIKE '%PHP%'
LIKE で使う % には、任意の0文字以上という意味があります。
| 書き方 | 意味 |
|---|---|
PHP% | PHPで始まる |
%PHP | PHPで終わる |
%PHP% | PHPを含む |
検索フォームでは、一般的に %キーワード% のような部分一致検索がよく使われます。
%や_をエスケープする理由
SQLの LIKE では、 % と _ は特別な意味を持ちます。
| 文字 | 意味 |
|---|---|
% | 任意の0文字以上 |
_ | 任意の1文字 |
そのため、ユーザーが % や _ を入力した場合、意図しない検索結果になることがあります。
例えば、ユーザーが % と検索すると、多くのデータに一致してしまう可能性があります。
そのような挙動を避けたい場合は、次のようにエスケープします。
function escapeLike($value) {
return str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $value);
}
そして、SQL側では ESCAPE を指定します。
$stmt = $pdo->prepare(
"SELECT id, title, body, created_at
FROM articles
WHERE title LIKE :keyword ESCAPE '\\\\'
OR body LIKE :keyword ESCAPE '\\\\'
ORDER BY created_at DESC"
);
通常の簡単な検索フォームであれば省略されることもありますが、実務向けに作るなら入れておくと安心です。
プリペアドステートメントでSQLインジェクション対策をする
検索フォームでは、ユーザーが入力した値をSQLに使います。
そのため、SQLインジェクション対策が必要です。
悪い例は以下です。
$sql = "SELECT * FROM articles WHERE title LIKE '%" . $_GET['keyword'] . "%'";
このように、ユーザー入力をSQLに直接連結してはいけません。
安全に書く場合は、PDOのプリペアドステートメントを使います。
$stmt = $pdo->prepare(
"SELECT id, title, body, created_at
FROM articles
WHERE title LIKE :keyword
OR body LIKE :keyword
ORDER BY created_at DESC"
);
$stmt->bindValue(':keyword', '%' . $keyword . '%', PDO::PARAM_STR);
$stmt->execute();
prepare() でSQLを準備し、bindValue() で値を渡します。
これにより、ユーザーが入力した値をSQL文としてではなく、単なる値として扱えます。
検索フォームを作る場合は、最低限以下の2つを意識してください。
// SQLに渡すとき
prepare() と bindValue() を使う
// HTMLに表示するとき
htmlspecialchars() を使う
この2つは、検索フォームの基本的なセキュリティ対策です。
複数キーワード検索を作る方法
複数キーワード検索とは
検索フォームでは、1つのキーワードだけでなく、複数のキーワードで検索したい場合があります。
例えば、ユーザーが次のように入力した場合です。
PHP 入門
この場合、「PHP」と「入門」の両方を含む記事を検索することができます。
複数キーワード検索には、大きく分けて2種類あります。
| 検索方式 | 意味 |
|---|---|
| AND検索 | すべてのキーワードを含む |
| OR検索 | いずれかのキーワードを含む |
一般的なサイト内検索では、検索結果を絞り込みやすいAND検索が使われることが多いです。
AND検索のコード例
以下は、複数キーワードをAND検索する例です。
<?php
require_once 'db.php';
function h($value) {
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function escapeLike($value) {
return str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $value);
}
$keyword = $_GET['keyword'] ?? '';
if (!is_string($keyword)) {
$keyword = '';
}
$keyword = trim($keyword);
$articles = [];
$error = '';
if (mb_strlen($keyword, 'UTF-8') > 100) {
$error = '検索キーワードは100文字以内で入力してください。';
} elseif ($keyword !== '') {
$normalizedKeyword = str_replace(' ', ' ', $keyword);
$keywords = preg_split('/\s+/', $normalizedKeyword, -1, PREG_SPLIT_NO_EMPTY);
$keywords = array_slice($keywords, 0, 5);
$where = [];
$params = [];
foreach ($keywords as $index => $word) {
$titleParam = ':title_keyword' . $index;
$bodyParam = ':body_keyword' . $index;
$where[] = "(title LIKE {$titleParam} ESCAPE '\\\\' OR body LIKE {$bodyParam} ESCAPE '\\\\')";
$params[$titleParam] = '%' . escapeLike($word) . '%';
$params[$bodyParam] = '%' . escapeLike($word) . '%';
}
$sql = 'SELECT id, title, body, created_at FROM articles';
if (count($where) > 0) {
$sql .= ' WHERE ' . implode(' AND ', $where);
}
$sql .= ' ORDER BY created_at DESC';
$stmt = $pdo->prepare($sql);
foreach ($params as $param => $value) {
$stmt->bindValue($param, $value, PDO::PARAM_STR);
}
$stmt->execute();
$articles = $stmt->fetchAll();
}
?>
複数キーワード検索のポイント
複数キーワード検索では、まず全角スペースを半角スペースに変換します。
$normalizedKeyword = str_replace(' ', ' ', $keyword);
日本語サイトでは、ユーザーが全角スペースでキーワードを区切ることもあるためです。
次に、スペースでキーワードを分割します。
$keywords = preg_split('/\s+/', $normalizedKeyword, -1, PREG_SPLIT_NO_EMPTY);
PREG_SPLIT_NO_EMPTY を指定すると、余計な空白によって空のキーワードが混ざるのを防げます。
さらに、キーワード数が多すぎるとSQLが長くなりすぎるため、上限を設けます。
$keywords = array_slice($keywords, 0, 5);
この例では、最大5語までに制限しています。
OR検索に変更する方法
AND検索では、すべてのキーワードを含む記事だけが表示されます。
$sql .= ' WHERE ' . implode(' AND ', $where);
OR検索にしたい場合は、AND を OR に変更します。
$sql .= ' WHERE ' . implode(' OR ', $where);
例えば、検索キーワードが「PHP 入門」の場合、AND検索とOR検索では結果が変わります。
| 検索方式 | 検索条件 |
|---|---|
| AND検索 | PHPと入門の両方を含む |
| OR検索 | PHPまたは入門を含む |
検索結果を絞り込みたいならAND検索、広く検索したいならOR検索が向いています。
カテゴリーで絞り込む検索フォーム
キーワードとカテゴリーを組み合わせる
検索フォームでは、キーワード検索に加えてカテゴリーで絞り込むケースもあります。
例えば、次のようなフォームです。
<form action="" method="get">
<label for="keyword">検索キーワード</label>
<input
id="keyword"
type="search"
name="keyword"
value="<?php echo h($keyword); ?>"
placeholder="キーワードを入力"
>
<label for="category">カテゴリー</label>
<select id="category" name="category">
<option value="">すべてのカテゴリー</option>
<option value="php">PHP</option>
<option value="wordpress">WordPress</option>
<option value="javascript">JavaScript</option>
</select>
<button type="submit">検索</button>
</form>
この場合、PHP側ではキーワード条件とカテゴリー条件を組み合わせます。
カテゴリー検索のコード例
テーブルに category カラムがある場合は、次のように書けます。
$keyword = $_GET['keyword'] ?? '';
$category = $_GET['category'] ?? '';
if (!is_string($keyword)) {
$keyword = '';
}
if (!is_string($category)) {
$category = '';
}
$keyword = trim($keyword);
$category = trim($category);
$allowedCategories = ['php', 'wordpress', 'javascript'];
$where = [];
$params = [];
if ($keyword !== '') {
$where[] = "(title LIKE :keyword ESCAPE '\\\\' OR body LIKE :keyword ESCAPE '\\\\')";
$params[':keyword'] = '%' . escapeLike($keyword) . '%';
}
if ($category !== '' && in_array($category, $allowedCategories, true)) {
$where[] = 'category = :category';
$params[':category'] = $category;
}
$sql = 'SELECT id, title, body, category, created_at FROM articles';
if (count($where) > 0) {
$sql .= ' WHERE ' . implode(' AND ', $where);
}
$sql .= ' ORDER BY created_at DESC';
$stmt = $pdo->prepare($sql);
foreach ($params as $param => $value) {
$stmt->bindValue($param, $value, PDO::PARAM_STR);
}
$stmt->execute();
$articles = $stmt->fetchAll();
カテゴリーの値は、ユーザーが自由に変更できるため、許可した値だけを使うようにします。
$allowedCategories = ['php', 'wordpress', 'javascript'];
このようにホワイトリスト化しておくと、存在しないカテゴリー値で検索されることを防ぎやすくなります。
ページネーション付き検索フォーム
検索結果が多い場合はページネーションを使う
検索結果が多い場合、すべてのデータを1ページに表示すると見づらくなります。
そのため、実務ではページネーションを使って、1ページあたりの表示件数を制限します。
例えば、1ページに10件ずつ表示する場合は、以下のように設定します。
$page = isset($_GET['page']) ? (int) $_GET['page'] : 1;
if ($page < 1) {
$page = 1;
}
$limit = 10;
$offset = ($page - 1) * $limit;
$limit は1ページあたりの表示件数です。
$offset は、何件目から取得するかを表します。
総件数を取得する
ページネーションを作るには、まず検索結果の総件数を取得します。
$countSql = "SELECT COUNT(*)
FROM articles
WHERE title LIKE :keyword ESCAPE '\\\\'
OR body LIKE :keyword ESCAPE '\\\\'";
$countStmt = $pdo->prepare($countSql);
$countStmt->bindValue(':keyword', $likeKeyword, PDO::PARAM_STR);
$countStmt->execute();
$total = (int) $countStmt->fetchColumn();
総件数が分かれば、総ページ数を計算できます。
$totalPages = (int) ceil($total / $limit);
例えば、検索結果が35件で、1ページあたり10件表示する場合、総ページ数は4ページになります。
LIMITとOFFSETで検索結果を取得する
ページごとの検索結果は、SQLの LIMIT と OFFSET を使って取得します。
$sql = "SELECT id, title, body, created_at
FROM articles
WHERE title LIKE :keyword ESCAPE '\\\\'
OR body LIKE :keyword ESCAPE '\\\\'
ORDER BY created_at DESC
LIMIT :limit OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':keyword', $likeKeyword, PDO::PARAM_STR);
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$articles = $stmt->fetchAll();
LIMIT と OFFSET は整数として扱うため、PDO::PARAM_INT を指定します。
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
ページネーションリンクを作る
ページネーションリンクは、検索キーワードを維持したままページ番号だけを変える必要があります。
<?php if ($totalPages > 1): ?>
<nav>
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
<?php
$query = [
'keyword' => $keyword,
'page' => $i,
];
$url = '?' . http_build_query($query);
?>
<?php if ($i === $page): ?>
<strong><?php echo $i; ?></strong>
<?php else: ?>
<a href="<?php echo h($url); ?>"><?php echo $i; ?></a>
<?php endif; ?>
<?php endfor; ?>
</nav>
<?php endif; ?>
http_build_query() を使うと、検索条件をURLに安全に組み立てやすくなります。
カテゴリーや並び替え条件がある場合も、配列に追加するだけで対応できます。
$query = [
'keyword' => $keyword,
'category' => $category,
'page' => $i,
];
検索キーワードをハイライト表示する方法
ハイライト表示とは
検索結果の中で、検索キーワードに一致した部分を目立たせたい場合があります。
例えば、「PHP」で検索した場合に、検索結果のタイトルや本文中の「PHP」を以下のように表示します。
<mark>PHP</mark>入門
HTMLの <mark> タグを使うと、該当部分をハイライトできます。
ハイライト関数の例
単一キーワードをハイライトする簡単な関数は以下です。
function highlightKeyword($text, $keyword) {
if ($keyword === '') {
return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
$pattern = '/' . preg_quote($keyword, '/') . '/iu';
$parts = preg_split(
$pattern,
$text,
-1,
PREG_SPLIT_DELIM_CAPTURE
);
if ($parts === false) {
return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
$result = '';
foreach ($parts as $part) {
if (preg_match($pattern, $part)) {
$result .= '<mark>' . htmlspecialchars($part, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</mark>';
} else {
$result .= htmlspecialchars($part, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
}
return $result;
}
使い方は以下です。
<h3><?php echo highlightKeyword($article['title'], $keyword); ?></h3>
ハイライト処理では、HTMLエスケープ後の文字列を直接置換するよりも、元の文字列を分割して、表示する直前に各部分をエスケープする方が安全です。
複数キーワードのハイライトには注意する
複数キーワードをハイライトする場合は、キーワード同士が重複する可能性があります。
例えば、次のようなキーワードです。
PHP PHP入門
このような場合、単純な置換処理ではHTMLが崩れたり、意図しない箇所がハイライトされたりすることがあります。
そのため、複数キーワードのハイライトを本格的に実装する場合は、キーワードの並び順や重複処理も考慮する必要があります。
初心者向けの検索フォームでは、まずは単一キーワードのハイライトから実装するとよいでしょう。
検索フォームの見た目を整えるCSS
HTMLの例
検索フォームは、HTMLだけでも作れますが、CSSを少し追加すると見やすくなります。
<form action="" method="get" class="search-form">
<label for="keyword" class="search-label">検索キーワード</label>
<input
id="keyword"
type="search"
name="keyword"
value="<?php echo h($keyword); ?>"
placeholder="キーワードを入力してください"
class="search-input"
>
<button type="submit" class="search-button">検索</button>
</form>
label を付けておくと、アクセシビリティの面でも良くなります。
CSSの例
簡単なCSSは以下です。
.search-form {
display: flex;
align-items: center;
gap: 8px;
max-width: 640px;
}
.search-label {
font-weight: bold;
}
.search-input {
flex: 1;
padding: 10px 12px;
border: 1px solid #ccc;
border-radius: 4px;
}
.search-button {
padding: 10px 20px;
border: none;
background: #333;
color: #fff;
border-radius: 4px;
cursor: pointer;
}
.search-button:hover {
opacity: 0.8;
}
スマートフォンでも使いやすくするなら、幅が狭い画面で縦並びにするとよいです。
@media (max-width: 600px) {
.search-form {
flex-direction: column;
align-items: stretch;
}
.search-button {
width: 100%;
}
}
WordPressで検索フォームを作る方法
WordPressでは標準検索機能を使う
WordPressで検索フォームを作る場合は、PHPで一から検索処理を書くのではなく、WordPressの標準検索機能を使うのが基本です。
WordPressでは、検索キーワードのパラメータ名は s です。
<input type="search" name="s">
例えば、「PHP」と検索すると、URLは次のようになります。
https://example.com/?s=PHP
searchform.phpを作成する
テーマフォルダに searchform.php を作成すると、WordPress標準の検索フォームをカスタマイズできます。
<form role="search" method="get" class="search-form" action="<?php echo esc_url(home_url('/')); ?>">
<label for="search-field">
<span class="screen-reader-text">検索:</span>
</label>
<input
id="search-field"
type="search"
class="search-field"
placeholder="キーワードを入力"
value="<?php echo esc_attr(get_search_query()); ?>"
name="s"
>
<button type="submit" class="search-submit">検索</button>
</form>
ポイントは、name="s" を使うことです。
name="s"
また、検索キーワードをHTML属性に出力するため、esc_attr() を使います。
value="<?php echo esc_attr(get_search_query()); ?>"
検索フォームを表示する
テーマ内で検索フォームを表示したい場所に、以下を書きます。
<?php get_search_form(); ?>
これで、テーマ内の searchform.php が読み込まれます。
検索結果ページを作る
WordPressの検索結果ページは、通常 search.php というテンプレートファイルで作ります。
<?php get_header(); ?>
<main class="search-results-page">
<h1>
「<?php echo esc_html(get_search_query()); ?>」の検索結果
</h1>
<?php if (have_posts()): ?>
<?php while (have_posts()): the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<h2>
<a href="<?php echo esc_url(get_permalink()); ?>">
<?php echo esc_html(get_the_title()); ?>
</a>
</h2>
<div>
<?php the_excerpt(); ?>
</div>
</article>
<?php endwhile; ?>
<?php the_posts_pagination(); ?>
<?php else: ?>
<p>検索結果が見つかりませんでした。</p>
<?php endif; ?>
</main>
<?php get_footer(); ?>
WordPressの検索結果ページでは、メインクエリが検索結果になっています。
そのため、基本的には WP_Query を新しく書かなくても、have_posts() と the_post() で検索結果を表示できます。
カスタム投稿タイプを検索対象に含める
WordPressでカスタム投稿タイプも検索対象に含めたい場合は、functions.php に以下のように書きます。
function my_search_filter($query) {
if (!is_admin() && $query->is_main_query() && $query->is_search()) {
$query->set('post_type', ['post', 'page', 'news']);
}
}
add_action('pre_get_posts', 'my_search_filter');
この例では、検索対象に以下の投稿タイプを含めています。
['post', 'page', 'news']
news はカスタム投稿タイプのスラッグです。
ただし、カスタム投稿タイプを検索対象にするには、登録時の設定も確認してください。
'public' => true,
'exclude_from_search' => false,
exclude_from_search が true になっていると、検索対象から除外される場合があります。
PHP検索フォームでよくあるエラー
検索しても結果が表示されない
検索しても結果が表示されない場合は、まずフォームの name とPHP側の受け取り名が一致しているか確認します。
フォーム側が以下の場合、
<input type="search" name="keyword">
PHP側では以下で受け取ります。
$_GET['keyword']
もしフォーム側が、
<input type="search" name="q">
なら、PHP側も以下にする必要があります。
$_GET['q']
フォームの name と $_GET のキーが違うと、検索キーワードを正しく受け取れません。
日本語検索がうまくいかない
日本語検索がうまくいかない場合は、文字コードを確認します。
HTMLでは以下を指定します。
<meta charset="UTF-8">
PDO接続では以下を指定します。
charset=utf8mb4
MySQLのデータベース、テーブル、カラムも utf8mb4 にしておくと安心です。
文字コードがずれていると、日本語が文字化けしたり、検索に一致しなかったりする場合があります。
SQLエラーが出る
SQLエラーが出る場合は、開発環境でPDOのエラーモードを確認します。
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
これを指定しておくと、SQLエラーが例外として扱われます。
ただし、本番環境でエラー詳細をそのまま画面に表示するのは避けましょう。
悪い例です。
catch (PDOException $e) {
echo $e->getMessage();
}
本番環境では、詳細をログに記録し、ユーザーには一般的なエラーメッセージだけを表示します。
catch (PDOException $e) {
error_log($e->getMessage());
exit('データベース接続に失敗しました。');
}
検索キーワードがフォームに残らない
検索後も入力欄にキーワードを残したい場合は、value 属性に検索キーワードを入れます。
<input
type="search"
name="keyword"
value="<?php echo h($keyword); ?>"
>
このとき、必ず htmlspecialchars() でエスケープします。
function h($value) {
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
検索キーワードはユーザー入力なので、そのままHTMLに出力してはいけません。
ページネーションで検索条件が消える
ページネーションリンクを作るときに、検索キーワードをURLに含め忘れると、2ページ目以降で検索条件が消えてしまいます。
悪い例です。
<a href="?page=2">2</a>
この場合、keyword がURLに含まれていません。
正しくは、以下のように検索条件も含めます。
$query = [
'keyword' => $keyword,
'page' => 2,
];
$url = '?' . http_build_query($query);
リンク出力時は、エスケープして表示します。
<a href="<?php echo h($url); ?>">2</a>
PHP検索フォームで意識したいセキュリティ対策
SQLインジェクション対策
SQLインジェクションを防ぐために、ユーザー入力をSQLへ直接連結しないようにします。
避けるべき書き方は以下です。
$sql = "SELECT * FROM articles WHERE title LIKE '%" . $_GET['keyword'] . "%'";
安全に書くには、PDOのプリペアドステートメントを使います。
$stmt = $pdo->prepare(
'SELECT * FROM articles WHERE title LIKE :keyword'
);
$stmt->bindValue(':keyword', '%' . $keyword . '%', PDO::PARAM_STR);
$stmt->execute();
XSS対策
ユーザーが入力した値や、データベースから取得した値をHTMLに出力するときは、htmlspecialchars() を使います。
echo htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
検索フォームでは、以下のような箇所で必要です。
- 入力欄の
value - 検索結果のタイトル
- 検索結果の本文
- エラーメッセージ
- ページネーションリンクのURL
特に、HTML属性に出力するときは忘れずにエスケープしましょう。
入力値のチェック
検索キーワードは、長すぎる文字列が送られる可能性もあります。
そのため、文字数制限を設けると安心です。
if (mb_strlen($keyword, 'UTF-8') > 100) {
$error = '検索キーワードは100文字以内で入力してください。';
}
また、複数キーワード検索では、キーワード数に上限を設けることも重要です。
$keywords = array_slice($keywords, 0, 5);
これにより、極端に長いSQLが生成されるのを防ぎやすくなります。
並び替え条件はホワイトリストで管理する
検索結果を並び替える場合、ユーザー入力をそのまま ORDER BY に使ってはいけません。
悪い例です。
$order = $_GET['order'] ?? 'created_at';
$sql = "SELECT * FROM articles ORDER BY {$order}";
このような書き方は危険です。
安全にするには、許可した並び替え条件だけを使います。
$allowedOrders = [
'new' => 'created_at DESC',
'old' => 'created_at ASC',
'title' => 'title ASC',
];
$orderKey = $_GET['order'] ?? 'new';
$orderSql = $allowedOrders[$orderKey] ?? $allowedOrders['new'];
$sql = "SELECT id, title, body, created_at
FROM articles
ORDER BY {$orderSql}";
プレースホルダで値を渡せるのは、基本的に検索キーワードなどの「値」です。
カラム名やSQL構文そのものをユーザー入力から組み立てる場合は、ホワイトリストで管理する必要があります。
PHP検索フォームの最小構成
まずはこの形をベースにする
PHPとMySQLで検索フォームを作るなら、まずは以下の形をベースにするとよいです。
<?php
require_once 'db.php';
function h($value) {
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function escapeLike($value) {
return str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $value);
}
$keyword = $_GET['keyword'] ?? '';
if (!is_string($keyword)) {
$keyword = '';
}
$keyword = trim($keyword);
$error = '';
$articles = [];
if (mb_strlen($keyword, 'UTF-8') > 100) {
$error = '検索キーワードは100文字以内で入力してください。';
} elseif ($keyword !== '') {
$likeKeyword = '%' . escapeLike($keyword) . '%';
$stmt = $pdo->prepare(
"SELECT id, title, body, created_at
FROM articles
WHERE title LIKE :keyword ESCAPE '\\\\'
OR body LIKE :keyword ESCAPE '\\\\'
ORDER BY created_at DESC"
);
$stmt->bindValue(':keyword', $likeKeyword, PDO::PARAM_STR);
$stmt->execute();
$articles = $stmt->fetchAll();
}
?>
<form action="" method="get">
<label for="keyword">検索キーワード</label>
<input
id="keyword"
type="search"
name="keyword"
value="<?php echo h($keyword); ?>"
placeholder="キーワードを入力"
>
<button type="submit">検索</button>
</form>
<?php if ($error !== ''): ?>
<p><?php echo h($error); ?></p>
<?php elseif ($keyword === ''): ?>
<p>検索キーワードを入力してください。</p>
<?php else: ?>
<h2>「<?php echo h($keyword); ?>」の検索結果</h2>
<?php if (count($articles) > 0): ?>
<?php foreach ($articles as $article): ?>
<article>
<h3><?php echo h($article['title']); ?></h3>
<p><?php echo nl2br(h($article['body'])); ?></p>
<small><?php echo h($article['created_at']); ?></small>
</article>
<?php endforeach; ?>
<?php else: ?>
<p>検索結果が見つかりませんでした。</p>
<?php endif; ?>
<?php endif; ?>
このコードでは、以下の対策を入れています。
$_GET['keyword']が配列だった場合の対策trim()による空白除去- 文字数制限
- PDOのプリペアドステートメント
LIKEのワイルドカードエスケープhtmlspecialchars()によるXSS対策- 検索後も入力欄にキーワードを残す処理
まとめ
PHP検索フォームはHTMLとPHPの連携で作れる
PHPで検索フォームを作る基本は、HTMLフォームでキーワードを送信し、PHPで受け取り、検索対象から一致するデータを取得して表示することです。
最もシンプルなフォームは以下です。
<form action="search.php" method="get">
<input type="search" name="keyword">
<button type="submit">検索</button>
</form>
PHP側では、$_GET でキーワードを受け取ります。
$keyword = $_GET['keyword'] ?? '';
MySQLから検索する場合は、LIKE を使います。
WHERE title LIKE '%キーワード%'
ただし、実務ではユーザー入力をSQLに直接連結してはいけません。
PDOのプリペアドステートメントを使い、安全に値を渡します。
$stmt = $pdo->prepare(
'SELECT * FROM articles WHERE title LIKE :keyword'
);
$stmt->bindValue(':keyword', '%' . $keyword . '%', PDO::PARAM_STR);
$stmt->execute();
また、検索キーワードや検索結果をHTMLに表示するときは、XSS対策として htmlspecialchars() を使います。
echo htmlspecialchars($keyword, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
PHPの検索フォームは、基本だけなら簡単に作れます。
しかし、実務で使うなら以下の点も意識することが大切です。
- SQLインジェクション対策をする
- XSS対策をする
- 検索キーワードの文字数を制限する
- 複数キーワード検索に対応する
- 必要に応じてカテゴリー絞り込みを追加する
- 検索結果が多い場合はページネーションを付ける
- WordPressでは標準検索機能を活用する
まずは基本の検索フォームを作り、その後に複数キーワード検索やページネーションなどを追加していくと、実用的な検索機能を作りやすくなります。
以上、PHPで検索フォームを作る方法についてでした。
最後までお読みいただき、ありがとうございました。










