feat: remove cache, refactor
This commit is contained in:
5
Makefile
5
Makefile
@@ -29,4 +29,7 @@ link:
|
|||||||
|
|
||||||
dev:
|
dev:
|
||||||
$(MAKE) link && \
|
$(MAKE) link && \
|
||||||
cd spa && npm run dev
|
cd spa && npm run dev
|
||||||
|
|
||||||
|
lint:
|
||||||
|
docker compose exec -w /module/oc_telegram_shop/upload/oc_telegram_shop web bash -c "./vendor/bin/phpstan analyse src framework"
|
||||||
@@ -12,7 +12,7 @@ services:
|
|||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: 256M
|
memory: 512M
|
||||||
environment:
|
environment:
|
||||||
- WEB_DOCUMENT_ROOT=/web/upload
|
- WEB_DOCUMENT_ROOT=/web/upload
|
||||||
- PHP_DISPLAY_ERRORS=1
|
- PHP_DISPLAY_ERRORS=1
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"symfony/cache": "^5.4"
|
"symfony/cache": "^5.4"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"roave/security-advisories": "dev-latest"
|
"roave/security-advisories": "dev-latest",
|
||||||
|
"phpstan/phpstan": "^2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "329d7196ee1db3f0f4e8b3552e28f83a",
|
"content-hash": "119f24aafb5ae0828f70c3c57fa07234",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "graham-campbell/result-type",
|
"name": "graham-campbell/result-type",
|
||||||
@@ -1733,6 +1733,64 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
|
{
|
||||||
|
"name": "phpstan/phpstan",
|
||||||
|
"version": "2.1.22",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/phpstan/phpstan.git",
|
||||||
|
"reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/41600c8379eb5aee63e9413fe9e97273e25d57e4",
|
||||||
|
"reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.4|^8.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"phpstan/phpstan-shim": "*"
|
||||||
|
},
|
||||||
|
"bin": [
|
||||||
|
"phpstan",
|
||||||
|
"phpstan.phar"
|
||||||
|
],
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"description": "PHPStan - PHP Static Analysis Tool",
|
||||||
|
"keywords": [
|
||||||
|
"dev",
|
||||||
|
"static analysis"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"docs": "https://phpstan.org/user-guide/getting-started",
|
||||||
|
"forum": "https://github.com/phpstan/phpstan/discussions",
|
||||||
|
"issues": "https://github.com/phpstan/phpstan/issues",
|
||||||
|
"security": "https://github.com/phpstan/phpstan/security/policy",
|
||||||
|
"source": "https://github.com/phpstan/phpstan-src"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/ondrejmirtes",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/phpstan",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-08-04T19:17:37+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "roave/security-advisories",
|
"name": "roave/security-advisories",
|
||||||
"version": "dev-latest",
|
"version": "dev-latest",
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Handlers;
|
namespace App\Handlers;
|
||||||
|
|
||||||
use Openguru\OpenCartFramework\Config\Settings;
|
|
||||||
use Openguru\OpenCartFramework\Http\JsonResponse;
|
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||||
use Openguru\OpenCartFramework\Http\Request;
|
|
||||||
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
|
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
|
||||||
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
||||||
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
||||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
|
||||||
use Symfony\Contracts\Cache\ItemInterface;
|
|
||||||
|
|
||||||
class CategoriesHandler
|
class CategoriesHandler
|
||||||
{
|
{
|
||||||
@@ -18,45 +16,41 @@ class CategoriesHandler
|
|||||||
private Builder $queryBuilder;
|
private Builder $queryBuilder;
|
||||||
private ImageToolInterface $ocImageTool;
|
private ImageToolInterface $ocImageTool;
|
||||||
|
|
||||||
public function __construct(Builder $queryBuilder, ImageToolInterface $ocImageTool, Settings $settings)
|
public function __construct(Builder $queryBuilder, ImageToolInterface $ocImageTool)
|
||||||
{
|
{
|
||||||
$this->queryBuilder = $queryBuilder;
|
$this->queryBuilder = $queryBuilder;
|
||||||
$this->ocImageTool = $ocImageTool;
|
$this->ocImageTool = $ocImageTool;
|
||||||
$this->settings = $settings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function index(): JsonResponse
|
public function index(): JsonResponse
|
||||||
{
|
{
|
||||||
$cache = new FilesystemAdapter();
|
$languageId = 1;
|
||||||
|
|
||||||
$categories = $cache->get('main-page-categories', function (ItemInterface $item) {
|
$categoriesFlat = $this->queryBuilder->newQuery()
|
||||||
$item->expiresAfter($this->settings->get('cache_categories_main', 60 * 5));
|
->select([
|
||||||
$languageId = 1;
|
'categories.category_id' => 'id',
|
||||||
|
'categories.parent_id' => 'parent_id',
|
||||||
|
'categories.image' => 'image',
|
||||||
|
'descriptions.name' => 'name',
|
||||||
|
'descriptions.description' => 'description',
|
||||||
|
])
|
||||||
|
->from(db_table('category'), 'categories')
|
||||||
|
->join(
|
||||||
|
db_table('category_description') . ' AS descriptions',
|
||||||
|
function (JoinClause $join) use ($languageId) {
|
||||||
|
$join->on('categories.category_id', '=', 'descriptions.category_id')
|
||||||
|
->where('descriptions.language_id', '=', $languageId);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
->where('categories.status', '=', 1)
|
||||||
|
->orderBy('parent_id')
|
||||||
|
->orderBy('sort_order')
|
||||||
|
->get();
|
||||||
|
|
||||||
$categoriesFlat = $this->queryBuilder->newQuery()
|
$categories = $this->buildCategoryTree($categoriesFlat);
|
||||||
->select([
|
|
||||||
'categories.category_id' => 'id',
|
|
||||||
'categories.parent_id' => 'parent_id',
|
|
||||||
'categories.image' => 'image',
|
|
||||||
'descriptions.name' => 'name',
|
|
||||||
'descriptions.description' => 'description',
|
|
||||||
])
|
|
||||||
->from(db_table('category'), 'categories')
|
|
||||||
->join(
|
|
||||||
db_table('category_description') . ' AS descriptions',
|
|
||||||
function (JoinClause $join) use ($languageId) {
|
|
||||||
$join->on('categories.category_id', '=', 'descriptions.category_id')
|
|
||||||
->where('descriptions.language_id', '=', $languageId);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
->where('categories.status', '=', 1)
|
|
||||||
->orderBy('parent_id')
|
|
||||||
->orderBy('sort_order')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
$categories = $this->buildCategoryTree($categoriesFlat);
|
return new JsonResponse([
|
||||||
|
'data' => array_map(static function ($category) {
|
||||||
return array_map(static function ($category) {
|
|
||||||
return [
|
return [
|
||||||
'id' => (int)$category['id'],
|
'id' => (int)$category['id'],
|
||||||
'image' => $category['image'],
|
'image' => $category['image'],
|
||||||
@@ -64,11 +58,7 @@ class CategoriesHandler
|
|||||||
'description' => $category['description'],
|
'description' => $category['description'],
|
||||||
'children' => $category['children'],
|
'children' => $category['children'],
|
||||||
];
|
];
|
||||||
}, $categories);
|
}, $categories),
|
||||||
});
|
|
||||||
|
|
||||||
return new JsonResponse([
|
|
||||||
'data' => $categories,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,191 +1,32 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Handlers;
|
namespace App\Handlers;
|
||||||
|
|
||||||
use App\Adapters\OcModelCatalogProductAdapter;
|
use App\Services\ProductsService;
|
||||||
use App\Decorators\OcRegistryDecorator;
|
use Exception;
|
||||||
use Cart\Currency;
|
|
||||||
use Cart\Tax;
|
|
||||||
use Openguru\OpenCartFramework\Config\Settings;
|
use Openguru\OpenCartFramework\Config\Settings;
|
||||||
use Openguru\OpenCartFramework\Http\JsonResponse;
|
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||||
use Openguru\OpenCartFramework\Http\Request;
|
use Openguru\OpenCartFramework\Http\Request;
|
||||||
use Openguru\OpenCartFramework\Http\Response;
|
use Openguru\OpenCartFramework\Logger\Logger;
|
||||||
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
|
use RuntimeException;
|
||||||
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
|
||||||
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
|
||||||
use Openguru\OpenCartFramework\Support\Arr;
|
|
||||||
use Openguru\OpenCartFramework\Support\PaginationHelper;
|
|
||||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
|
||||||
use Symfony\Contracts\Cache\ItemInterface;
|
|
||||||
|
|
||||||
class ProductsHandler
|
class ProductsHandler
|
||||||
{
|
{
|
||||||
private Builder $queryBuilder;
|
|
||||||
private Currency $currency;
|
|
||||||
private Tax $tax;
|
|
||||||
private Settings $settings;
|
private Settings $settings;
|
||||||
private OcModelCatalogProductAdapter $ocModelCatalogProduct;
|
private ProductsService $productsService;
|
||||||
private ImageToolInterface $ocImageTool;
|
private Logger $logger;
|
||||||
private OcRegistryDecorator $oc;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(Settings $settings, ProductsService $productsService, Logger $logger)
|
||||||
Builder $queryBuilder,
|
|
||||||
Currency $currency,
|
|
||||||
Tax $tax,
|
|
||||||
Settings $settings,
|
|
||||||
OcModelCatalogProductAdapter $ocModelCatalogProduct,
|
|
||||||
ImageToolInterface $ocImageTool,
|
|
||||||
OcRegistryDecorator $registry
|
|
||||||
) {
|
|
||||||
$this->queryBuilder = $queryBuilder;
|
|
||||||
$this->currency = $currency;
|
|
||||||
$this->tax = $tax;
|
|
||||||
$this->settings = $settings;
|
|
||||||
$this->ocModelCatalogProduct = $ocModelCatalogProduct;
|
|
||||||
$this->ocImageTool = $ocImageTool;
|
|
||||||
$this->oc = $registry;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getProductsResponse(array $params): array
|
|
||||||
{
|
{
|
||||||
$page = $params['page'];
|
$this->settings = $settings;
|
||||||
$perPage = $params['perPage'];
|
$this->productsService = $productsService;
|
||||||
$categoryId = $params['categoryId'];
|
$this->logger = $logger;
|
||||||
$search = $params['search'];
|
|
||||||
$forMainPage = $params['forMainPage'];
|
|
||||||
$featuredProducts = $params['featuredProducts'];
|
|
||||||
$mainpageProducts = $params['mainpageProducts'];
|
|
||||||
$languageId = 1;
|
|
||||||
$categoryName = '';
|
|
||||||
$imageWidth = 300;
|
|
||||||
$imageHeight = 300;
|
|
||||||
|
|
||||||
if ($categoryId) {
|
|
||||||
$categoryName = $this->queryBuilder->newQuery()
|
|
||||||
->select(['name'])
|
|
||||||
->from(db_table('category_description'), 'category')
|
|
||||||
->where('language_id', '=', $languageId)
|
|
||||||
->where('category_id', '=', $categoryId)
|
|
||||||
->value('name');
|
|
||||||
}
|
|
||||||
|
|
||||||
$productsQuery = $this->queryBuilder->newQuery()
|
|
||||||
->select([
|
|
||||||
'products.product_id' => 'product_id',
|
|
||||||
'products.quantity' => 'product_quantity',
|
|
||||||
'product_description.name' => 'product_name',
|
|
||||||
'products.price' => 'price',
|
|
||||||
'products.image' => 'product_image',
|
|
||||||
'products.tax_class_id' => 'tax_class_id',
|
|
||||||
])
|
|
||||||
->from(db_table('product'), 'products')
|
|
||||||
->join(
|
|
||||||
db_table('product_description') . ' AS product_description',
|
|
||||||
function (JoinClause $join) use ($languageId) {
|
|
||||||
$join->on('products.product_id', '=', 'product_description.product_id')
|
|
||||||
->where('product_description.language_id', '=', $languageId);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
->when($categoryId !== 0, function (Builder $query) use ($categoryId) {
|
|
||||||
$query->join(
|
|
||||||
db_table('product_to_category') . ' AS product_to_category',
|
|
||||||
function (JoinClause $join) use ($categoryId) {
|
|
||||||
$join->on('product_to_category.product_id', '=', 'products.product_id')
|
|
||||||
->where('product_to_category.category_id', '=', $categoryId);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
})
|
|
||||||
->when(
|
|
||||||
$forMainPage && $mainpageProducts === 'featured' && $featuredProducts,
|
|
||||||
function (Builder $query) use ($featuredProducts) {
|
|
||||||
$query->whereIn('products.product_id', $featuredProducts);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
->when($search, function (Builder $query) use ($search) {
|
|
||||||
$query->where('product_description.name', 'LIKE', '%' . $search . '%');
|
|
||||||
});
|
|
||||||
|
|
||||||
$total = $productsQuery->count();
|
|
||||||
$lastPage = PaginationHelper::calculateLastPage($total, $perPage);
|
|
||||||
$hasMore = $page + 1 <= $lastPage;
|
|
||||||
|
|
||||||
$products = $productsQuery
|
|
||||||
->forPage($page, $perPage)
|
|
||||||
->orderBy($mainpageProducts === 'latest' ? 'date_modified' : 'viewed', 'DESC')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
$productIds = Arr::pluck($products, 'product_id');
|
|
||||||
$productsImages = [];
|
|
||||||
|
|
||||||
if ($productIds) {
|
|
||||||
$productsImages = $this->queryBuilder->newQuery()
|
|
||||||
->select([
|
|
||||||
'products_images.product_id' => 'product_id',
|
|
||||||
'products_images.image' => 'image',
|
|
||||||
])
|
|
||||||
->from(db_table('product_image'), 'products_images')
|
|
||||||
->orderBy('products_images.sort_order', 'ASC')
|
|
||||||
->whereIn('product_id', $productIds)
|
|
||||||
->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
$productsImagesMap = [];
|
|
||||||
foreach ($productsImages as $item) {
|
|
||||||
$productsImagesMap[$item['product_id']][] = [
|
|
||||||
'url' => $this->ocImageTool->resize($item['image'], $imageWidth, $imageHeight, 'placeholder.png'),
|
|
||||||
'alt' => 'Product Image',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'data' => array_map(function ($product) use ($productsImagesMap, $imageWidth, $imageHeight) {
|
|
||||||
$allImages = [];
|
|
||||||
|
|
||||||
$image = $this->ocImageTool->resize(
|
|
||||||
$product['product_image'],
|
|
||||||
$imageWidth,
|
|
||||||
$imageHeight,
|
|
||||||
'placeholder.png'
|
|
||||||
);
|
|
||||||
|
|
||||||
$allImages[] = [
|
|
||||||
'url' => $image,
|
|
||||||
'alt' => $product['product_name'],
|
|
||||||
];
|
|
||||||
|
|
||||||
$price = $this->currency->format(
|
|
||||||
$this->tax->calculate(
|
|
||||||
$product['price'],
|
|
||||||
$product['tax_class_id'],
|
|
||||||
$this->settings->get('oc_config_tax'),
|
|
||||||
),
|
|
||||||
$this->settings->get('oc_default_currency'),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (! empty($productsImagesMap[$product['product_id']])) {
|
|
||||||
$allImages = array_merge($allImages, $productsImagesMap[$product['product_id']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'id' => (int) $product['product_id'],
|
|
||||||
'product_quantity' => (int) $product['product_quantity'],
|
|
||||||
'name' => $product['product_name'],
|
|
||||||
'price' => $price,
|
|
||||||
'images' => $allImages,
|
|
||||||
];
|
|
||||||
}, $products),
|
|
||||||
|
|
||||||
'meta' => [
|
|
||||||
'currentCategoryName' => $categoryName,
|
|
||||||
'hasMore' => $hasMore,
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(Request $request): JsonResponse
|
public function handle(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$cache = new FilesystemAdapter();
|
|
||||||
|
|
||||||
$page = (int) $request->get('page', 1);
|
$page = (int) $request->get('page', 1);
|
||||||
$perPage = min((int) $request->get('perPage', 6), 15);
|
$perPage = min((int) $request->get('perPage', 6), 15);
|
||||||
$categoryId = (int) $request->get('categoryId', 0);
|
$categoryId = (int) $request->get('categoryId', 0);
|
||||||
@@ -194,45 +35,17 @@ class ProductsHandler
|
|||||||
$featuredProducts = $this->settings->get('featured_products');
|
$featuredProducts = $this->settings->get('featured_products');
|
||||||
$mainpageProducts = $this->settings->get('mainpage_products');
|
$mainpageProducts = $this->settings->get('mainpage_products');
|
||||||
|
|
||||||
if ($forMainPage && $page === 1) {
|
$response = $this->productsService->getProductsResponse(
|
||||||
$response = $cache->get(
|
compact(
|
||||||
'main-page-products',
|
'page',
|
||||||
function (ItemInterface $cacheitem) use (
|
'perPage',
|
||||||
$page,
|
'categoryId',
|
||||||
$perPage,
|
'search',
|
||||||
$categoryId,
|
'forMainPage',
|
||||||
$search,
|
'featuredProducts',
|
||||||
$forMainPage,
|
'mainpageProducts'
|
||||||
$featuredProducts,
|
)
|
||||||
$mainpageProducts
|
);
|
||||||
): array {
|
|
||||||
$cacheitem->expiresAfter($this->settings->get('cache_products_main', 60 * 10));
|
|
||||||
return $this->getProductsResponse(
|
|
||||||
compact(
|
|
||||||
'page',
|
|
||||||
'perPage',
|
|
||||||
'categoryId',
|
|
||||||
'search',
|
|
||||||
'forMainPage',
|
|
||||||
'featuredProducts',
|
|
||||||
'mainpageProducts'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$response = $this->getProductsResponse(
|
|
||||||
compact(
|
|
||||||
'page',
|
|
||||||
'perPage',
|
|
||||||
'categoryId',
|
|
||||||
'search',
|
|
||||||
'forMainPage',
|
|
||||||
'featuredProducts',
|
|
||||||
'mainpageProducts'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonResponse($response);
|
return new JsonResponse($response);
|
||||||
}
|
}
|
||||||
@@ -246,152 +59,22 @@ class ProductsHandler
|
|||||||
$imageFullWidth = 1000;
|
$imageFullWidth = 1000;
|
||||||
$imageFullHeight = 1000;
|
$imageFullHeight = 1000;
|
||||||
|
|
||||||
$product = $this->queryBuilder->newQuery()
|
try {
|
||||||
->select([
|
$product = $this->productsService->getProduct(
|
||||||
'products.product_id' => 'product_id',
|
$productId,
|
||||||
'product_description.name' => 'product_name',
|
$languageId,
|
||||||
'product_description.description' => 'product_description',
|
$imageWidth,
|
||||||
'products.price' => 'price',
|
$imageHeight,
|
||||||
'products.minimum' => 'minimum',
|
$imageFullWidth,
|
||||||
'products.quantity' => 'quantity',
|
$imageFullHeight
|
||||||
'products.image' => 'product_image',
|
);
|
||||||
'products.tax_class_id' => 'tax_class_id',
|
} catch (Exception $exception) {
|
||||||
'manufacturer.name' => 'product_manufacturer',
|
$this->logger->logException($exception);
|
||||||
])
|
throw new RuntimeException('Error get product with id ' . $productId);
|
||||||
->from(db_table('product'), 'products')
|
|
||||||
->join(
|
|
||||||
db_table('product_description') . ' AS product_description',
|
|
||||||
function (JoinClause $join) use ($languageId) {
|
|
||||||
$join->on('products.product_id', '=', 'product_description.product_id')
|
|
||||||
->where('product_description.language_id', '=', $languageId);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
->leftJoin(
|
|
||||||
db_table('manufacturer') . ' AS manufacturer',
|
|
||||||
function (JoinClause $join) use ($languageId) {
|
|
||||||
$join->on('products.manufacturer_id', '=', 'manufacturer.manufacturer_id');
|
|
||||||
}
|
|
||||||
)
|
|
||||||
->where('products.product_id', '=', $productId)
|
|
||||||
->limit(1)
|
|
||||||
->firstOrNull();
|
|
||||||
|
|
||||||
if (! $product) {
|
|
||||||
return new JsonResponse([], Response::HTTP_NOT_FOUND);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$productsImages = $this->queryBuilder->newQuery()
|
|
||||||
->select([
|
|
||||||
'products_images.product_id' => 'product_id',
|
|
||||||
'products_images.image' => 'image',
|
|
||||||
])
|
|
||||||
->from(db_table('product_image'), 'products_images')
|
|
||||||
->orderBy('products_images.sort_order', 'ASC')
|
|
||||||
->where('products_images.product_id', '=', $productId)
|
|
||||||
->get();
|
|
||||||
|
|
||||||
$imagePaths = [];
|
|
||||||
$imagePaths[] = $product['product_image'];
|
|
||||||
foreach ($productsImages as $item) {
|
|
||||||
$imagePaths[] = $item['image'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$images = [];
|
|
||||||
foreach ($imagePaths as $imagePath) {
|
|
||||||
[$width, $height] = $this->ocImageTool->getRealSize($imagePath);
|
|
||||||
$images[] = [
|
|
||||||
'thumbnailURL' => $this->ocImageTool->resize($imagePath, $imageWidth, $imageHeight, 'placeholder.png'),
|
|
||||||
'largeURL' => $this->ocImageTool->resize($imagePath, $imageFullWidth, $imageFullHeight, 'placeholder.png'),
|
|
||||||
'width' => $width,
|
|
||||||
'height' => $height,
|
|
||||||
'alt' => $product['product_name'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$price = $this->currency->format(
|
|
||||||
$this->tax->calculate(
|
|
||||||
$product['price'],
|
|
||||||
$product['tax_class_id'],
|
|
||||||
$this->settings->get('oc_config_tax'),
|
|
||||||
),
|
|
||||||
$this->settings->get('oc_default_currency'),
|
|
||||||
);
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'id' => $product['product_id'],
|
|
||||||
'name' => $product['product_name'],
|
|
||||||
'description' => html_entity_decode($product['product_description']),
|
|
||||||
'manufacturer' => $product['product_manufacturer'],
|
|
||||||
'price' => $price,
|
|
||||||
'minimum' => $product['minimum'],
|
|
||||||
'quantity' => $product['quantity'],
|
|
||||||
'images' => $images,
|
|
||||||
'options' => $this->loadProductOptions($product),
|
|
||||||
'attributes' => $this->loadProductAttributes($product['product_id']),
|
|
||||||
];
|
|
||||||
|
|
||||||
return new JsonResponse([
|
return new JsonResponse([
|
||||||
'data' => $data,
|
'data' => $product,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function loadProductOptions($product): array
|
|
||||||
{
|
|
||||||
$result = [];
|
|
||||||
$productId = $product['product_id'];
|
|
||||||
$taxClassId = $product['tax_class_id'];
|
|
||||||
|
|
||||||
$options = $this->ocModelCatalogProduct->getProductOptions($productId);
|
|
||||||
$ocConfigTax = $this->settings->get('oc_config_tax');
|
|
||||||
$ocDefaultCurrency = $this->settings->get('oc_default_currency');
|
|
||||||
|
|
||||||
foreach ($options as $option) {
|
|
||||||
$product_option_value_data = [];
|
|
||||||
|
|
||||||
foreach ($option['product_option_value'] as $option_value) {
|
|
||||||
if (! $option_value['subtract'] || ($option_value['quantity'] > 0)) {
|
|
||||||
if ((float) $option_value['price']) {
|
|
||||||
$priceWithTax = $this->tax->calculate(
|
|
||||||
$option_value['price'],
|
|
||||||
$taxClassId,
|
|
||||||
$ocConfigTax ? 'Р' : false,
|
|
||||||
);
|
|
||||||
|
|
||||||
$price = $this->currency->format($priceWithTax, $ocDefaultCurrency);
|
|
||||||
} else {
|
|
||||||
$price = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$product_option_value_data[] = [
|
|
||||||
'product_option_value_id' => (int) $option_value['product_option_value_id'],
|
|
||||||
'option_value_id' => (int) $option_value['option_value_id'],
|
|
||||||
'name' => $option_value['name'],
|
|
||||||
'image' => $this->ocImageTool->resize($option_value['image'], 50, 50),
|
|
||||||
'price' => $price,
|
|
||||||
'price_prefix' => $option_value['price_prefix'],
|
|
||||||
'selected' => false,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$result[] = [
|
|
||||||
'product_option_id' => (int) $option['product_option_id'],
|
|
||||||
'values' => $product_option_value_data,
|
|
||||||
'option_id' => (int) $option['option_id'],
|
|
||||||
'name' => $option['name'],
|
|
||||||
'type' => $option['type'],
|
|
||||||
'value' => $option['value'],
|
|
||||||
'required' => filter_var($option['required'], FILTER_VALIDATE_BOOLEAN),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function loadProductAttributes(int $productId): array
|
|
||||||
{
|
|
||||||
$this->oc->load->model('catalog/product');
|
|
||||||
|
|
||||||
return $this->oc->model_catalog_product->getProductAttributes($productId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,344 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Adapters\OcModelCatalogProductAdapter;
|
||||||
|
use App\Decorators\OcRegistryDecorator;
|
||||||
|
use Cart\Currency;
|
||||||
|
use Cart\Tax;
|
||||||
|
use Exception;
|
||||||
|
use Openguru\OpenCartFramework\Config\Settings;
|
||||||
|
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
||||||
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
use Openguru\OpenCartFramework\Support\PaginationHelper;
|
||||||
|
|
||||||
|
class ProductsService
|
||||||
|
{
|
||||||
|
private Builder $queryBuilder;
|
||||||
|
private Currency $currency;
|
||||||
|
private Tax $tax;
|
||||||
|
private Settings $settings;
|
||||||
|
private OcModelCatalogProductAdapter $ocModelCatalogProduct;
|
||||||
|
private ImageToolInterface $ocImageTool;
|
||||||
|
private OcRegistryDecorator $oc;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
Builder $queryBuilder,
|
||||||
|
Currency $currency,
|
||||||
|
Tax $tax,
|
||||||
|
Settings $settings,
|
||||||
|
OcModelCatalogProductAdapter $ocModelCatalogProduct,
|
||||||
|
ImageToolInterface $ocImageTool,
|
||||||
|
OcRegistryDecorator $registry
|
||||||
|
) {
|
||||||
|
$this->queryBuilder = $queryBuilder;
|
||||||
|
$this->currency = $currency;
|
||||||
|
$this->tax = $tax;
|
||||||
|
$this->settings = $settings;
|
||||||
|
$this->ocModelCatalogProduct = $ocModelCatalogProduct;
|
||||||
|
$this->ocImageTool = $ocImageTool;
|
||||||
|
$this->oc = $registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProductsResponse(array $params): array
|
||||||
|
{
|
||||||
|
$page = $params['page'];
|
||||||
|
$perPage = $params['perPage'];
|
||||||
|
$categoryId = $params['categoryId'];
|
||||||
|
$search = $params['search'];
|
||||||
|
$forMainPage = $params['forMainPage'];
|
||||||
|
$featuredProducts = $params['featuredProducts'];
|
||||||
|
$mainpageProducts = $params['mainpageProducts'];
|
||||||
|
$languageId = 1;
|
||||||
|
$categoryName = '';
|
||||||
|
$imageWidth = 300;
|
||||||
|
$imageHeight = 300;
|
||||||
|
|
||||||
|
if ($categoryId) {
|
||||||
|
$categoryName = $this->queryBuilder->newQuery()
|
||||||
|
->select(['name'])
|
||||||
|
->from(db_table('category_description'), 'category')
|
||||||
|
->where('language_id', '=', $languageId)
|
||||||
|
->where('category_id', '=', $categoryId)
|
||||||
|
->value('name');
|
||||||
|
}
|
||||||
|
|
||||||
|
$productsQuery = $this->queryBuilder->newQuery()
|
||||||
|
->select([
|
||||||
|
'products.product_id' => 'product_id',
|
||||||
|
'products.quantity' => 'product_quantity',
|
||||||
|
'product_description.name' => 'product_name',
|
||||||
|
'products.price' => 'price',
|
||||||
|
'products.image' => 'product_image',
|
||||||
|
'products.tax_class_id' => 'tax_class_id',
|
||||||
|
])
|
||||||
|
->from(db_table('product'), 'products')
|
||||||
|
->join(
|
||||||
|
db_table('product_description') . ' AS product_description',
|
||||||
|
function (JoinClause $join) use ($languageId) {
|
||||||
|
$join->on('products.product_id', '=', 'product_description.product_id')
|
||||||
|
->where('product_description.language_id', '=', $languageId);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
->when($categoryId !== 0, function (Builder $query) use ($categoryId) {
|
||||||
|
$query->join(
|
||||||
|
db_table('product_to_category') . ' AS product_to_category',
|
||||||
|
function (JoinClause $join) use ($categoryId) {
|
||||||
|
$join->on('product_to_category.product_id', '=', 'products.product_id')
|
||||||
|
->where('product_to_category.category_id', '=', $categoryId);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
->when(
|
||||||
|
$forMainPage && $mainpageProducts === 'featured' && $featuredProducts,
|
||||||
|
function (Builder $query) use ($featuredProducts) {
|
||||||
|
$query->whereIn('products.product_id', $featuredProducts);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
->when($search, function (Builder $query) use ($search) {
|
||||||
|
$query->where('product_description.name', 'LIKE', '%' . $search . '%');
|
||||||
|
});
|
||||||
|
|
||||||
|
$total = $productsQuery->count();
|
||||||
|
$lastPage = PaginationHelper::calculateLastPage($total, $perPage);
|
||||||
|
$hasMore = $page + 1 <= $lastPage;
|
||||||
|
|
||||||
|
$products = $productsQuery
|
||||||
|
->forPage($page, $perPage)
|
||||||
|
->orderBy($mainpageProducts === 'latest' ? 'date_modified' : 'viewed', 'DESC')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$productIds = Arr::pluck($products, 'product_id');
|
||||||
|
$productsImages = [];
|
||||||
|
|
||||||
|
if ($productIds) {
|
||||||
|
$productsImages = $this->queryBuilder->newQuery()
|
||||||
|
->select([
|
||||||
|
'products_images.product_id' => 'product_id',
|
||||||
|
'products_images.image' => 'image',
|
||||||
|
])
|
||||||
|
->from(db_table('product_image'), 'products_images')
|
||||||
|
->orderBy('products_images.sort_order')
|
||||||
|
->whereIn('product_id', $productIds)
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
$productsImagesMap = [];
|
||||||
|
foreach ($productsImages as $item) {
|
||||||
|
$productsImagesMap[$item['product_id']][] = [
|
||||||
|
'url' => $this->ocImageTool->resize($item['image'], $imageWidth, $imageHeight, 'placeholder.png'),
|
||||||
|
'alt' => 'Product Image',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'data' => array_map(function ($product) use ($productsImagesMap, $imageWidth, $imageHeight) {
|
||||||
|
$allImages = [];
|
||||||
|
|
||||||
|
$image = $this->ocImageTool->resize(
|
||||||
|
$product['product_image'],
|
||||||
|
$imageWidth,
|
||||||
|
$imageHeight,
|
||||||
|
'placeholder.png'
|
||||||
|
);
|
||||||
|
|
||||||
|
$allImages[] = [
|
||||||
|
'url' => $image,
|
||||||
|
'alt' => $product['product_name'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$price = $this->currency->format(
|
||||||
|
$this->tax->calculate(
|
||||||
|
$product['price'],
|
||||||
|
$product['tax_class_id'],
|
||||||
|
$this->settings->get('oc_config_tax'),
|
||||||
|
),
|
||||||
|
$this->settings->get('oc_default_currency'),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (! empty($productsImagesMap[$product['product_id']])) {
|
||||||
|
$allImages = array_merge($allImages, $productsImagesMap[$product['product_id']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => (int) $product['product_id'],
|
||||||
|
'product_quantity' => (int) $product['product_quantity'],
|
||||||
|
'name' => $product['product_name'],
|
||||||
|
'price' => $price,
|
||||||
|
'images' => $allImages,
|
||||||
|
];
|
||||||
|
}, $products),
|
||||||
|
|
||||||
|
'meta' => [
|
||||||
|
'currentCategoryName' => $categoryName,
|
||||||
|
'hasMore' => $hasMore,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function getProduct(
|
||||||
|
int $productId,
|
||||||
|
int $languageId,
|
||||||
|
int $imageWidth,
|
||||||
|
int $imageHeight,
|
||||||
|
int $imageFullWidth,
|
||||||
|
int $imageFullHeight
|
||||||
|
): array {
|
||||||
|
$product = $this->queryBuilder->newQuery()
|
||||||
|
->select([
|
||||||
|
'products.product_id' => 'product_id',
|
||||||
|
'product_description.name' => 'product_name',
|
||||||
|
'product_description.description' => 'product_description',
|
||||||
|
'products.price' => 'price',
|
||||||
|
'products.minimum' => 'minimum',
|
||||||
|
'products.quantity' => 'quantity',
|
||||||
|
'products.image' => 'product_image',
|
||||||
|
'products.tax_class_id' => 'tax_class_id',
|
||||||
|
'manufacturer.name' => 'product_manufacturer',
|
||||||
|
])
|
||||||
|
->from(db_table('product'), 'products')
|
||||||
|
->join(
|
||||||
|
db_table('product_description') . ' AS product_description',
|
||||||
|
function (JoinClause $join) use ($languageId) {
|
||||||
|
$join->on('products.product_id', '=', 'product_description.product_id')
|
||||||
|
->where('product_description.language_id', '=', $languageId);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
->leftJoin(
|
||||||
|
db_table('manufacturer') . ' AS manufacturer',
|
||||||
|
function (JoinClause $join) {
|
||||||
|
$join->on('products.manufacturer_id', '=', 'manufacturer.manufacturer_id');
|
||||||
|
}
|
||||||
|
)
|
||||||
|
->where('products.product_id', '=', $productId)
|
||||||
|
->limit(1)
|
||||||
|
->firstOrNull();
|
||||||
|
|
||||||
|
if (! $product) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$productsImages = $this->queryBuilder->newQuery()
|
||||||
|
->select([
|
||||||
|
'products_images.product_id' => 'product_id',
|
||||||
|
'products_images.image' => 'image',
|
||||||
|
])
|
||||||
|
->from(db_table('product_image'), 'products_images')
|
||||||
|
->orderBy('products_images.sort_order')
|
||||||
|
->where('products_images.product_id', '=', $productId)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$imagePaths = [];
|
||||||
|
$imagePaths[] = $product['product_image'];
|
||||||
|
foreach ($productsImages as $item) {
|
||||||
|
$imagePaths[] = $item['image'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$images = [];
|
||||||
|
foreach ($imagePaths as $imagePath) {
|
||||||
|
[$width, $height] = $this->ocImageTool->getRealSize($imagePath);
|
||||||
|
$images[] = [
|
||||||
|
'thumbnailURL' => $this->ocImageTool->resize($imagePath, $imageWidth, $imageHeight, 'placeholder.png'),
|
||||||
|
'largeURL' => $this->ocImageTool->resize(
|
||||||
|
$imagePath,
|
||||||
|
$imageFullWidth,
|
||||||
|
$imageFullHeight,
|
||||||
|
'placeholder.png'
|
||||||
|
),
|
||||||
|
'width' => $width,
|
||||||
|
'height' => $height,
|
||||||
|
'alt' => $product['product_name'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$price = $this->currency->format(
|
||||||
|
$this->tax->calculate(
|
||||||
|
$product['price'],
|
||||||
|
$product['tax_class_id'],
|
||||||
|
$this->settings->get('oc_config_tax'),
|
||||||
|
),
|
||||||
|
$this->settings->get('oc_default_currency'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $product['product_id'],
|
||||||
|
'name' => $product['product_name'],
|
||||||
|
'description' => html_entity_decode($product['product_description']),
|
||||||
|
'manufacturer' => $product['product_manufacturer'],
|
||||||
|
'price' => $price,
|
||||||
|
'minimum' => $product['minimum'],
|
||||||
|
'quantity' => $product['quantity'],
|
||||||
|
'images' => $images,
|
||||||
|
'options' => $this->loadProductOptions($product),
|
||||||
|
'attributes' => $this->loadProductAttributes($product['product_id']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadProductOptions($product): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
$productId = $product['product_id'];
|
||||||
|
$taxClassId = $product['tax_class_id'];
|
||||||
|
|
||||||
|
$options = $this->ocModelCatalogProduct->getProductOptions($productId);
|
||||||
|
$ocConfigTax = $this->settings->get('oc_config_tax');
|
||||||
|
$ocDefaultCurrency = $this->settings->get('oc_default_currency');
|
||||||
|
|
||||||
|
foreach ($options as $option) {
|
||||||
|
$product_option_value_data = [];
|
||||||
|
|
||||||
|
foreach ($option['product_option_value'] as $option_value) {
|
||||||
|
if (! $option_value['subtract'] || ($option_value['quantity'] > 0)) {
|
||||||
|
if ((float) $option_value['price']) {
|
||||||
|
$priceWithTax = $this->tax->calculate(
|
||||||
|
$option_value['price'],
|
||||||
|
$taxClassId,
|
||||||
|
$ocConfigTax ? 'Р' : false,
|
||||||
|
);
|
||||||
|
|
||||||
|
$price = $this->currency->format($priceWithTax, $ocDefaultCurrency);
|
||||||
|
} else {
|
||||||
|
$price = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$product_option_value_data[] = [
|
||||||
|
'product_option_value_id' => (int) $option_value['product_option_value_id'],
|
||||||
|
'option_value_id' => (int) $option_value['option_value_id'],
|
||||||
|
'name' => $option_value['name'],
|
||||||
|
'image' => $this->ocImageTool->resize($option_value['image'], 50, 50),
|
||||||
|
'price' => $price,
|
||||||
|
'price_prefix' => $option_value['price_prefix'],
|
||||||
|
'selected' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result[] = [
|
||||||
|
'product_option_id' => (int) $option['product_option_id'],
|
||||||
|
'values' => $product_option_value_data,
|
||||||
|
'option_id' => (int) $option['option_id'],
|
||||||
|
'name' => $option['name'],
|
||||||
|
'type' => $option['type'],
|
||||||
|
'value' => $option['value'],
|
||||||
|
'required' => filter_var($option['required'], FILTER_VALIDATE_BOOLEAN),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private function loadProductAttributes(int $productId): array
|
||||||
|
{
|
||||||
|
$this->oc->load->model('catalog/product');
|
||||||
|
|
||||||
|
return $this->oc->model_catalog_product->getProductAttributes($productId);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user