Просмотр одного товара через PHP: код и инструкции
Реализация просмотра товара в PHP
Наиболее эффективное решение для отображения товара в интернет-магазине строится на связке PDO (безопасная работа с БД), шаблонизатора Twig (разделение логики и представления) и обработки ошибок с возвратом корректного HTTP-статуса. Этот подход обеспечивает защиту от SQL-инъекций, гибкость вёрстки и правильную индексацию страниц поисковыми системами.
// config/db.php
$host = 'localhost';
$dbname = 'shop';
$user = 'root';
$pass = '';
$dsn = "mysql:host=$host;dbname=$dbname;charset=utf8mb4";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
http_response_code(500);
die('Ошибка соединения с БД');
}// product.php
require __DIR__ . '/config/db.php';
$id = (int)($_GET['id'] ?? 0);
if ($id <= 0) {
http_response_code(400);
die('Неверный идентификатор товара');
}
$stmt = $pdo->prepare('SELECT * FROM products WHERE id = :id LIMIT 1');
$stmt->execute(['id' => $id]);
$product = $stmt->fetch();
if (!$product) {
http_response_code(404);
die('Товар не найден');
}
// Далее рендеринг шаблонаШаблон (Twig) подключается через require_once и отображает данные товара. Кэширование шаблонов ускоряет вывод. Основные плюсы: безопасность, читаемость, поддержка.
Как реализовать просмотр товара без шаблонизатора, используя только PHP и HTML?
Для небольших проектов допустимо смешивать PHP и HTML напрямую.
<?php
require 'config/db.php';
$id = (int)($_GET['id'] ?? 0);
$product = $pdo->prepare('SELECT * FROM products WHERE id = ?');
$product->execute([$id]);
$data = $product->fetch();
if (!$data) { http_response_code(404); exit('Товар не найден'); }
?>
<!DOCTYPE html>
<html><head><title><?= htmlspecialchars($data['name']) ?></title></head>
<body>
<h2><?= htmlspecialchars($data['name']) ?></h2>
<p><?= nl2br(htmlspecialchars($data['description'])) ?></p>
<span class="fw-bold">Цена: <?= number_format($data['price'], 2) ?> руб.</span>
</body></html>Проблемы и ошибки:
- Отсутствие экранирования вывода приводит к XSS-уязвимостям. Всегда используйте htmlspecialchars.
- Повторение HTML-кода на каждой странице усложняет поддержку.
- Некорректная обработка ошибок (например, не возвращается 404 при отсутствии товара).
Как добавить кэширование результатов запроса к базе данных для ускорения работы?
Используйте Redis или Memcached для хранения данных товара по ключу product:{id}. Это снижает нагрузку на базу данных.
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$cacheKey = "product:$id";
$product = $redis->get($cacheKey);
if (!$product) {
$product = $pdo->query(/* ... */)->fetch();
$redis->setex($cacheKey, 3600, serialize($product));
} else {
$product = unserialize($product);
}Возможные сложности:
- При обновлении товара нужно инвалидировать кэш.
- Сериализация/десериализация может замедлить работу при больших объёмах.
- Необходимо обрабатывать отказы Redis (fallback на прямое чтение из БД).
Как организовать ЧПУ (человекопонятные URL) для страниц товара?
Используйте Apache mod_rewrite или Nginx, перенаправляя запросы вида /product/123 на product.php?id=123.
# .htaccess
RewriteEngine On
RewriteRule ^product/([0-9]+)$ product.php?id=$1 [L,QSA]В PHP получайте id из пути через $_SERVER['REQUEST_URI'] или разбирайте в точке входа (роутер).
Типичные ошибки:
- Правило .htaccess конфликтует с другими правилами.
- Не учитываются параметры запроса (QSA).
- Не обрабатывается случай, когда товара с таким ID не существует (возвращается 200 вместо 404).
Как использовать ORM (например, Eloquent без Laravel) для получения товара?
Подключите Eloquent через Composer и работайте с моделями.
use Illuminate\Database\Capsule\Manager as Capsule;
$capsule = new Capsule;
$capsule->addConnection(/* ... */);
$capsule->bootEloquent();
$product = Product::find($id);
if (!$product) { abort(404); }Этот подход удобен для крупных проектов, но требует установки дополнительных зависимостей.
Недостатки:
- Излишняя сложность для простого магазина.
- Тормозит на больших таблицах без правильной индексации.
- Затруднён контроль над SQL-запросами.
Расширенные примеры реализации просмотра товара
Приведём полный рабочий пример с PDO, Twig, обработкой ошибок и поддержкой ЧПУ.
Структура файлов:
/shop
/config
db.php
/templates
product.twig
/public
index.php (единая точка входа)
.htaccess
composer.json1. composer.json
{
"require": {
"twig/twig": "^3.0",
"ext-pdo": "*"
}
}2. config/db.php
<?php
return [
'dsn' => 'mysql:host=localhost;dbname=shop;charset=utf8mb4',
'user' => 'root',
'pass' => '',
];3. public/index.php (точка входа с маршрутизацией)
<?php
require_once __DIR__ . '/../vendor/autoload.php';
$config = require __DIR__ . '/../config/db.php';
// Подключение к БД
try {
$pdo = new PDO($config['dsn'], $config['user'], $config['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Database error']);
exit;
}
// Twig
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../templates');
$twig = new \Twig\Environment($loader, ['cache' => __DIR__ . '/../cache', 'auto_reload' => true]);
// Разбираем URL
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$parts = explode('/', trim($uri, '/'));
if ($parts[0] === 'product' && isset($parts[1]) && ctype_digit($parts[1])) {
$id = (int)$parts[1];
$stmt = $pdo->prepare('SELECT * FROM products WHERE id = :id LIMIT 1');
$stmt->execute(['id' => $id]);
$product = $stmt->fetch();
if (!$product) {
http_response_code(404);
echo $twig->render('404.twig', ['message' => 'Товар не найден']);
exit;
}
// Защита вывода
$product['name'] = htmlspecialchars($product['name'], ENT_QUOTES, 'UTF-8');
$product['description'] = nl2br(htmlspecialchars($product['description'], ENT_QUOTES, 'UTF-8'));
$product['price'] = number_format($product['price'], 2, ',', ' ');
echo $twig->render('product.twig', ['product' => $product]);
} else {
http_response_code(404);
echo $twig->render('404.twig', ['message' => 'Страница не найдена']);
}4. templates/product.twig
<!DOCTYPE html>
<html>
<head>
<title>{{ product.name }} — магазин</title>
</head>
<body>
<h2>{{ product.name }}</h2>
<div class="description">{{ product.description|raw }}</div>
<p class="price fw-bold">{{ product.price }} руб.</p>
<a href="/catalog">Назад</a>
</body>
</html>5. public/.htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]Результат при обращении к /product/5 (товар существует):
<!DOCTYPE html>
<html>
<head>
<title>Ноутбук ASUS — магазин</title>
</head>
<body>
<h2>Ноутбук ASUS</h2>
<div class="description">Мощный ноутбук для работы и игр.<br>
Скидка до 15%.</div>
<p class="price fw-bold">89 990,00 руб.</p>
<a href="/catalog">Назад</a>
</body>
</html>Дополнительный пример: кэширование данных с инвалидацией при обновлении товара через админ-панель.
// В админке после сохранения товара
$productId = $_POST['id'];
// ... обновление БД ...
$redis = new Redis();
$redis->connect('127.0.0.1');
$redis->del("product:$productId"); // очищаем кэшПример с использованием фильтров Twig для форматирования цены (без PHP):
// В шаблоне
<p class="fw-bold">{{ product.price|number_format(2, ',', ' ') }} руб.</p>Такой подход позволяет делегировать форматирование представлению, оставляя в контроллере только бизнес-логику.