Связываем фронтенд и бэкенд: HTML, CSS, PHP, MySQL
Основные подходы к интеграции фронтенда и бэкенда с HTML, CSS, PHP и MySQL
Эффективное решение: подготовленные запросы PDO и вывод данных в HTML с CSS
Для безопасного и производительного взаимодействия фронтенда (HTML+CSS) и бэкенда (PHP+MySQL) рекомендуется использовать расширение PDO (PHP Data Objects). Оно предоставляет единый интерфейс для работы с разными базами данных и, что важнее, защищает от SQL-инъекций через подготовленные запросы.
Пример: вывести список пользователей из таблицы users в виде таблицы с CSS-стилями.
Как выполнить выборку из MySQL через PDO и отобразить данные в HTML?
<?php
$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';
$user = 'root';
$pass = '';
try {
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$stmt = $pdo->prepare('SELECT id, name, email FROM users ORDER BY id');
$stmt->execute();
$users = $stmt->fetchAll();
} catch (PDOException $e) {
die('Ошибка подключения: ' . $e->getMessage());
}
?>
<!DOCTYPE html>
<html>
<head>
<style>
.users-table { border-collapse: collapse; width: 80%; margin: 20px auto; }
.users-table th, .users-table td { border: 1px solid #ccc; padding: 8px; text-align: left; }
.users-table th { background-color: #f4f4f4; }
</style>
</head>
<body>
<table class="users-table">
<thead><tr><th>ID</th><th>Имя</th><th>Email</th></tr></thead>
<tbody>
<?php foreach ($users as $row): ?>
<tr>
<td><?= htmlspecialchars($row['id'], ENT_QUOTES, 'UTF-8') ?></td>
<td><?= htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8') ?></td>
<td><?= htmlspecialchars($row['email'], ENT_QUOTES, 'UTF-8') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</body>
</html>Возможные проблемы и их решение:
- Ошибка подключения к MySQL. Проверить имя хоста, базы данных, логин/пароль, а также запущен ли сервер MySQL.
- SQL-инъекции. Использование подготовленных запросов (как в примере) решает эту проблему. Никогда не вставлять пользовательские данные напрямую в строку запроса.
- XSS-уязвимости. При выводе данных в HTML всегда применять
htmlspecialchars()с указанием кодировки.
Как использовать процедурный подход mysqli для интеграции?
Расширение mysqli предлагает как объектно-ориентированный, так и процедурный стиль. Процедурный способ может быть удобен для простых сценариев.
<?php
$link = mysqli_connect('localhost', 'root', '', 'testdb');
if (!$link) {
die('Ошибка соединения: ' . mysqli_connect_error());
}
mysqli_set_charset($link, 'utf8mb4');
$result = mysqli_query($link, 'SELECT * FROM users');
if ($result) {
while ($row = mysqli_fetch_assoc($result)) {
echo 'Имя: ' . htmlspecialchars($row['name']) . '<br>';
}
mysqli_free_result($result);
}
mysqli_close($link);
?>Типичные ошибки:
- Забыли проверить результат
mysqli_connect()- соединение может быть неудачным. - Не установлена кодировка соединения - возможны проблемы с кириллицей. Используйте
mysqli_set_charset($link, 'utf8mb4'). - Процедурный API не поддерживает подготовленные запросы в таком же удобном виде, как PDO, что повышает риск инъекций при невнимательном экранировании.
Как отделить логику от представления с помощью шаблонизатора Twig?
Twig позволяет вынести HTML-шаблоны в отдельные файлы, передавая в них данные из PHP. Это улучшает читаемость кода и упрощает совместную работу фронтендера и бэкендера.
<?php
require_once '/path/to/vendor/autoload.php';
$loader = new \Twig\Loader\FilesystemLoader('/path/to/templates');
$twig = new \Twig\Environment($loader, [
'cache' => '/path/to/compilation_cache',
]);
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'root', '');
$stmt = $pdo->query('SELECT * FROM products');
$products = $stmt->fetchAll();
echo $twig->render('products.html.twig', ['products' => $products]);
?>
<!-- templates/products.html.twig -->
<h2>Товары</h2>
<ul>
{% for product in products %}
<li>{{ product.name|e }} - {{ product.price|number_format(2, ',', ' ') }} руб.</li>
{% else %}
<li>Товаров нет</li>
{% endfor %}
</ul>Проблемы и решения:
- Необходимость установки Twig через Composer. Убедитесь, что путь к автозагрузчику корректен.
- Кеш шаблонов нужно очищать при изменениях во время разработки. Отключите кеш или используйте
auto_reload: true. - Экранирование по умолчанию работает, но для атрибутов HTML может потребоваться дополнительный фильтр
|e('html_attr').
Как подгружать данные без перезагрузки страницы через fetch (AJAX)?
Фронтенд отправляет запрос к PHP-скрипту, который возвращает JSON, и затем JavaScript обновляет DOM.
<!-- PHP-эндпоинт api/comments.php -->
<?php
header('Content-Type: application/json; charset=utf-8');
$pdo = new PDO('mysql:host=localhost;dbname=blog', 'root', '');
$stmt = $pdo->query('SELECT * FROM comments ORDER BY created_at DESC LIMIT 10');
echo json_encode($stmt->fetchAll(), JSON_UNESCAPED_UNICODE);
?>
<!-- HTML+JS -->
<div id="comments-container"></div>
<script>
fetch('api/comments.php')
.then(response => {
if (!response.ok) throw new Error('Ошибка сервера');
return response.json();
})
.then(comments => {
const container = document.getElementById('comments-container');
container.innerHTML = comments.map(c =>
`<p class="comment"><strong>${c.author}</strong>: ${c.text}</p>`
).join('');
})
.catch(error => console.error('Ошибка:', error));
</script>Распространённые ошибки:
- CORS-ограничения, если PHP и HTML находятся на разных доменах. Добавить заголовок
Access-Control-Allow-Origin: *в PHP (с осторожностью). - Неверный Content-Type в ответе - JSON не распарсится. Всегда указывать
header('Content-Type: application/json'). - Отсутствие обработки ошибок в JS приводит к молчаливому падению. Используйте
.catch().
Как создать простое REST API на PHP для обмена JSON?
API позволяет отделить бэкенд от фронтенда полностью, используя стандартные HTTP методы (GET, POST, PUT, DELETE).
<?php
// index.php (единая точка входа)
$method = $_SERVER['REQUEST_METHOD'];
$path = $_SERVER['PATH_INFO'] ?? '/';
if ($path === '/users' && $method === 'GET') {
// Получить список пользователей
$pdo = new PDO('mysql:host=localhost;dbname=api', 'root', '');
$stmt = $pdo->query('SELECT id, name, email FROM users');
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($users, JSON_UNESCAPED_UNICODE);
exit;
}
if ($path === '/users' && $method === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
$name = $input['name'] ?? '';
$email = $input['email'] ?? '';
// валидация, затем INSERT
$stmt = $pdo->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
$stmt->execute([$name, $email]);
http_response_code(201);
echo json_encode(['id' => $pdo->lastInsertId()]);
exit;
}
http_response_code(404);
echo json_encode(['error' => 'Not Found']);
?>Сложности реализации:
- Без маршрутизатора код может стать громоздким. Рекомендуется использовать микрофреймворки (Slim, Lumen).
- Безопасность: каждый эндпоинт должен проверять права доступа (например, через API-ключи или токены).
- Обработка PUT/DELETE: данные могут приходить в теле запроса, как и POST, но метод нужно правильно обрабатывать в серверных настройках.
Расширенные примеры интеграции
Полный CRUD: вывод, добавление, редактирование, удаление через PDO
Создадим мини-приложение для управления списком задач (tasks).
<?php
// config.php
$dsn = 'mysql:host=localhost;dbname=todolist;charset=utf8mb4';
$pdo = new PDO($dsn, 'root', '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
// index.php
require 'config.php';
// Обработка POST (добавление)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($_POST['action'] === 'add') {
$task = trim($_POST['task']);
if ($task !== '') {
$stmt = $pdo->prepare('INSERT INTO tasks (title) VALUES (?)');
$stmt->execute([$task]);
}
header('Location: index.php');
exit;
}
if ($_POST['action'] === 'delete' && isset($_POST['id'])) {
$stmt = $pdo->prepare('DELETE FROM tasks WHERE id = ?');
$stmt->execute([(int)$_POST['id']]);
header('Location: index.php');
exit;
}
}
// Выборка всех задач
$tasks = $pdo->query('SELECT * FROM tasks ORDER BY created_at DESC')->fetchAll();
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 20px auto; }
.task-list { list-style: none; padding: 0; }
.task-item { display: flex; justify-content: space-between; padding: 10px; border-bottom: 1px solid #ddd; }
.task-item form { display: inline; }
.add-form { margin-bottom: 20px; }
.add-form input[type="text"] { padding: 8px; width: 70%; }
.add-form button { padding: 8px 16px; background: #5cb85c; color: white; border: none; cursor: pointer; }
.delete-btn { background: #d9534f; color: white; border: none; padding: 5px 10px; cursor: pointer; }
</style>
<title>Список задач</title>
</head>
<body>
<h2>Мои задачи</h2>
<form class="add-form" method="post">
<input type="hidden" name="action" value="add">
<input type="text" name="task" placeholder="Новая задача..." required>
<button type="submit">Добавить</button>
</form>
<ul class="task-list">
<?php foreach ($tasks as $task): ?>
<li class="task-item">
<span><?= htmlspecialchars($task['title'], ENT_QUOTES, 'UTF-8') ?></span>
<form method="post">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= $task['id'] ?>">
<button type="submit" class="delete-btn">Удалить</button>
</form>
</li>
<?php endforeach; ?>
</ul>
</body>
</html>Результат:
Страница с формой добавления и списком задач, каждая с кнопкой удаления. После отправки формы страница перезагружается с обновлёнными данными.
Динамическая фильтрация через AJAX с передачей параметров
Предположим, нужно отображать товары по категории без перезагрузки всей страницы.
<!-- PHP-эндпоинт get_products.php -->
<?php
header('Content-Type: application/json; charset=utf-8');
$category = isset($_GET['category']) ? $_GET['category'] : '';
if (!$category) {
echo json_encode(['error' => 'Категория не указана']);
exit;
}
$pdo = new PDO('mysql:host=localhost;dbname=shop', 'root', '');
$stmt = $pdo->prepare('SELECT id, name, price FROM products WHERE category = ?');
$stmt->execute([$category]);
echo json_encode($stmt->fetchAll(), JSON_UNESCAPED_UNICODE);
?>
<!-- HTML+JS -->
<select id="category-select">
<option value="">Выберите категорию</option>
<option value="electronics">Электроника</option>
<option value="clothing">Одежда</option>
</select>
<div id="product-list"></div>
<script>
document.getElementById('category-select').addEventListener('change', function() {
const category = this.value;
if (!category) {
document.getElementById('product-list').innerHTML = '';
return;
}
fetch(`get_products.php?category=${encodeURIComponent(category)}`)
.then(res => res.json())
.then(products => {
if (products.error) {
document.getElementById('product-list').innerHTML = `<p>${products.error}</p>`;
return;
}
const html = products.map(p => `<div class="product">
<h3>${p.name}</h3>
<p>${p.price} руб.</p>
</div>`).join('');
document.getElementById('product-list').innerHTML = html;
})
.catch(err => console.error(err));
});
</script>Результат:
При выборе категории из выпадающего списка в блоке #product-list появляется перечень товаров без перезагрузки страницы. Данные приходят в формате JSON.