Создание страницы каталога с детальным описанием на PHP

Раздел: Разработка каталогов на PHP -> Отображение каталога

Реализация детальной страницы каталога на PHP: обзор подходов

Как организовать вывод детальной страницы товара без дублирования кода?

Наиболее эффективное решение строится на принципе MVC (Model-View-Controller). Код разделяется на три слоя: модель отвечает за работу с данными, контроллер обрабатывает запрос и передаёт данные в представление, а представление отвечает за визуализацию. Такой подход обеспечивает переиспользование кода и лёгкую поддержку.

Пример структуры файлов:

/catalog
  /controllers
    ProductController.php
  /models
    Product.php
  /views
    product.php
  index.php (роутер)

Catalog detailed php (детальная страница каталога php)

Шаг 1. Роутер (index.php)

require_once 'controllers/ProductController.php';

$id = (int)($_GET['id'] ?? 0);
$controller = new ProductController();
$controller->show($id);

Шаг 2. Модель (Product.php)

class Product {
    private PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }

    public function getById(int $id): ?array {
        $stmt = $this->pdo->prepare('SELECT * FROM products WHERE id = ?');
        $stmt->execute([$id]);
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
    }
}

Шаг 3. Контроллер (ProductController.php)

class ProductController {
    public function show(int $id): void {
        $pdo = new PDO('mysql:host=localhost;dbname=catalog', 'user', 'pass');
        $model = new Product($pdo);
        $product = $model->getById($id);

        if (!$product) {
            http_response_code(404);
            require 'views/404.php';
            return;
        }

        require 'views/product.php';
    }
}

Шаг 4. Представление (views/product.php)

<h1><?= htmlspecialchars($product['name']) ?></h1>
<p><?= nl2br(htmlspecialchars($product['description'])) ?></p>
<span class="price"><?= number_format($product['price'], 2) ?> руб.</span>

Типичные проблемы:

  • SQL-инъекции при конкатенации id в запрос (решение – использование подготовленных выражений).
  • XSS-уязвимости при выводе данных без экранирования (использовать htmlspecialchars).
  • Отсутствие обработки 404 (проверять существование товара).

Как быстро вывести детальную страницу, если нет фреймворка?

Самый простой вариант – вся логика в одном файле. Подходит для прототипов или очень маленьких проектов.

$id = (int)($_GET['id'] ?? 0);
$pdo = new PDO('mysql:host=localhost;dbname=catalog', 'user', 'pass');
$stmt = $pdo->prepare('SELECT * FROM products WHERE id = ?');
$stmt->execute([$id]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$product) {
    http_response_code(404);
    echo 'Товар не найден';
    exit;
}
?>
<h1><?= htmlspecialchars($product['name']) ?></h1>
...

Недостатки:

  • Смешивание логики и представления.
  • Сложность поддержки при росте проекта.

Как реализовать детальную страницу в Laravel?

Laravel предоставляет готовый маршрут, контроллер, Eloquent ORM и Blade шаблоны.

// routes/web.php
Route::get('/product/{id}', [ProductController::class, 'show']);

// app/Http/Controllers/ProductController.php
public function show($id) {
    $product = Product::findOrFail($id);
    return view('product', compact('product'));
}

// resources/views/product.blade.php
<h1>{{ $product->name }}</h1>
<p>{{ $product->description }}</p>

Ошибки:

  • Использование find($id) без findOrFail – при отсутствии записи вернёт null, а не 404.
  • Отсутствие валидации id (Laravel сам преобразует в int, но можно добавить Rule).

Как сделать детальную страницу с подгрузкой через AJAX?

Используется REST API, возвращающий JSON. Клиентская часть получает данные и рендерит HTML.

// api.php (роут для API)
Route::get('/api/product/{id}', function ($id) {
    $product = Product::find($id);
    if (!$product) {
        return response()->json(['error' => 'Not found'], 404);
    }
    return response()->json($product);
});

// JavaScript (fetch)
fetch('/api/product/123')
  .then(response => response.json())
  .then(product => {
    document.getElementById('name').textContent = product.name;
    document.getElementById('price').textContent = product.price;
  });

Проблемы:

  • SEO – содержимое не индексируется поисковиками. Решение: SSR или prerender.
  • CSRF защита для API (если используются сессии).

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

Пример 1: Полный контроллер с валидацией и логированием

Пример
class ProductController {
    private Product $model;
    private Logger $logger;

    public function __construct(Product $model, Logger $logger) {
        $this->model = $model;
        $this->logger = $logger;
    }

    public function show(int $id): void {
        // Валидация id
        if ($id <= 0) {
            $this->logger->warning('Invalid product ID: ' . $id);
            http_response_code(400);
            require 'views/400.php';
            return;
        }

        try {
            $product = $this->model->getById($id);
            if (!$product) {
                $this->logger->info('Product not found: ' . $id);
                http_response_code(404);
                require 'views/404.php';
                return;
            }

            // Генерация мета-тегов
            $metaTitle = htmlspecialchars($product['name']) . ' - Каталог';
            $metaDescription = mb_substr(strip_tags($product['description']), 0, 160);

            require 'views/product.php';
        } catch (\Exception $e) {
            $this->logger->error('Database error: ' . $e->getMessage());
            http_response_code(500);
            require 'views/500.php';
        }
    }
}
Результат: страница с корректным HTTP-статусом, мета-тегами и логированием ошибок.

Пример 2: Использование шаблонизатора Twig

Пример
// composer require twig/twig

$loader = new \Twig\Loader\FilesystemLoader('templates');
$twig = new \Twig\Environment($loader, ['cache' => 'cache']);

echo $twig->render('product.html.twig', [
    'product' => $product,
    'breadcrumbs' => [
        ['url' => '/catalog', 'title' => 'Каталог'],
        ['url' => '/catalog/category/' . $product['category_id'], 'title' => $product['category_name']],
        ['title' => $product['name']]
    ]
]);
В шаблоне product.html.twig: хлебные крошки, мета-теги, микроразметка Schema.org.

Пример 3: Кэширование результата

Пример
$cacheKey = 'product_' . $id;
$cached = apcu_fetch($cacheKey);
if ($cached !== false) {
    echo $cached;
    return;
}

ob_start();
// ... отрисовка страницы
$html = ob_get_clean();
apcu_store($cacheKey, $html, 3600);
echo $html;
Сокращение времени загрузки страницы при повторных запросах. Требуется APCu или Redis.

Пример 4: Обработка SEO-дублей с помощью canonical

Пример
$canonical = 'https://example.com/product/' . $product['id'];
$requestUri = $_SERVER['REQUEST_URI'];
if (parse_url($requestUri, PHP_URL_PATH) !== '/product/' . $product['id']) {
    // Перенаправление на канонический URL
    header('Location: ' . $canonical, true, 301);
    exit;
}
Предотвращает дублирование страниц с разными параметрами (например, /product/123?ref=utm).

Детальная страница каталога PHP - comments

En
Catalog detailed php (php)