Недавно решил, что на моём техническом блоге не хватает поиска. Казалось бы — дело пяти минут: вставил форму, написал пару строк SQL — и готово. Но через пару часов понял: половина решений в интернете — это дыры в безопасности размером с грузовик.
Разобрался, как сделать всё правильно — и делюсь кейсом.
Нужно добавить поиск по:
При этом:
Самое простое — обычная GET-форма:
<form method="GET" action="/">
<input type="text" name="q" value="<?= htmlspecialchars($_GET['q'] ?? '') ?>" placeholder="Поиск по блогу...">
<button type="submit">🔍</button>
</form>
Обрати внимание: htmlspecialchars()
при выводе значения — это первая линия защиты от XSS.
Многие пишут так:
// НИКОГДА НЕ ДЕЛАЙ ТАК!
$query = "SELECT * FROM articles WHERE title LIKE '%" . $_GET['q'] . "%'";
Это прямой путь к SQL-инъекции. Вместо этого — используем подготовленные выражения (Prepared Statements) через PDO:
$search_query = trim($_GET['q'] ?? '');
$is_search = !empty($search_query);
if ($is_search) {
$search_sql = " AND (a.title LIKE :search OR a.preview_text LIKE :search OR a.content LIKE :search)";
} else {
$search_sql = "";
}
$stmt = $pdo->prepare("SELECT a.id, a.title, a.preview_text, a.image, a.created_at, c.name as category
FROM articles a
LEFT JOIN categories c ON a.category_id = c.id
WHERE 1=1 $search_sql
ORDER BY a.created_at DESC
LIMIT :limit OFFSET :offset");
$stmt->bindValue(':limit', $articles_per_page, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
if ($is_search) {
$stmt->bindValue(':search', '%' . $search_query . '%', PDO::PARAM_STR);
}
$stmt->execute();
Ключевые моменты:
bindValue(':search', ... , PDO::PARAM_STR)
— автоматически экранирует строку,$_GET
— только параметры,' OR '1'='1
— он попадёт в строку как текст, а не как SQL-код.Даже если данные из БД — свои, при выводе результатов поиска всегда используй htmlspecialchars()
:
<h3><a href="article.php?id=<?= $row['id'] ?>">
<?= htmlspecialchars($row['title']) ?>
</a></h3>
Это не даст внедрить <script>
через заголовок статьи (например, если кто-то когда-то внесёт вредоносные данные в БД).
Важно: в таблице code_posts
у меня нет поля content
, только intro
. Поэтому для неё условие другое:
$search_sql_codes = " AND (cp.title LIKE :search OR cp.intro LIKE :search)";
Если бы я не проверил структуру через DESCRIBE code_posts
— получил бы ошибку SQL и пустую страницу.
Теперь у блога есть рабочий, безопасный поиск:
И да — никаких фреймворков, Elasticsearch и JavaScript. Только чистый PHP, здравый смысл и внимание к деталям.
P.S. Блог всё ещё в режиме эксперимента, но этот кусок кода уже работает в продакшене — проверено на реальных запросах и попытках «пощупать» форму.
Блог только запустил, все статьи генерирую через нейросеть т.к. лень, возможны ошибки. Просто чтобы вы знали и не запускали ядерный реактор по моим статьям ))
Если у вас есть вопросы, или Нашли неточность? пишите в коментах — вместе поправим и сделаем статью более качественной. Я лично объясню нюансы из практики.
Комментарии
Пока нет комментариев. Будьте первым!