• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / caching / 20835655444

08 Jan 2026 11:36PM UTC coverage: 87.202%. Remained the same
20835655444

push

github

dg
used attribute Deprecated

586 of 672 relevant lines covered (87.2%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.55
/src/Caching/Cache.php
1
<?php
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Nette\Caching;
11

12
use Nette;
13
use function array_keys, array_map, array_shift, array_slice, array_unique, array_values, constant, count, defined, filemtime, func_get_args, get_class, is_array, is_object, is_scalar, serialize, substr, time;
14

15

16
/**
17
 * Implements the cache for a application.
18
 */
19
class Cache
20
{
21
        /** dependency */
22
        public const
23
                Priority = 'priority',
24
                Expire = 'expire',
25
                Sliding = 'sliding',
26
                Tags = 'tags',
27
                Files = 'files',
28
                Items = 'items',
29
                Constants = 'consts',
30
                Callbacks = 'callbacks',
31
                Namespaces = 'namespaces',
32
                All = 'all';
33

34
        #[\Deprecated('use Cache::Priority')]
35
        public const PRIORITY = self::Priority;
36

37
        #[\Deprecated('use Cache::Expire')]
38
        public const EXPIRATION = self::Expire;
39

40
        #[\Deprecated('use Cache::Expire')]
41
        public const EXPIRE = self::Expire;
42

43
        #[\Deprecated('use Cache::Sliding')]
44
        public const SLIDING = self::Sliding;
45

46
        #[\Deprecated('use Cache::Tags')]
47
        public const TAGS = self::Tags;
48

49
        #[\Deprecated('use Cache::Files')]
50
        public const FILES = self::Files;
51

52
        #[\Deprecated('use Cache::Items')]
53
        public const ITEMS = self::Items;
54

55
        #[\Deprecated('use Cache::Constants')]
56
        public const CONSTS = self::Constants;
57

58
        #[\Deprecated('use Cache::Callbacks')]
59
        public const CALLBACKS = self::Callbacks;
60

61
        #[\Deprecated('use Cache::Namespaces')]
62
        public const NAMESPACES = self::Namespaces;
63

64
        #[\Deprecated('use Cache::All')]
65
        public const ALL = self::All;
66

67
        /** @internal */
68
        public const NamespaceSeparator = "\x00";
69

70
        private Storage $storage;
71
        private string $namespace;
72

73

74
        public function __construct(Storage $storage, ?string $namespace = null)
1✔
75
        {
76
                $this->storage = $storage;
1✔
77
                $this->namespace = $namespace . self::NamespaceSeparator;
1✔
78
        }
1✔
79

80

81
        /**
82
         * Returns cache storage.
83
         */
84
        final public function getStorage(): Storage
85
        {
86
                return $this->storage;
×
87
        }
88

89

90
        /**
91
         * Returns cache namespace.
92
         */
93
        final public function getNamespace(): string
94
        {
95
                return substr($this->namespace, 0, -1);
1✔
96
        }
97

98

99
        /**
100
         * Returns new nested cache object.
101
         */
102
        public function derive(string $namespace): static
1✔
103
        {
104
                return new static($this->storage, $this->namespace . $namespace);
1✔
105
        }
106

107

108
        /**
109
         * Reads the specified item from the cache or generate it.
110
         */
111
        public function load(mixed $key, ?callable $generator = null, ?array $dependencies = null): mixed
1✔
112
        {
113
                $storageKey = $this->generateKey($key);
1✔
114
                $data = $this->storage->read($storageKey);
1✔
115
                if ($data === null && $generator) {
1✔
116
                        $this->storage->lock($storageKey);
1✔
117
                        try {
118
                                $data = $generator(...[&$dependencies]);
1✔
119
                        } catch (\Throwable $e) {
1✔
120
                                $this->storage->remove($storageKey);
1✔
121
                                throw $e;
1✔
122
                        }
123

124
                        $this->save($key, $data, $dependencies);
1✔
125
                }
126

127
                return $data;
1✔
128
        }
129

130

131
        /**
132
         * Reads multiple items from the cache.
133
         */
134
        public function bulkLoad(array $keys, ?callable $generator = null): array
1✔
135
        {
136
                if (count($keys) === 0) {
1✔
137
                        return [];
×
138
                }
139

140
                foreach ($keys as $key) {
1✔
141
                        if (!is_scalar($key)) {
1✔
142
                                throw new Nette\InvalidArgumentException('Only scalar keys are allowed in bulkLoad()');
1✔
143
                        }
144
                }
145

146
                $result = [];
1✔
147
                if (!$this->storage instanceof BulkReader) {
1✔
148
                        foreach ($keys as $key) {
1✔
149
                                $result[$key] = $this->load(
1✔
150
                                        $key,
1✔
151
                                        $generator
1✔
152
                                                ? fn(&$dependencies) => $generator(...[$key, &$dependencies])
1✔
153
                                                : null,
1✔
154
                                );
155
                        }
156

157
                        return $result;
1✔
158
                }
159

160
                $storageKeys = array_map($this->generateKey(...), $keys);
1✔
161
                $cacheData = $this->storage->bulkRead($storageKeys);
1✔
162
                foreach ($keys as $i => $key) {
1✔
163
                        $storageKey = $storageKeys[$i];
1✔
164
                        if (isset($cacheData[$storageKey])) {
1✔
165
                                $result[$key] = $cacheData[$storageKey];
1✔
166
                        } elseif ($generator) {
1✔
167
                                $result[$key] = $this->load($key, fn(&$dependencies) => $generator(...[$key, &$dependencies]));
1✔
168
                        } else {
169
                                $result[$key] = null;
1✔
170
                        }
171
                }
172

173
                return $result;
1✔
174
        }
175

176

177
        /**
178
         * Writes item into the cache.
179
         * Dependencies are:
180
         * - Cache::Priority => (int) priority
181
         * - Cache::Expire => (timestamp) expiration, infinite if null
182
         * - Cache::Sliding => (bool) use sliding expiration?
183
         * - Cache::Tags => (array) tags
184
         * - Cache::Files => (array|string) file names
185
         * - Cache::Items => (array|string) cache items
186
         * - Cache::Constants => (array|string) cache items
187
         * @return mixed  value itself
188
         * @throws Nette\InvalidArgumentException
189
         */
190
        public function save(mixed $key, mixed $data, ?array $dependencies = null): mixed
1✔
191
        {
192
                $key = $this->generateKey($key);
1✔
193

194
                if ($data instanceof \Closure) {
1✔
195
                        $this->storage->lock($key);
1✔
196
                        try {
197
                                $data = $data(...[&$dependencies]);
1✔
198
                        } catch (\Throwable $e) {
×
199
                                $this->storage->remove($key);
×
200
                                throw $e;
×
201
                        }
202
                }
203

204
                if ($data === null) {
1✔
205
                        $this->storage->remove($key);
1✔
206
                        return null;
1✔
207
                } else {
208
                        $dependencies = $this->completeDependencies($dependencies);
1✔
209
                        if (isset($dependencies[self::Expire]) && $dependencies[self::Expire] <= 0) {
1✔
210
                                $this->storage->remove($key);
1✔
211
                        } else {
212
                                $this->storage->write($key, $data, $dependencies);
1✔
213
                        }
214

215
                        return $data;
1✔
216
                }
217
        }
218

219

220
        /**
221
         * Writes multiple items into cache
222
         */
223
        public function bulkSave(array $items, ?array $dependencies = null): void
1✔
224
        {
225
                $write = $remove = [];
1✔
226

227
                if (!$this->storage instanceof BulkWriter) {
1✔
228
                        foreach ($items as $key => $data) {
1✔
229
                                $this->save($key, $data, $dependencies);
1✔
230
                        }
231
                        return;
1✔
232
                }
233

234
                $dependencies = $this->completeDependencies($dependencies);
1✔
235
                if (isset($dependencies[self::Expire]) && $dependencies[self::Expire] <= 0) {
1✔
236
                        $this->storage->bulkRemove(array_map($this->generateKey(...), array_keys($items)));
×
237
                        return;
×
238
                }
239

240
                foreach ($items as $key => $data) {
1✔
241
                        $key = $this->generateKey($key);
1✔
242
                        if ($data === null) {
1✔
243
                                $remove[] = $key;
×
244
                        } else {
245
                                $write[$key] = $data;
1✔
246
                        }
247
                }
248

249
                if ($remove) {
1✔
250
                        $this->storage->bulkRemove($remove);
×
251
                }
252

253
                if ($write) {
1✔
254
                        $this->storage->bulkWrite($write, $dependencies);
1✔
255
                }
256
        }
1✔
257

258

259
        private function completeDependencies(?array $dp): array
1✔
260
        {
261
                // convert expire into relative amount of seconds
262
                if (isset($dp[self::Expire])) {
1✔
263
                        $dp[self::Expire] = Nette\Utils\DateTime::from($dp[self::Expire])->format('U') - time();
1✔
264
                }
265

266
                // make list from TAGS
267
                if (isset($dp[self::Tags])) {
1✔
268
                        $dp[self::Tags] = array_values((array) $dp[self::Tags]);
1✔
269
                }
270

271
                // make list from NAMESPACES
272
                if (isset($dp[self::Namespaces])) {
1✔
273
                        $dp[self::Namespaces] = array_values((array) $dp[self::Namespaces]);
×
274
                }
275

276
                // convert FILES into CALLBACKS
277
                if (isset($dp[self::Files])) {
1✔
278
                        foreach (array_unique((array) $dp[self::Files]) as $item) {
1✔
279
                                $dp[self::Callbacks][] = [[self::class, 'checkFile'], $item, @filemtime($item) ?: null]; // @ - stat may fail
1✔
280
                        }
281

282
                        unset($dp[self::Files]);
1✔
283
                }
284

285
                // add namespaces to items
286
                if (isset($dp[self::Items])) {
1✔
287
                        $dp[self::Items] = array_unique(array_map($this->generateKey(...), (array) $dp[self::Items]));
1✔
288
                }
289

290
                // convert CONSTS into CALLBACKS
291
                if (isset($dp[self::Constants])) {
1✔
292
                        foreach (array_unique((array) $dp[self::Constants]) as $item) {
1✔
293
                                $dp[self::Callbacks][] = [[self::class, 'checkConst'], $item, constant($item)];
1✔
294
                        }
295

296
                        unset($dp[self::Constants]);
1✔
297
                }
298

299
                if (!is_array($dp)) {
1✔
300
                        $dp = [];
1✔
301
                }
302

303
                return $dp;
1✔
304
        }
305

306

307
        /**
308
         * Removes item from the cache.
309
         */
310
        public function remove(mixed $key): void
1✔
311
        {
312
                $this->save($key, null);
1✔
313
        }
1✔
314

315

316
        /**
317
         * Removes items from the cache by conditions.
318
         * Conditions are:
319
         * - Cache::Priority => (int) priority
320
         * - Cache::Tags => (array) tags
321
         * - Cache::All => true
322
         */
323
        public function clean(?array $conditions = null): void
1✔
324
        {
325
                $conditions = (array) $conditions;
1✔
326
                if (isset($conditions[self::Tags])) {
1✔
327
                        $conditions[self::Tags] = array_values((array) $conditions[self::Tags]);
1✔
328
                }
329

330
                $this->storage->clean($conditions);
1✔
331
        }
1✔
332

333

334
        /**
335
         * Caches results of function/method calls.
336
         */
337
        public function call(callable $function): mixed
1✔
338
        {
339
                $key = func_get_args();
1✔
340
                if (is_array($function) && is_object($function[0])) {
1✔
341
                        $key[0][0] = $function[0]::class;
1✔
342
                }
343

344
                return $this->load($key, fn() => $function(...array_slice($key, 1)));
1✔
345
        }
346

347

348
        /**
349
         * Caches results of function/method calls.
350
         */
351
        public function wrap(callable $function, ?array $dependencies = null): \Closure
1✔
352
        {
353
                return function () use ($function, $dependencies) {
1✔
354
                        $key = [$function, $args = func_get_args()];
1✔
355
                        if (is_array($function) && is_object($function[0])) {
1✔
356
                                $key[0][0] = $function[0]::class;
1✔
357
                        }
358

359
                        return $this->load($key, function (&$deps) use ($function, $args, $dependencies) {
1✔
360
                                $deps = $dependencies;
1✔
361
                                return $function(...$args);
1✔
362
                        });
1✔
363
                };
1✔
364
        }
365

366

367
        /**
368
         * Starts the output cache.
369
         */
370
        public function capture(mixed $key): ?OutputHelper
1✔
371
        {
372
                $data = $this->load($key);
1✔
373
                if ($data === null) {
1✔
374
                        return new OutputHelper($this, $key);
1✔
375
                }
376

377
                echo $data;
1✔
378
                return null;
1✔
379
        }
380

381

382
        #[\Deprecated('use capture()')]
383
        public function start($key): ?OutputHelper
384
        {
385
                trigger_error(__METHOD__ . '() was renamed to capture()', E_USER_DEPRECATED);
×
386
                return $this->capture($key);
×
387
        }
388

389

390
        /**
391
         * Generates internal cache key.
392
         */
393
        protected function generateKey($key): string
394
        {
395
                return $this->namespace . hash('xxh128', is_scalar($key) ? (string) $key : serialize($key));
1✔
396
        }
397

398

399
        /********************* dependency checkers ****************d*g**/
400

401

402
        /**
403
         * Checks CALLBACKS dependencies.
404
         */
405
        public static function checkCallbacks(array $callbacks): bool
1✔
406
        {
407
                foreach ($callbacks as $callback) {
1✔
408
                        if (!array_shift($callback)(...$callback)) {
1✔
409
                                return false;
1✔
410
                        }
411
                }
412

413
                return true;
1✔
414
        }
415

416

417
        /**
418
         * Checks CONSTS dependency.
419
         */
420
        private static function checkConst(string $const, $value): bool
1✔
421
        {
422
                return defined($const) && constant($const) === $value;
1✔
423
        }
424

425

426
        /**
427
         * Checks FILES dependency.
428
         */
429
        private static function checkFile(string $file, ?int $time): bool
1✔
430
        {
431
                return @filemtime($file) == $time; // @ - stat may fail
1✔
432
        }
433
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc