Просмотр одного товара через 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.json

1. 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>

Такой подход позволяет делегировать форматирование представлению, оставляя в контроллере только бизнес-логику.

Просмотр товара PHP - comments

En
Product view php (php)