final protected function getMultipleCacheItems(
        array $ids,
        string $keyPrefix,
        callable $missingLoader,
        callable $loadedTagger,
        array $keySuffixes = []
    ): array {
        if (empty($ids)) {
            return [];
        }

        // Generate unique cache keys
        $cacheKeys = [];
        $cacheKeysToIdMap = [];
        foreach (\array_unique($ids) as $id) {
            $key = $keyPrefix . $id . ($keySuffixes[$id] ?? '');
            $cacheKeys[] = $key;
            $cacheKeysToIdMap[$key] = $id;
        }

        // Load cache items by cache keys (will contain hits and misses)
        $list = [];
        $cacheMisses = [];
        foreach ($this->cache->getItems($cacheKeys) as $key => $cacheItem) {
            $id = $cacheKeysToIdMap[$key];
            if ($cacheItem->isHit()) {
                $list[$id] = $cacheItem->get();
            } else {
                $cacheMisses[] = $id;
                $list[$id] = $cacheItem;
            }
        }

        // No misses, return completely cached list
        if (empty($cacheMisses)) {
            return $list;
        }

        // Load missing items, save to cache & apply to list if found
        $loadedList = $missingLoader($cacheMisses);
        foreach ($cacheMisses as $id) {
            if (isset($loadedList[$id])) {
                $this->cache->save(
                    $list[$id]
                        ->set($loadedList[$id])
                        ->tag($loadedTagger($loadedList[$id]))
                );
                $list[$id] = $loadedList[$id];
            } else {
                unset($list[$id]);
            }
        }

        return $list;
    }