Реализация класса каталога в PHP для e-commerce проектов

Раздел: E-commerce -> Каталог товаров

Реализация класса каталога товаров в PHP

Класс каталога - центральный компонент интернет-магазина, отвечающий за управление списком товаров, их фильтрацию, сортировку и постраничный вывод. В зависимости от размера проекта и требований к производительности можно выбрать различные подходы. Далее рассмотрено основное эффективное решение и несколько альтернатив.

Класс Catalog на PDO с гибкими методами

Наиболее универсальный вариант - класс, использующий PDO для работы с реляционной базой данных. Он поддерживает фильтрацию по категории, цене, бренду, а также пагинацию и сортировку.


class Catalog {
    private PDO $pdo;
    
    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
    
    public function getProducts(
        int $page = 1,
        int $perPage = 12,
        ?int $categoryId = null,
        ?string $sort = 'name_asc',
        ?float $minPrice = null,
        ?float $maxPrice = null
    ): array {
        $offset = ($page - 1) * $perPage;
        $where = [];
        $params = [];
        
        if ($categoryId) {
            $where[] = 'category_id = :category_id';
            $params[':category_id'] = $categoryId;
        }
        if ($minPrice) {
            $where[] = 'price >= :min_price';
            $params[':min_price'] = $minPrice;
        }
        if ($maxPrice) {
            $where[] = 'price <= :max_price';
            $params[':max_price'] = $maxPrice;
        }
        
        $whereClause = $where ? 'WHERE ' . implode(' AND ', $where) : '';
        
        $order = match($sort) {
            'price_asc' => 'ORDER BY price ASC',
            'price_desc' => 'ORDER BY price DESC',
            'name_desc' => 'ORDER BY name DESC',
            default => 'ORDER BY name ASC'
        };
        
        $sql = "SELECT * FROM products {$whereClause} {$order} LIMIT :limit OFFSET :offset";
        $stmt = $this->pdo->prepare($sql);
        $stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
        $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
        foreach ($params as $key => $val) {
            $stmt->bindValue($key, $val);
        }
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    
    public function countProducts(...$filters): int {
        // аналогичная логика для COUNT(*)
    }
}

Catalog php class (класс каталога в php)

Цель:

обеспечить производительную и безопасную выборку товаров с динамическими условиями. Использование: крупные магазины с тысячами товаров и сложными фильтрами.

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

  • Отсутствие экранирования параметров - использование подготовленных выражений решает проблему.
  • Неэффективная пагинация на больших объёмах - для больших таблиц стоит добавить индексы.
  • Забывают считать общее количество товаров для пагинации - требуется отдельный метод countProducts.

Вариант 1. Как сделать простой класс каталога без базы данных?

Для небольших каталогов или временных прототипов можно хранить товары в массиве внутри класса.


class InMemoryCatalog {
    private array $products;
    
    public function __construct(array $products = []) {
        $this->products = $products;
    }
    
    public function filterByCategory(int $catId): array {
        return array_filter($this->products, fn($p) => $p['category_id'] === $catId);
    }
}

Вопрос: какой случай использования у такого подхода?

Ответ: демонстрация функционала, тестирование, магазины с десятком товаров без необходимости сохранять изменения.

Проблемы:

  • Данные не сохраняются между запросами.
  • Ограниченный объём памяти для больших наборов.
  • Нет возможности выполнять сложные SQL-подобные запросы.

Вариант 2. Как интегрировать класс каталога с ORM (например, Eloquent)?

В современных фреймворках используют ORM для работы с БД. Класс каталога становится сервисом, работающим с моделью Product.


class CatalogService {
    public function getFilteredProducts(array $filters): LengthAwarePaginator {
        $query = Product::query();
        if (!empty($filters['category_id'])) {
            $query->where('category_id', $filters['category_id']);
        }
        return $query->paginate($filters['per_page'] ?? 12);
    }
}

Вопрос: когда использовать ORM вместо прямого PDO?

Ответ: когда проект уже использует фреймворк (Laravel, Symfony) - ORM ускоряет разработку и предоставляет встроенные возможности пагинации/фильтрации.

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

  • N+1 запросов при получении связанных данных (используйте with()).
  • Неоптимальная производительность при сложных фильтрах - лучше перейти на сырые запросы.

Вариант 3. Как реализовать класс каталога с паттерном Repository?

Repository абстрагирует логику доступа к данным, упрощая тестирование и замену хранилища.


interface ProductRepositoryInterface {
    public function findById(int $id): ?Product;
    public function findAll(array $criteria): Collection;
}

class Catalog {
    public function __construct(private ProductRepositoryInterface $repository) {}
    
    public function getProductsByCategory(int $categoryId): array {
        return $this->repository->findAll(['category_id' => $categoryId]);
    }
}

Вопрос: какая выгода от использования Repository?

Ответ: код становится независимым от конкретной реализации БД, легко подменить на mock в тестах или переключиться с MySQL на MongoDB.

Проблемы:

  • Увеличение количества классов.
  • Избыточность для простых сценариев.
  • Необходимость чётко определить интерфейс, чтобы не потерять гибкость.

Расширенные примеры кода для класса каталога

Пример 1. Класс с кешированием результатов

Добавление мемкеша или файлового кеша для снижения нагрузки на базу данных.

Пример

class CachedCatalog extends Catalog {
    private CacheInterface $cache;
    private int $ttl;
    
    public function getProducts(int $page = 1, int $perPage = 12, ?int $categoryId = null): array {
        $cacheKey = "products_{$page}_{$perPage}_" . ($categoryId ?? 'all');
        if ($cached = $this->cache->get($cacheKey)) {
            return $cached;
        }
        $products = parent::getProducts($page, $perPage, $categoryId);
        $this->cache->set($cacheKey, $products, $this->ttl);
        return $products;
    }
}
// Использование
$catalog = new CachedCatalog($pdo, new FileCache(), 3600);
$products = $catalog->getProducts(1, 12, 5);

Результат:

повторный запрос к тому же набору товаров не вызывает SQL-запрос, данные берутся из кеша.

Пример 2. Класс с поддержкой составных фильтров и полнотекстового поиска

Пример

class AdvancedCatalog {
    private PDO $pdo;
    
    public function searchByText(string $query, int $limit = 10): array {
        $sql = "SELECT *, MATCH(name, description) AGAINST(:query IN BOOLEAN MODE) AS relevance
                FROM products
                WHERE MATCH(name, description) AGAINST(:query IN BOOLEAN MODE)
                ORDER BY relevance DESC
                LIMIT :limit";
        $stmt = $this->pdo->prepare($sql);
        $stmt->bindValue(':query', $query, PDO::PARAM_STR);
        $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

Результат:

при поиске "красное платье" вернутся товары с упоминанием этих слов в названии или описании, отсортированные по релевантности.

Пример 3. Агрегация данных - получение минимальной и максимальной цены в категории

Пример

public function getPriceRange(?int $categoryId = null): array {
    $sql = "SELECT MIN(price) AS min_price, MAX(price) AS max_price FROM products";
    $params = [];
    if ($categoryId) {
        $sql .= " WHERE category_id = :cat";
        $params[':cat'] = $categoryId;
    }
    $stmt = $this->pdo->prepare($sql);
    $stmt->execute($params);
    return $stmt->fetch(PDO::FETCH_ASSOC);
}
// Вызов
$range = $catalog->getPriceRange(5);
// $range = ['min_price' => 199.00, 'max_price' => 2999.00]

Результат:

массив с границами цен, используется для построения слайдера фильтра.

Пример 4. Интеграция с внешним API (например, поставщик товаров)

Пример

class ApiCatalog {
    private HttpClient $client;
    
    public function getProductsFromApi(array $filters): array {
        $response = $this->client->get('https://supplier.example.com/products', [
            'query' => $filters
        ]);
        return json_decode($response->getBody(), true);
    }
}

Результат:

класс работает как прокси - получает данные от внешнего сервиса, может кешировать и трансформировать их в массив товаров.

Класс каталога в PHP - comments

En
Catalog php class (php)