diff --git a/CHANGELOG.md b/CHANGELOG.md index d80a4a2..cadb015 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 bc8a610..f08db38 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 f8caf91..822ba94 100644 --- a/src/Repositories/CategoryVarcharRepository.php +++ b/src/Repositories/CategoryVarcharRepository.php @@ -28,6 +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 int BATCH_SIZE = 1000; + + /** + * Cache for the category varchar values loaded by entity ID. + * + * @var array + */ + private array $cacheByEntityId = []; /** * Initializes the repository's prepared statements. @@ -42,21 +55,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, int $batchSize = self::BATCH_SIZE): array { + $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 +99,22 @@ 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 c404349..7fcfcd5 100644 --- a/src/Repositories/CategoryVarcharRepositoryInterface.php +++ b/src/Repositories/CategoryVarcharRepositoryInterface.php @@ -25,16 +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 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, int $batchSize = 1000): array; /** * Returns the category varchar values for the categories with