Files
new.rybstock.ru/app/Repositories/ProductRepository.php
T
2026-06-04 01:30:45 +03:00

207 lines
7.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Repositories;
use App\Services\PriceTierService;
final class ProductRepository extends BaseRepository
{
/**
* @return array<int, array<string, mixed>>
*/
public function popularPreview(int $limit = 10): array
{
$limit = max(1, min($limit, 30));
$sql = $this->previewSelectSql(
'ORDER BY p.sales_count DESC, RAND()',
$limit
);
$statement = $this->pdo()->query($sql);
return $this->attachPriceTiers($statement->fetchAll());
}
/**
* @return array<int, array<string, mixed>>
*/
public function catalogPreview(int $limit = 24): array
{
$limit = max(1, min($limit, 60));
$sql = $this->previewSelectSql(
'ORDER BY p.sort_order, p.name',
$limit
);
$statement = $this->pdo()->query($sql);
return $this->attachPriceTiers($statement->fetchAll());
}
/**
* @return array<int, array<string, mixed>>
*/
public function forCategory(int $categoryId, int $limit = 48): array
{
$limit = max(1, min($limit, 96));
$categoryIds = (new CategoryRepository())->descendantIds($categoryId);
$placeholders = implode(',', array_fill(0, count($categoryIds), '?'));
$sql = $this->previewSelectSql(
'AND p.category_id IN (' . $placeholders . ') ORDER BY p.sort_order, p.name',
$limit
);
$statement = $this->pdo()->prepare($sql);
$statement->execute($categoryIds);
return $this->attachPriceTiers($statement->fetchAll());
}
/**
* @return array<int, array<string, mixed>>
*/
public function adminLatest(int $limit = 30): array
{
$limit = max(1, min($limit, 100));
$statement = $this->pdo()->query(
'SELECT p.id, p.name, p.slug, p.is_published, c.name AS category_name, p.created_at
FROM products p
LEFT JOIN categories c ON c.id = p.category_id
ORDER BY p.created_at DESC, p.id DESC
LIMIT ' . $limit
);
return $statement->fetchAll();
}
/**
* @param array<string, mixed> $data
*/
public function createWithVariant(array $data): int
{
if (($data['name'] ?? '') === '' || ($data['slug'] ?? '') === '') {
throw new \InvalidArgumentException('Название и slug обязательны.');
}
$pdo = $this->pdo();
$pdo->beginTransaction();
try {
$statement = $pdo->prepare(
'INSERT INTO products
(category_id, name, slug, short_description, seo_title, seo_description, seo_keywords, base_unit, is_published, is_available, published_at)
VALUES
(:category_id, :name, :slug, :short_description, :seo_title, :seo_description, :seo_keywords, :base_unit, :is_published, 1, :published_at)'
);
$statement->execute([
'category_id' => $data['category_id'],
'name' => $data['name'],
'slug' => $data['slug'],
'short_description' => $data['short_description'] ?: null,
'seo_title' => $data['seo_title'] ?: null,
'seo_description' => $data['seo_description'] ?: null,
'seo_keywords' => $data['seo_keywords'] ?: null,
'base_unit' => $data['base_unit'] ?: 'kg',
'is_published' => $data['is_published'] ?? 0,
'published_at' => !empty($data['is_published']) ? date('Y-m-d H:i:s') : null,
]);
$productId = (int) $pdo->lastInsertId();
if (($data['variant_name'] ?? '') !== '' && (float) ($data['package_price'] ?? 0) > 0) {
$variantStatement = $pdo->prepare(
'INSERT INTO product_variants
(product_id, name, unit, package_quantity, step_quantity, price_per_unit, package_price, old_package_price, discount_type, discount_percent, is_default, is_published, is_available)
VALUES
(:product_id, :name, :unit, :package_quantity, :step_quantity, :price_per_unit, :package_price, :old_package_price, :discount_type, :discount_percent, 1, 1, 1)'
);
$variantStatement->execute([
'product_id' => $productId,
'name' => $data['variant_name'],
'unit' => $data['base_unit'] ?: 'kg',
'package_quantity' => $data['package_quantity'] ?: 1,
'step_quantity' => $data['step_quantity'] ?: ($data['package_quantity'] ?: 1),
'price_per_unit' => $data['price_per_unit'],
'package_price' => $data['package_price'],
'old_package_price' => $data['old_package_price'] ?? null,
'discount_type' => $data['discount_type'] ?? 'none',
'discount_percent' => $data['discount_percent'] ?? null,
]);
}
$pdo->commit();
return $productId;
} catch (\Throwable $exception) {
$pdo->rollBack();
throw $exception;
}
}
private function previewSelectSql(string $tailSql, int $limit): string
{
return 'SELECT
p.id,
p.name,
p.slug,
p.legacy_path,
p.h1,
p.seo_title,
p.seo_description,
p.seo_keywords,
c.name AS category_name,
c.slug AS category_slug,
p.main_image_path,
p.base_unit,
p.package_display_mode,
p.sales_count,
v.id AS variant_id,
v.name AS variant_name,
v.unit,
v.package_quantity,
v.price_per_unit,
v.package_price,
v.old_package_price,
v.discount_type,
v.discount_percent
FROM products p
LEFT JOIN categories c ON c.id = p.category_id
LEFT JOIN product_variants v
ON v.id = (
SELECT pv.id
FROM product_variants pv
WHERE pv.product_id = p.id
AND pv.is_published = 1
AND pv.is_available = 1
ORDER BY pv.is_default DESC, pv.sort_order, pv.id
LIMIT 1
)
WHERE p.is_published = 1
AND p.is_available = 1
' . $tailSql . '
LIMIT ' . $limit;
}
/**
* @param array<int, array<string, mixed>> $products
* @return array<int, array<string, mixed>>
*/
private function attachPriceTiers(array $products): array
{
$priceTierService = new PriceTierService();
foreach ($products as &$product) {
$basePrice = $product['price_per_unit'] ?? $product['package_price'] ?? null;
$product['price_tiers'] = $basePrice === null
? []
: $priceTierService->forAmount((float) $basePrice);
}
unset($product);
return $products;
}
}