Товары в PHP-магазине: от базы данных до интерфейса

Раздел: Веб-разработка на PHP -> Управление товарами

Основные подходы к управлению товарами на PHP

Наиболее эффективное решение: объектно-ориентированный подход с PDO

Для безопасной и гибкой работы с товарами рекомендуется использовать класс Product, инкапсулирующий логику CRUD через PDO. Это защищает от SQL-инъекций и упрощает поддержку.

class Product {
    private $pdo;
    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
    public function getAll() {
        $stmt = $this->pdo->query('SELECT * FROM products ORDER BY id DESC');
        return $stmt->fetchAll();
    }
    public function getById($id) {
        $stmt = $this->pdo->prepare('SELECT * FROM products WHERE id = ?');
        $stmt->execute([$id]);
        return $stmt->fetch();
    }
    public function create($data) {
        $sql = 'INSERT INTO products (name, price, description) VALUES (?, ?, ?)';
        $stmt = $this->pdo->prepare($sql);
        return $stmt->execute([$data['name'], $data['price'], $data['description']]);
    }
    public function update($id, $data) {
        $sql = 'UPDATE products SET name=?, price=?, description=? WHERE id=?';
        $stmt = $this->pdo->prepare($sql);
        return $stmt->execute([$data['name'], $data['price'], $data['description'], $id]);
    }
    public function delete($id) {
        $stmt = $this->pdo->prepare('DELETE FROM products WHERE id = ?');
        return $stmt->execute([$id]);
    }
}

Shop product php (товары в интернет-магазине php)

Пояснение: каждый метод использует подготовленные выражения (placeholder ?), что исключает внедрение вредоносного кода. Конструктор принимает объект PDO, что позволяет легко менять настройки базы данных.

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

  • Ошибка "Undefined index" при обращении к $_POST - необходимо проверять существование ключей через isset() или фильтровать входящие данные.
  • Ошибка "SQLSTATE[23000]" при нарушении уникальности - обрабатывать исключения и возвращать пользователю понятное сообщение.
  • Проблемы с кодировкой (кракозябры) - устанавливать кодировку подключения: $pdo->exec('SET NAMES utf8').

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

Процедурный подход быстрее для небольших проектов. Функции и глобальные переменные могут упростить начальную разработку, но усложняют тестирование.

function getProducts($mysqli) {
    $result = $mysqli->query('SELECT * FROM products');
    while ($row = $result->fetch_assoc()) {
        $products[] = $row;
    }
    return $products ?? [];
}

Проблема: прямое использование query() без экранирования приводит к SQL-инъекциям. Решение - всегда применять prepare() с привязкой параметров.

Типичная ошибка: забыть освободить ресурсы после запроса.

Как ускорить разработку с помощью Laravel?

Laravel предоставляет Eloquent ORM, миграции и валидацию. Модель Product может выглядеть так:

class Product extends Model {
    protected $fillable = ['name', 'price', 'description'];
}

Контроллер использует Product::all(), Product::find(), Product::create(). Встроенная защита от массового присвоения.

Проблема: высокая сложность для простых сайтов - требуется установка composer, настройка artisan.

Как хранить товары в JSON-файле без базы данных?

Для прототипов или ultra-лёгких решений подходит файловое хранение в JSON.

function loadProducts() {
    $json = file_get_contents('products.json');
    return json_decode($json, true) ?? [];
}
function saveProducts($products) {
    file_put_contents('products.json', json_encode($products, JSON_PRETTY_PRINT));
}

Операции CRUD выполняются над массивом в памяти.

Ошибка: одновременные запросы приводят к повреждению файла. Используйте блокировку flock().

Как обеспечить замену источника данных через репозиторий?

Паттерн Repository абстрагирует хранилище. Интерфейс ProductRepositoryInterface и реализации для MySQL, JSON, API.

interface ProductRepositoryInterface {
    public function find($id);
    public function findAll();
    public function save($product);
    public function delete($id);
}

Код, использующий репозиторий, не зависит от конкретной реализации.

Типичная ошибка: добавление зависимостей от конкретных классов в коде, что нарушает принцип замены.

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

Пример 1: Добавление товара с валидацией и загрузкой изображения

Страница add_product.php содержит форму и скрипт обработки. Код проверяет обязательные поля, загружает файл и вставляет запись в БД.

Пример
<?php
require_once 'Product.php';
$pdo = new PDO('mysql:host=localhost;dbname=shop','root','');
$product = new Product($pdo);
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = trim($_POST['name']);
    $price = floatval($_POST['price']);
    $description = trim($_POST['description']);
    if (empty($name)) $errors[] = 'Название обязательно';
    if ($price <= 0) $errors[] = 'Цена должна быть больше 0';
    // загрузка изображения
    $image = '';
    if (!empty($_FILES['image']['name'])) {
        $ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
        $newName = uniqid() . '.' . $ext;
        move_uploaded_file($_FILES['image']['tmp_name'], 'uploads/' . $newName);
        $image = $newName;
    }
    if (empty($errors)) {
        $product->create([
            'name' => $name,
            'price' => $price,
            'description' => $description
        ]);
        // можно сохранить имя изображения в отдельном поле
        echo '<p>Товар добавлен успешно</p>';
    } else {
        echo '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>';
    }
}
?>
<form method="post" enctype="multipart/form-data">
    <input type="text" name="name" placeholder="Название" required>
    <input type="number" step="0.01" name="price" placeholder="Цена" required>
    <textarea name="description" placeholder="Описание"></textarea>
    <input type="file" name="image" accept="image/*">
    <button type="submit">Сохранить</button>
</form>
Результат: отображается форма с полями. После заполнения и нажатия кнопки появляется сообщение об успехе или список ошибок. Изображение сохраняется в папку uploads.

Пример 2: Список товаров с пагинацией

Вывод всех товаров постранично с использованием LIMIT и OFFSET. Количество записей на страницу задаётся константой.

Пример
<?php
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$perPage = 10;
$offset = ($page - 1) * $perPage;
$stmt = $pdo->prepare('SELECT * FROM products ORDER BY id DESC LIMIT ? OFFSET ?');
$stmt->bindValue(1, $perPage, PDO::PARAM_INT);
$stmt->bindValue(2, $offset, PDO::PARAM_INT);
$stmt->execute();
$products = $stmt->fetchAll();
$total = $pdo->query('SELECT COUNT(*) FROM products')->fetchColumn();
$pages = ceil($total / $perPage);
?>
<table>
    <tr><th>ID</th><th>Название</th><th>Цена</th><th>Действия</th></tr>
    <?php foreach ($products as $p): ?>
    <tr>
        <td><?= $p['id'] ?></td>
        <td><?= htmlspecialchars($p['name']) ?></td>
        <td><?= $p['price'] ?></td>
        <td><a href="edit.php?id=<?= $p['id'] ?>">Редактировать</a> <a href="delete.php?id=<?= $p['id'] ?>" onclick="return confirm('Удалить?')">Удалить</a></td>
    </tr>
    <?php endforeach; ?>
</table>
<?php for ($i = 1; $i <= $pages; $i++): ?>
    <a href="?page=<?= $i ?>"><?= $i ?></a>
<?php endfor; ?>
Результат: таблица с товарами, под ней ссылки для переключения страниц. Данные экранированы через htmlspecialchars для предотвращения XSS.

Пример 3: Редактирование товара с предзаполнением формы

Страница edit.php получает id товара, загружает данные в форму и при отправке обновляет запись.

Пример
<?php
$id = (int)$_GET['id'];
$item = $product->getById($id);
if (!$item) { die('Товар не найден'); }
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $product->update($id, [
        'name' => $_POST['name'],
        'price' => $_POST['price'],
        'description' => $_POST['description']
    ]);
    header('Location: index.php');
    exit;
}
?>
<form method="post">
    <input type="text" name="name" value="<?= htmlspecialchars($item['name']) ?>">
    <input type="number" step="0.01" name="price" value="<?= $item['price'] ?>">
    <textarea name="description"><?= htmlspecialchars($item['description']) ?></textarea>
    <button type="submit">Обновить</button>
</form>
Результат: форма заполнена текущими данными. После изменения и отправки происходит редирект на список товаров.

Пример 4: Удаление товара с подтверждением и обработкой ошибок

Удаление через отдельный скрипт, который проверяет наличие товара и выполняет DELETE.

Пример
<?php
$id = (int)$_GET['id'];
$item = $product->getById($id);
if (!$item) {
    die('Товар не найден');
}
if (isset($_GET['confirm']) && $_GET['confirm'] === 'yes') {
    $product->delete($id);
    header('Location: index.php');
    exit;
}
?>
<p>Вы уверены, что хотите удалить товар "<?= htmlspecialchars($item['name']) ?>"?</p>
<a href="?id=<?= $id ?>&amp;confirm=yes">Да, удалить</a>
<a href="index.php">Нет</a>
Результат: страница подтверждения с именем товара. При нажатии на "Да, удалить" товар удаляется и происходит редирект. Если товара нет, выводится ошибка.

Товары в интернет-магазине PHP - comments

En
Shop product php (php)