Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
45 changes: 32 additions & 13 deletions src/Assembler/CategoryAssembler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}

Expand All @@ -131,40 +131,59 @@ 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
if ($entityIds === null) {
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
if (sizeof($entityIds) === 0 || !isset($category[MemberNames::NAME])) {
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
Expand Down
66 changes: 52 additions & 14 deletions src/Repositories/CategoryVarcharRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
}

/**
Expand All @@ -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] = [];
}
}
6 changes: 3 additions & 3 deletions src/Repositories/CategoryVarcharRepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading