1010
1111final class ExternalPayloadStorage
1212{
13+ private const MAX_CACHE_ENTRIES = 128 ;
14+
15+ private const MAX_CACHE_BYTES = 16777216 ;
16+
17+ /**
18+ * @var array<string, array{data: string, bytes: int, used_at: int}>
19+ */
20+ private static array $ verifiedCache = [];
21+
22+ private static int $ verifiedCacheBytes = 0 ;
23+
24+ private static int $ verifiedCacheSequence = 0 ;
25+
1326 public static function store (
1427 ExternalPayloadStorageDriver $ driver ,
1528 string $ data ,
@@ -24,6 +37,13 @@ public static function store(
2437
2538 public static function fetch (ExternalPayloadStorageDriver $ driver , ExternalPayloadReference $ reference ): string
2639 {
40+ $ cacheKey = self ::cacheKey ($ reference );
41+ if (isset (self ::$ verifiedCache [$ cacheKey ])) {
42+ self ::$ verifiedCache [$ cacheKey ]['used_at ' ] = ++self ::$ verifiedCacheSequence ;
43+
44+ return self ::$ verifiedCache [$ cacheKey ]['data ' ];
45+ }
46+
2747 $ data = $ driver ->get ($ reference ->uri );
2848
2949 if (strlen ($ data ) !== $ reference ->sizeBytes ) {
@@ -34,6 +54,73 @@ public static function fetch(ExternalPayloadStorageDriver $driver, ExternalPaylo
3454 throw new ExternalPayloadIntegrityException ('External payload hash does not match its reference. ' );
3555 }
3656
57+ self ::rememberVerified ($ cacheKey , $ data );
58+
3759 return $ data ;
3860 }
61+
62+ public static function flushVerifiedCache (): void
63+ {
64+ self ::$ verifiedCache = [];
65+ self ::$ verifiedCacheBytes = 0 ;
66+ self ::$ verifiedCacheSequence = 0 ;
67+ }
68+
69+ private static function cacheKey (ExternalPayloadReference $ reference ): string
70+ {
71+ return implode ("\n" , [
72+ $ reference ->uri ,
73+ $ reference ->sha256 ,
74+ (string ) $ reference ->sizeBytes ,
75+ $ reference ->codec ,
76+ ]);
77+ }
78+
79+ private static function rememberVerified (string $ cacheKey , string $ data ): void
80+ {
81+ $ bytes = strlen ($ data );
82+ if ($ bytes > self ::MAX_CACHE_BYTES ) {
83+ return ;
84+ }
85+
86+ if (isset (self ::$ verifiedCache [$ cacheKey ])) {
87+ self ::$ verifiedCacheBytes -= self ::$ verifiedCache [$ cacheKey ]['bytes ' ];
88+ }
89+
90+ self ::$ verifiedCache [$ cacheKey ] = [
91+ 'data ' => $ data ,
92+ 'bytes ' => $ bytes ,
93+ 'used_at ' => ++self ::$ verifiedCacheSequence ,
94+ ];
95+ self ::$ verifiedCacheBytes += $ bytes ;
96+
97+ self ::evictVerifiedCache ();
98+ }
99+
100+ private static function evictVerifiedCache (): void
101+ {
102+ while (
103+ count (self ::$ verifiedCache ) > self ::MAX_CACHE_ENTRIES
104+ || self ::$ verifiedCacheBytes > self ::MAX_CACHE_BYTES
105+ ) {
106+ $ oldestKey = null ;
107+ $ oldestUsedAt = PHP_INT_MAX ;
108+
109+ foreach (self ::$ verifiedCache as $ key => $ entry ) {
110+ if ($ entry ['used_at ' ] < $ oldestUsedAt ) {
111+ $ oldestKey = $ key ;
112+ $ oldestUsedAt = $ entry ['used_at ' ];
113+ }
114+ }
115+
116+ if ($ oldestKey === null ) {
117+ self ::$ verifiedCacheBytes = 0 ;
118+
119+ return ;
120+ }
121+
122+ self ::$ verifiedCacheBytes -= self ::$ verifiedCache [$ oldestKey ]['bytes ' ];
123+ unset(self ::$ verifiedCache [$ oldestKey ]);
124+ }
125+ }
39126}
0 commit comments