Категории

Как я добавил безопасный поиск в свой блог на чистом PHP (и не словил SQL-инъекцию)

10.10.2025 15:07 | коды из категории: создание сайтов

Как я добавил безопасный поиск в свой блог на чистом PHP (и не словил SQL-инъекцию)

Недавно решил, что на моём техническом блоге не хватает поиска. Казалось бы — дело пяти минут: вставил форму, написал пару строк SQL — и готово. Но через пару часов понял: половина решений в интернете — это дыры в безопасности размером с грузовик.

Разобрался, как сделать всё правильно — и делюсь кейсом.

Задача

Нужно добавить поиск по:

При этом:

Шаг 1: Форма поиска

Самое простое — обычная GET-форма:

<form method="GET" action="/">
  <input type="text" name="q" value="<?= htmlspecialchars($_GET['q'] ?? '') ?>" placeholder="Поиск по блогу...">
  <button type="submit">🔍</button>
</form>

Обрати внимание: htmlspecialchars() при выводе значения — это первая линия защиты от XSS.

Шаг 2: Безопасный SQL-запрос

Многие пишут так:

// НИКОГДА НЕ ДЕЛАЙ ТАК!
$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();

Ключевые моменты:

Шаг 3: Защита от XSS при выводе

Даже если данные из БД — свои, при выводе результатов поиска всегда используй htmlspecialchars():

<h3><a href="article.php?id=<?= $row['id'] ?>">
  <?= htmlspecialchars($row['title']) ?>
</a></h3>

Это не даст внедрить <script> через заголовок статьи (например, если кто-то когда-то внесёт вредоносные данные в БД).

Шаг 4: Учёт особенностей таблиц

Важно: в таблице 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. Блог всё ещё в режиме эксперимента, но этот кусок кода уже работает в продакшене — проверено на реальных запросах и попытках «пощупать» форму.

Теги: #PHP #Безопасность #MySQL #pdo #блог #html #backend #защита #поиск #sql_injection #xss #подготовленные_запросы #формы #get

Комментарии

Пока нет комментариев. Будьте первым!

Оставить комментарий

← Назад к списку

Важно: Блог-эксперимент

Блог только запустил, все статьи генерирую через нейросеть т.к. лень, возможны ошибки. Просто чтобы вы знали и не запускали ядерный реактор по моим статьям ))
Если у вас есть вопросы, или Нашли неточность? пишите в коментах — вместе поправим и сделаем статью более качественной. Я лично объясню нюансы из практики.

Посетителей сегодня: 0


Кто я | Контакты и регион

© Digital Specialist | Не являемся сотрудниками Google, Яндекса и NASA