From d7306addfea3a409a52f57ae0bac79f4d3c4461f Mon Sep 17 00:00:00 2001 From: Andrii Mazur Date: Mon, 31 Aug 2026 18:24:07 +0300 Subject: [PATCH 1/2] Batch category path resolution --- src/Assembler/CategoryAssembler.php | 45 +++++++++---- .../CategoryVarcharRepository.php | 63 ++++++++++++++----- .../CategoryVarcharRepositoryInterface.php | 7 ++- 3 files changed, 85 insertions(+), 30 deletions(-) diff --git a/src/Assembler/CategoryAssembler.php b/src/Assembler/CategoryAssembler.php index bc8a6105..0d0c40dd 100644 --- a/src/Assembler/CategoryAssembler.php +++ b/src/Assembler/CategoryAssembler.php @@ -94,7 +94,7 @@ public function getCategoriesWithResolvedPath() continue; } - // cut-off the root category + // cut off the root category array_shift($entityIds); // continue with the next category if no entity IDs are available @@ -111,7 +111,7 @@ public function getCategoriesWithResolvedPath() } } - // append the catogory with the string path as key + // append the category with the string path as key $categories[$this->serializer->implode($path)] = $category; } @@ -131,14 +131,16 @@ public function getCategoriesWithResolvedPath() public function getCategoriesWithResolvedPathByStoreView($storeViewId) { // prepare the categories - $categories = array(); + $categories = []; // load the categories from the database $availableCategories = $this->categoryRepository->findAllByStoreView($storeViewId); - // create the array with the resolved category path as keys + // step 1: collect the path entity IDs + $prepared = []; + $allEntityIds = []; foreach ($availableCategories as $category) { - // expload the entity IDs from the category path + // explode the entity IDs from the category path $entityIds = $this->serializer->explode($category[MemberNames::PATH]); // continue if nothing to explode @@ -146,7 +148,7 @@ public function getCategoriesWithResolvedPathByStoreView($storeViewId) continue; } - // cut-off the root category + // cut off the root category array_shift($entityIds); // continue with the next category if no entity IDs are available @@ -154,17 +156,34 @@ public function getCategoriesWithResolvedPathByStoreView($storeViewId) continue; } - // initialize the array for the path elements - $path = array(); + $prepared[] = ['category' => $category, 'entityIds' => $entityIds]; foreach ($entityIds as $entityId) { - $cat = $this->categoryVarcharRepository->findByEntityId($entityId); - if ($cat && isset($cat[MemberNames::VALUE])) { - $path[] = $cat[MemberNames::VALUE]; + $allEntityIds[(int) $entityId] = true; + } + } + + // step 2: load all category names + $nameByEntityId = []; + if ($allEntityIds !== []) { + foreach ($this->categoryVarcharRepository->findAllByEntityIds(array_keys($allEntityIds)) as $row) { + if (isset($row[MemberNames::ENTITY_ID]) && isset($row[MemberNames::VALUE])) { + $nameByEntityId[(int) $row[MemberNames::ENTITY_ID]] = $row[MemberNames::VALUE]; } } + } - // append the catogory with the string path as key - $categories[$this->serializer->implode($path)] = $category; + // step 3: resolve each category's path from index + foreach ($prepared as $item) { + // initialize the array for the path elements + $path = []; + foreach ($item['entityIds'] as $entityId) { + if (isset($nameByEntityId[(int) $entityId])) { + $path[] = $nameByEntityId[(int) $entityId]; + } + } + + // append the category with the string path as key + $categories[$this->serializer->implode($path)] = $item['category']; } // return array with the categories diff --git a/src/Repositories/CategoryVarcharRepository.php b/src/Repositories/CategoryVarcharRepository.php index f8caf912..91ed22f6 100644 --- a/src/Repositories/CategoryVarcharRepository.php +++ b/src/Repositories/CategoryVarcharRepository.php @@ -29,6 +29,20 @@ class CategoryVarcharRepository extends AbstractRepository implements CategoryVarcharRepositoryInterface { + /** + * The default number of entity IDs to load in a single batch query. + * + * @var integer + */ + private const BATCH_SIZE = 1000; + + /** + * Cache for the category varchar values loaded by entity ID. + * + * @var array + */ + private $cacheByEntityId = []; + /** * Initializes the repository's prepared statements. * @@ -42,21 +56,38 @@ public function init() * Returns the category varchar values for the categories with * the passed with the passed entity IDs. * - * @param array $entityIds The array with the category IDs + * @param array $entityIds The array with the category IDs + * @param integer $batchSize The maximum number of entity IDs per query * - * @return mixed The category varchar values + * @return array The category varchar values */ - public function findAllByEntityIds(array $entityIds) + public function findAllByEntityIds(array $entityIds, $batchSize = self::BATCH_SIZE) { + $result = []; + $entityIds = array_values(array_unique(array_map('intval', $entityIds))); - // prepare the cache key - $vals = implode(',', $entityIds); - $sql = str_replace('?', $vals, $this->loadStatement(SqlStatementKeys::CATEGORY_VARCHARS_BY_ENTITY_IDS)); + if ($entityIds === []) { + return $result; + } - // load the categories with the passed values and return them - if ($stmt = $this->getConnection()->query($sql)) { - return $stmt->fetchAll(); + $batchSize = (int) $batchSize; + if ($batchSize < 1) { + $batchSize = self::BATCH_SIZE; + } + + foreach (array_chunk($entityIds, $batchSize) as $chunk) { + $vals = implode(',', $chunk); + $sql = str_replace('?', $vals, $this->loadStatement(SqlStatementKeys::CATEGORY_VARCHARS_BY_ENTITY_IDS)); + + if ($stmt = $this->getConnection()->query($sql)) { + foreach ($stmt->fetchAll() as $row) { + $result[] = $row; + } + } } + + // return the collected result rows + return $result; } /** @@ -69,14 +100,18 @@ public function findAllByEntityIds(array $entityIds) */ public function findByEntityId($entityId) { + $entityId = (int) $entityId; + + if (array_key_exists($entityId, $this->cacheByEntityId)) { + return $this->cacheByEntityId[$entityId]; + } - // prepare the cache key - $sql = str_replace('?', $entityId, $this->loadStatement(SqlStatementKeys::CATEGORY_VARCHARS_BY_ENTITY_IDS)); + $sql = str_replace('?', (string) $entityId, $this->loadStatement(SqlStatementKeys::CATEGORY_VARCHARS_BY_ENTITY_IDS)); - // load the categories with the passed values and return them if ($stmt = $this->getConnection()->query($sql)) { - return $stmt->fetch(); + return $this->cacheByEntityId[$entityId] = $stmt->fetch(); } - return []; + + return $this->cacheByEntityId[$entityId] = []; } } diff --git a/src/Repositories/CategoryVarcharRepositoryInterface.php b/src/Repositories/CategoryVarcharRepositoryInterface.php index c4043493..72df2ff4 100644 --- a/src/Repositories/CategoryVarcharRepositoryInterface.php +++ b/src/Repositories/CategoryVarcharRepositoryInterface.php @@ -30,11 +30,12 @@ interface CategoryVarcharRepositoryInterface extends RepositoryInterface * Returns the category varchar values for the categories with * the passed with the passed entity IDs. * - * @param array $entityIds The array with the category IDs + * @param array $entityIds The array with the category IDs + * @param integer $batchSize The maximum number of entity IDs per query * - * @return mixed The category varchar values + * @return array The category varchar values */ - public function findAllByEntityIds(array $entityIds); + public function findAllByEntityIds(array $entityIds, $batchSize = 1000); /** * Returns the category varchar values for the categories with From 3a7eeb8be08080088fc8435ef368310fe196e478 Mon Sep 17 00:00:00 2001 From: Victor Svizev Date: Thu, 3 Sep 2026 21:32:26 +0200 Subject: [PATCH 2/2] Update CHANGELOG.md. Refactor code --- CHANGELOG.md | 6 ++++++ src/Assembler/CategoryAssembler.php | 8 ++++---- src/Repositories/CategoryVarcharRepository.php | 17 ++++++++++------- .../CategoryVarcharRepositoryInterface.php | 5 ++--- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d80a4a28..cadb015c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# Version 18.3.0 + +## Features + +* Optimize category path resolution with batched entity ID processing and caching + # Version 18.2.0 ## Features diff --git a/src/Assembler/CategoryAssembler.php b/src/Assembler/CategoryAssembler.php index 0d0c40dd..f08db384 100644 --- a/src/Assembler/CategoryAssembler.php +++ b/src/Assembler/CategoryAssembler.php @@ -158,7 +158,7 @@ public function getCategoriesWithResolvedPathByStoreView($storeViewId) $prepared[] = ['category' => $category, 'entityIds' => $entityIds]; foreach ($entityIds as $entityId) { - $allEntityIds[(int) $entityId] = true; + $allEntityIds[(int)$entityId] = true; } } @@ -167,7 +167,7 @@ public function getCategoriesWithResolvedPathByStoreView($storeViewId) if ($allEntityIds !== []) { foreach ($this->categoryVarcharRepository->findAllByEntityIds(array_keys($allEntityIds)) as $row) { if (isset($row[MemberNames::ENTITY_ID]) && isset($row[MemberNames::VALUE])) { - $nameByEntityId[(int) $row[MemberNames::ENTITY_ID]] = $row[MemberNames::VALUE]; + $nameByEntityId[(int)$row[MemberNames::ENTITY_ID]] = $row[MemberNames::VALUE]; } } } @@ -177,8 +177,8 @@ public function getCategoriesWithResolvedPathByStoreView($storeViewId) // initialize the array for the path elements $path = []; foreach ($item['entityIds'] as $entityId) { - if (isset($nameByEntityId[(int) $entityId])) { - $path[] = $nameByEntityId[(int) $entityId]; + if (isset($nameByEntityId[(int)$entityId])) { + $path[] = $nameByEntityId[(int)$entityId]; } } diff --git a/src/Repositories/CategoryVarcharRepository.php b/src/Repositories/CategoryVarcharRepository.php index 91ed22f6..822ba941 100644 --- a/src/Repositories/CategoryVarcharRepository.php +++ b/src/Repositories/CategoryVarcharRepository.php @@ -28,20 +28,19 @@ */ class CategoryVarcharRepository extends AbstractRepository implements CategoryVarcharRepositoryInterface { - /** * The default number of entity IDs to load in a single batch query. * * @var integer */ - private const BATCH_SIZE = 1000; + private const int BATCH_SIZE = 1000; /** * Cache for the category varchar values loaded by entity ID. * * @var array */ - private $cacheByEntityId = []; + private array $cacheByEntityId = []; /** * Initializes the repository's prepared statements. @@ -61,7 +60,7 @@ public function init() * * @return array The category varchar values */ - public function findAllByEntityIds(array $entityIds, $batchSize = self::BATCH_SIZE) + public function findAllByEntityIds(array $entityIds, int $batchSize = self::BATCH_SIZE): array { $result = []; $entityIds = array_values(array_unique(array_map('intval', $entityIds))); @@ -70,7 +69,7 @@ public function findAllByEntityIds(array $entityIds, $batchSize = self::BATCH_SI return $result; } - $batchSize = (int) $batchSize; + $batchSize = (int)$batchSize; if ($batchSize < 1) { $batchSize = self::BATCH_SIZE; } @@ -100,13 +99,17 @@ public function findAllByEntityIds(array $entityIds, $batchSize = self::BATCH_SI */ public function findByEntityId($entityId) { - $entityId = (int) $entityId; + $entityId = (int)$entityId; if (array_key_exists($entityId, $this->cacheByEntityId)) { return $this->cacheByEntityId[$entityId]; } - $sql = str_replace('?', (string) $entityId, $this->loadStatement(SqlStatementKeys::CATEGORY_VARCHARS_BY_ENTITY_IDS)); + $sql = str_replace( + '?', + (string)$entityId, + $this->loadStatement(SqlStatementKeys::CATEGORY_VARCHARS_BY_ENTITY_IDS) + ); if ($stmt = $this->getConnection()->query($sql)) { return $this->cacheByEntityId[$entityId] = $stmt->fetch(); diff --git a/src/Repositories/CategoryVarcharRepositoryInterface.php b/src/Repositories/CategoryVarcharRepositoryInterface.php index 72df2ff4..7fcfcd50 100644 --- a/src/Repositories/CategoryVarcharRepositoryInterface.php +++ b/src/Repositories/CategoryVarcharRepositoryInterface.php @@ -25,17 +25,16 @@ */ interface CategoryVarcharRepositoryInterface extends RepositoryInterface { - /** * Returns the category varchar values for the categories with * the passed with the passed entity IDs. * - * @param array $entityIds The array with the category IDs + * @param array $entityIds The array with the category IDs * @param integer $batchSize The maximum number of entity IDs per query * * @return array The category varchar values */ - public function findAllByEntityIds(array $entityIds, $batchSize = 1000); + public function findAllByEntityIds(array $entityIds, int $batchSize = 1000): array; /** * Returns the category varchar values for the categories with