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

nette / caching / 8225085521

10 Mar 2024 10:09PM UTC coverage: 87.656% (-0.3%) from 87.969%
8225085521

push

github

dg
removed legacy services names

561 of 640 relevant lines covered (87.66%)

0.88 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

14

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

33
        /** @deprecated use Cache::Priority */
34
        public const PRIORITY = self::Priority;
35

36
        /** @deprecated use Cache::Expire */
37
        public const EXPIRATION = self::Expire;
38

39
        /** @deprecated use Cache::Expire */
40
        public const EXPIRE = self::Expire;
41

42
        /** @deprecated use Cache::Sliding */
43
        public const SLIDING = self::Sliding;
44

45
        /** @deprecated use Cache::Tags */
46
        public const TAGS = self::Tags;
47

48
        /** @deprecated use Cache::Files */
49
        public const FILES = self::Files;
50

51
        /** @deprecated use Cache::Items */
52
        public const ITEMS = self::Items;
53

54
        /** @deprecated use Cache::Constants */
55
        public const CONSTS = self::Constants;
56

57
        /** @deprecated use Cache::Callbacks */
58
        public const CALLBACKS = self::Callbacks;
59

60
        /** @deprecated use Cache::Namespaces */
61
        public const NAMESPACES = self::Namespaces;
62

63
        /** @deprecated use Cache::All */
64
        public const ALL = self::All;
65

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

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

72

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

79

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

88

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

97

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

106

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

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

126
                return $data;
1✔
127
        }
128

129

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

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

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

156
                        return $result;
1✔
157
                }
158

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

172
                return $result;
1✔
173
        }
174

175

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

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

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

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

218

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

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

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

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

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

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

257

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

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

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

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

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

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

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

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

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

302
                return $dp;
1✔
303
        }
304

305

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

314

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

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

332

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

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

346

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

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

365

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

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

380

381
        /**
382
         * @deprecated  use capture()
383
         */
384
        public function start($key): ?OutputHelper
385
        {
386
                trigger_error(__METHOD__ . '() was renamed to capture()', E_USER_DEPRECATED);
×
387
                return $this->capture($key);
×
388
        }
389

390

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

399

400
        /********************* dependency checkers ****************d*g**/
401

402

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

414
                return true;
1✔
415
        }
416

417

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

426

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