Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.12% covered (warning)
78.12%
25 / 32
33.33% covered (danger)
33.33%
2 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
WebmailMessageCacheManager
77.42% covered (warning)
77.42%
24 / 31
33.33% covered (danger)
33.33%
2 / 6
18.95
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 buildMessagesCacheKey
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 getMailboxCacheVersion
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 invalidateMailboxMessagesCache
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 getCachedMessages
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 saveCachedMessages
50.00% covered (danger)
50.00%
2 / 4
0.00% covered (danger)
0.00%
0 / 1
4.12
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\Mail\Application\Service\Message;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Modules\Mail\Domain\Model\MailMessageSummaryDto;
12use App\Modules\Mail\Domain\Model\MailSearchCriteriaDto;
13use Throwable;
14use Yiisoft\Cache\CacheInterface;
15
16/**
17 * Webmail Message Cache Manager.
18 *
19 * Handles query result caching and versioned invalidation for mailbox folder message lists.
20 *
21 * @package App\Modules\Mail\Application\Service\Message
22 */
23final readonly class WebmailMessageCacheManager
24{
25    /**
26     * WebmailMessageCacheManager constructor.
27     *
28     * @param CacheInterface|null $cache Cache backend implementation.
29     */
30    public function __construct(
31        private ?CacheInterface $cache = null,
32    ) {
33    }
34
35    /**
36     * Builds unique cache key for message list query.
37     *
38     * @param int                   $mailboxId Mailbox identifier.
39     * @param MailSearchCriteriaDto $criteria  Search and filter criteria.
40     * @param int                   $page      Page index.
41     * @param int                   $perPage   Items per page.
42     * @return string Unique cache key.
43     */
44    public function buildMessagesCacheKey(
45        int $mailboxId,
46        MailSearchCriteriaDto $criteria,
47        int $page,
48        int $perPage,
49    ): string {
50        $ver = $this->getMailboxCacheVersion($mailboxId);
51        $criteriaHash = hash('sha256', serialize([
52            $criteria->folder,
53            $criteria->query,
54            $criteria->isUnreadOnly,
55            $criteria->isFlaggedOnly,
56            $criteria->hasAttachmentsOnly,
57            $criteria->sortDirection,
58        ]));
59
60        return sprintf('webmail_msgs_%d_v%d_%s_p%d_l%d', $mailboxId, $ver, $criteriaHash, $page, $perPage);
61    }
62
63    /**
64     * Retrieves current cache invalidation version for a mailbox.
65     *
66     * @param int $mailboxId Mailbox identifier.
67     * @return int Invalidation version integer.
68     */
69    public function getMailboxCacheVersion(int $mailboxId): int
70    {
71        if ($this->cache === null) {
72            return 1;
73        }
74
75        try {
76            $ver = $this->cache->psr()->get('webmail_ver_' . $mailboxId);
77            return is_int($ver) ? $ver : 1;
78        } catch (Throwable) {
79            return 1;
80        }
81    }
82
83    /**
84     * Invalidates all cached message lists for a mailbox.
85     *
86     * @param int $mailboxId Mailbox identifier.
87     */
88    public function invalidateMailboxMessagesCache(int $mailboxId): void
89    {
90        if ($this->cache !== null) {
91            try {
92                $ver = $this->getMailboxCacheVersion($mailboxId);
93                $this->cache->psr()->set('webmail_ver_' . $mailboxId, $ver + 1, 86400 * 30);
94            } catch (Throwable) {
95                // Ignore cache invalidation error
96            }
97        }
98    }
99
100    /**
101     * Retrieves cached message list payload.
102     *
103     * @param string $cacheKey Cache key.
104     * @return array{messages: array<MailMessageSummaryDto>, total: int, is_cached: bool}|null Cached data or null.
105     */
106    public function getCachedMessages(string $cacheKey): ?array
107    {
108        if ($this->cache === null) {
109            return null;
110        }
111
112        try {
113            /** @var array{messages: array<MailMessageSummaryDto>, total: int, is_cached: bool}|null $data */
114            $data = $this->cache->psr()->get($cacheKey);
115            return is_array($data) ? $data : null;
116        } catch (Throwable) {
117            return null;
118        }
119    }
120
121    /**
122     * Stores message list payload into cache backend with 1 hour TTL.
123     *
124     * @param string               $cacheKey Cache key.
125     * @param array<string, mixed> $payload Cached payload.
126     */
127    public function saveCachedMessages(string $cacheKey, array $payload): void
128    {
129        if ($this->cache === null) {
130            return;
131        }
132
133        try {
134            $this->cache->psr()->set($cacheKey, $payload, 3600);
135        } catch (Throwable) {
136            // Ignore cache write failure
137        }
138    }
139}