Страница просмотра с поиском реализация на PHP

Раздел: Веб-разработка -> Пользовательский интерфейс

Реализация просмотра и поиска на PHP

Основное эффективное решение для страницы просмотра одной записи с функцией поиска других записей использует безопасный подход с подготовленными запросами PDO и разделение логики. Это позволяет избежать SQL-инъекций и корректно обрабатывать пользовательский ввод.


// Файл view.php
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($id === false || $id === null) {
    die('Неверный идентификатор');
}

try {
    $pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'user', 'pass');
    $stmt = $pdo->prepare('SELECT * FROM articles WHERE id = :id');
    $stmt->execute(['id' => $id]);
    $article = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$article) {
        echo 'Статья не найдена';
        exit;
    }
} catch (PDOException $e) {
    echo 'Ошибка базы данных';
    exit;
}

// Вывод деталей статьи
?>
<h3><?= htmlspecialchars($article['title']) ?></h3>
<p><?= nl2br(htmlspecialchars($article['content'])) ?></p>

// Форма поиска (GET запрос на этот же скрипт)
<form method="get">
    <input type="text" name="search" placeholder="Поиск статей..." value="<?= htmlspecialchars($_GET['search'] ?? '') ?>">
    <button type="submit">Искать</button>
</form>

<?php
$search = trim($_GET['search'] ?? '');
if ($search !== '') {
    $stmt = $pdo->prepare('SELECT id, title FROM articles WHERE title LIKE :search LIMIT 10');
    $stmt->execute(['search' => '%' . $search . '%']);
    $results = $stmt->fetchAll(PDO::FETCH_ASSOC);
    if ($results) {
        echo '<ul>';
        foreach ($results as $row) {
            echo '<li><a href="?id=' . $row['id'] . '">' . htmlspecialchars($row['title']) . '</a></li>';
        }
        echo '</ul>';
    } else {
        echo 'Ничего не найдено';
    }
}
?>
  

Пояснение шагов:

  • Фильтрация входного параметра id через filter_input с проверкой на целое число.
  • Подключение к базе данных через PDO с использованием исключений.
  • Подготовленный запрос для выборки статьи по id.
  • Экранирование вывода через htmlspecialchars для предотвращения XSS.
  • Реализация поиска с параметром search через LIKE с подстановочными знаками.

Типичные ошибки и их решения

Ошибка 1: Прямая вставка переменной в SQL (например, "SELECT * FROM articles WHERE id = $id") приводит к SQL-инъекции. Решение: всегда использовать подготовленные запросы.

Ошибка 2: Отсутствие проверки существования записи. Если id не найден, код выдаст исключение или пустой результат. Решение: выполнить проверку if (!$article).

Ошибка 3: Необработанные символы в выводе (например, HTML-теги в заголовке). Решение: применять htmlspecialchars ко всем выводимым данным.

Вариант 1: Как защитить приложение от SQL-инъекций с помощью PDO?

Использование PDO с подготовленными запросами является стандартом безопасности. Пример для редактирования записи:


$stmt = $pdo->prepare('UPDATE articles SET title = :title WHERE id = :id');
$stmt->execute(['title' => $_POST['title'], 'id' => $id]);
  

Проблема: Если забыть передать все плейсхолдеры, запрос завершится ошибкой. Решение: проверять соответствие плейсхолдеров и ключей массива.

Вариант 2: Как организовать просмотр записи с помощью mysqli?

Mysqli также поддерживает подготовленные запросы:


$mysqli = new mysqli('localhost', 'user', 'pass', 'test');
$stmt = $mysqli->prepare('SELECT * FROM articles WHERE id = ?');
$stmt->bind_param('i', $id);
$stmt->execute();
$result = $stmt->get_result();
$article = $result->fetch_assoc();
  

Проблема: Неправильное указание типа параметра (i, s, d) может привести к ошибкам. Решение: строго соответствовать типу данных.

Вариант 3: Как реализовать интерактивный поиск без перезагрузки страницы?

Использование AJAX с отдельным скриптом search.php:


// JavaScript (fetch)
document.getElementById('search-form').addEventListener('submit', async function(e) {
    e.preventDefault();
    const query = document.querySelector('input[name="search"]').value;
    const response = await fetch('search.php?q=' + encodeURIComponent(query));
    const data = await response.text();
    document.getElementById('results').innerHTML = data;
});

// search.php
$q = $_GET['q'] ?? '';
if ($q) {
    $stmt = $pdo->prepare('SELECT id, title FROM articles WHERE title LIKE :q LIMIT 10');
    $stmt->execute(['q' => '%' . $q . '%']);
    while ($row = $stmt->fetch()) {
        echo '<div><a href="?id=' . $row['id'] . '">' . htmlspecialchars($row['title']) . '</a></div>';
    }
}
  

Проблема: XSS при вставке результатов без экранирования. Решение: всегда использовать htmlspecialchars.

Вариант 4: Как сделать быстрый просмотр без защиты (только для обучения)?

Простой конкатенация строк (опасно, не рекомендуется для продакшена):


$id = $_GET['id'];
$query = "SELECT * FROM articles WHERE id = $id";
$result = mysqli_query($conn, $query);
$article = mysqli_fetch_assoc($result);
  

Проблема: Полная уязвимость для SQL-инъекций. Никогда не использовать в реальных проектах.

Расширенные примеры реализации просмотра и поиска

Пример 1: Полная страница view.php с пагинацией поиска

Пример

// view.php - просмотр одной статьи и поиск с пагинацией
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
$search = trim($_GET['search'] ?? '');
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = 5;

try {
    $pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'user', 'pass', [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]);
    
    // Получение статьи
    if ($id) {
        $stmt = $pdo->prepare('SELECT * FROM articles WHERE id = :id');
        $stmt->execute(['id' => $id]);
        $article = $stmt->fetch();
        if ($article) {
            echo '<h3>' . htmlspecialchars($article['title']) . '</h3>';
            echo '<div>' . nl2br(htmlspecialchars($article['content'])) . '</div>';
        }
    }
    
    // Поиск с пагинацией
    if ($search) {
        $offset = ($page - 1) * $perPage;
        $countStmt = $pdo->prepare('SELECT COUNT(*) FROM articles WHERE title LIKE :search');
        $countStmt->execute(['search' => '%' . $search . '%']);
        $total = $countStmt->fetchColumn();
        $totalPages = ceil($total / $perPage);
        
        $stmt = $pdo->prepare('SELECT id, title, created_at FROM articles WHERE title LIKE :search ORDER BY created_at DESC LIMIT :limit OFFSET :offset');
        $stmt->bindValue('search', '%' . $search . '%', PDO::PARAM_STR);
        $stmt->bindValue('limit', $perPage, PDO::PARAM_INT);
        $stmt->bindValue('offset', $offset, PDO::PARAM_INT);
        $stmt->execute();
        $results = $stmt->fetchAll();
        
        if ($results) {
            echo '<ul>';
            foreach ($results as $row) {
                echo '<li><a href="?id=' . $row['id'] . '&search=' . urlencode($search) . '">' . htmlspecialchars($row['title']) . '</a> (' . $row['created_at'] . ')</li>';
            }
            echo '</ul>';
            // пагинация
            if ($totalPages > 1) {
                for ($p = 1; $p <= $totalPages; $p++) {
                    $active = ($p == $page) ? ' class="fw-bold"' : '';
                    echo '<a href="?search=' . urlencode($search) . '&page=' . $p . '"' . $active . '>' . $p . '</a> ';
                }
            }
        } else {
            echo 'Результатов не найдено';
        }
    }
} catch (PDOException $e) {
    echo 'Ошибка: ' . $e->getMessage();
}
  

Результат выполнения (пример вывода):

<h3>Заголовок статьи 1</h3>
<div>Текст статьи...</div>
<ul>
  <li><a href="?id=5&search=php">Статья о PHP</a> (2024-03-01)</li>
  <li><a href="?id=8&search=php">PHP для начинающих</a> (2024-02-20)</li>
</ul>
<a href="?search=php&page=1" class="fw-bold">1</a> <a href="?search=php&page=2">2</a>
  

Пример 2: Обработка ошибок при отсутствии записи

Пример

$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if (!$id) {
    http_response_code(400);
    echo 'Неверный или отсутствующий идентификатор';
    exit;
}
$stmt = $pdo->prepare('SELECT * FROM articles WHERE id = :id');
$stmt->execute(['id' => $id]);
if ($stmt->rowCount() === 0) {
    http_response_code(404);
    echo 'Запись не найдена';
    exit;
}
$article = $stmt->fetch();
  

Пример 3: Использование полнотекстового поиска (MySQL FULLTEXT)

Пример

// Предварительно создать FULLTEXT индекс: ALTER TABLE articles ADD FULLTEXT(title, content);
$search = trim($_GET['search'] ?? '');
if ($search) {
    $stmt = $pdo->prepare('SELECT id, title, MATCH(title, content) AGAINST(:search IN BOOLEAN MODE) AS relevance FROM articles WHERE MATCH(title, content) AGAINST(:search2 IN BOOLEAN MODE) ORDER BY relevance DESC LIMIT 10');
    $stmt->execute(['search' => $search, 'search2' => $search]);
    // вывод результатов
}
  

Результат (пример):

id: 1, title: "Основы PHP", relevance: 2.5
id: 4, title: "PHP и MySQL", relevance: 1.8
  

Пример 4: Защита от XSS при выводе результатов поиска

Пример

function escape($data) {
    return htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
}
// ...
echo '<a href="?id=' . $row['id'] . '">' . escape($row['title']) . '</a>';
  

Результат (безопасный вывод):

<a href="?id=2">Статья с &quot;кавычками&quot;</a>
  

Просмотр и поиск - comments

En
Index 1 view php (php)