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

nette / caching / 15762926525

19 Jun 2025 04:58PM UTC coverage: 87.445%. Remained the same
15762926525

push

github

dg
optimized global function calls

592 of 677 relevant lines covered (87.44%)

0.87 hits per line

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

92.2
/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, md5, 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
69
                NamespaceSeparator = "\x00",
70
                NAMESPACE_SEPARATOR = self::NamespaceSeparator;
71

72
        private Storage $storage;
73
        private string $namespace;
74

75

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

82

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

91

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

100

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

109

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

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

129
                return $data;
1✔
130
        }
131

132

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

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

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

159
                        return $result;
1✔
160
                }
161

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

175
                return $result;
1✔
176
        }
177

178

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

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

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

217
                        return $data;
1✔
218
                }
219
        }
220

221

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

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

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

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

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

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

260

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

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

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

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

284
                        unset($dp[self::Files]);
1✔
285
                }
286

287
                // add namespaces to items
288
                if (isset($dp[self::Items])) {
1✔
289
                        $dp[self::Items] = array_unique(array_map([$this, 'generateKey'], (array) $dp[self::Items]));
1✔
290
                }
291

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

298
                        unset($dp[self::Constants]);
1✔
299
                }
300

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

305
                return $dp;
1✔
306
        }
307

308

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

317

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

332
                $this->storage->clean($conditions);
1✔
333
        }
1✔
334

335

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

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

349

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

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

368

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

379
                echo $data;
1✔
380
                return null;
1✔
381
        }
382

383

384
        /**
385
         * @deprecated  use capture()
386
         */
387
        public function start($key): ?OutputHelper
388
        {
389
                return $this->capture($key);
×
390
        }
391

392

393
        /**
394
         * Generates internal cache key.
395
         */
396
        protected function generateKey($key): string
397
        {
398
                return $this->namespace . md5(is_scalar($key) ? (string) $key : serialize($key));
1✔
399
        }
400

401

402
        /********************* dependency checkers ****************d*g**/
403

404

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

416
                return true;
1✔
417
        }
418

419

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

428

429
        /**
430
         * Checks FILES dependency.
431
         */
432
        private static function checkFile(string $file, ?int $time): bool
1✔
433
        {
434
                return @filemtime($file) == $time; // @ - stat may fail
1✔
435
        }
436
}
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