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

nette / caching / 5772374863

pending completion
5772374863

push

github

dg
typo

1 of 1 new or added line in 1 file covered. (100.0%)

540 of 607 relevant lines covered (88.96%)

0.89 hits per line

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

93.44
/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
        use Nette\SmartObject;
21

22
        /** dependency */
23
        public const
24
                Priority = 'priority',
25
                Expire = 'expire',
26
                Sliding = 'sliding',
27
                Tags = 'tags',
28
                Files = 'files',
29
                Items = 'items',
30
                Constants = 'consts',
31
                Callbacks = 'callbacks',
32
                Namespaces = 'namespaces',
33
                All = 'all';
34

35
        /** @deprecated use Cache::Priority */
36
        public const PRIORITY = self::Priority;
37

38
        /** @deprecated use Cache::Expire */
39
        public const EXPIRATION = self::Expire;
40

41
        /** @deprecated use Cache::Expire */
42
        public const EXPIRE = self::Expire;
43

44
        /** @deprecated use Cache::Sliding */
45
        public const SLIDING = self::Sliding;
46

47
        /** @deprecated use Cache::Tags */
48
        public const TAGS = self::Tags;
49

50
        /** @deprecated use Cache::Files */
51
        public const FILES = self::Files;
52

53
        /** @deprecated use Cache::Items */
54
        public const ITEMS = self::Items;
55

56
        /** @deprecated use Cache::Constants */
57
        public const CONSTS = self::Constants;
58

59
        /** @deprecated use Cache::Callbacks */
60
        public const CALLBACKS = self::Callbacks;
61

62
        /** @deprecated use Cache::Namespaces */
63
        public const NAMESPACES = self::Namespaces;
64

65
        /** @deprecated use Cache::All */
66
        public const ALL = self::All;
67

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

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

74

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

81

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

90

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

99

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

108

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

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

128
                return $data;
1✔
129
        }
130

131

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

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

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

158
                        return $result;
1✔
159
                }
160

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

174
                return $result;
1✔
175
        }
176

177

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

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

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

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

220

221
        private function completeDependencies(?array $dp): array
1✔
222
        {
223
                // convert expire into relative amount of seconds
224
                if (isset($dp[self::Expire])) {
1✔
225
                        $dp[self::Expire] = Nette\Utils\DateTime::from($dp[self::Expire])->format('U') - time();
1✔
226
                }
227

228
                // make list from TAGS
229
                if (isset($dp[self::Tags])) {
1✔
230
                        $dp[self::Tags] = array_values((array) $dp[self::Tags]);
1✔
231
                }
232

233
                // make list from NAMESPACES
234
                if (isset($dp[self::Namespaces])) {
1✔
235
                        $dp[self::Namespaces] = array_values((array) $dp[self::Namespaces]);
×
236
                }
237

238
                // convert FILES into CALLBACKS
239
                if (isset($dp[self::Files])) {
1✔
240
                        foreach (array_unique((array) $dp[self::Files]) as $item) {
1✔
241
                                $dp[self::Callbacks][] = [[self::class, 'checkFile'], $item, @filemtime($item) ?: null]; // @ - stat may fail
1✔
242
                        }
243

244
                        unset($dp[self::Files]);
1✔
245
                }
246

247
                // add namespaces to items
248
                if (isset($dp[self::Items])) {
1✔
249
                        $dp[self::Items] = array_unique(array_map([$this, 'generateKey'], (array) $dp[self::Items]));
1✔
250
                }
251

252
                // convert CONSTS into CALLBACKS
253
                if (isset($dp[self::Constants])) {
1✔
254
                        foreach (array_unique((array) $dp[self::Constants]) as $item) {
1✔
255
                                $dp[self::Callbacks][] = [[self::class, 'checkConst'], $item, constant($item)];
1✔
256
                        }
257

258
                        unset($dp[self::Constants]);
1✔
259
                }
260

261
                if (!is_array($dp)) {
1✔
262
                        $dp = [];
1✔
263
                }
264

265
                return $dp;
1✔
266
        }
267

268

269
        /**
270
         * Removes item from the cache.
271
         */
272
        public function remove(mixed $key): void
1✔
273
        {
274
                $this->save($key, null);
1✔
275
        }
1✔
276

277

278
        /**
279
         * Removes items from the cache by conditions.
280
         * Conditions are:
281
         * - Cache::Priority => (int) priority
282
         * - Cache::Tags => (array) tags
283
         * - Cache::All => true
284
         */
285
        public function clean(?array $conditions = null): void
1✔
286
        {
287
                $conditions = (array) $conditions;
1✔
288
                if (isset($conditions[self::Tags])) {
1✔
289
                        $conditions[self::Tags] = array_values((array) $conditions[self::Tags]);
1✔
290
                }
291

292
                $this->storage->clean($conditions);
1✔
293
        }
1✔
294

295

296
        /**
297
         * Caches results of function/method calls.
298
         */
299
        public function call(callable $function): mixed
1✔
300
        {
301
                $key = func_get_args();
1✔
302
                if (is_array($function) && is_object($function[0])) {
1✔
303
                        $key[0][0] = get_class($function[0]);
1✔
304
                }
305

306
                return $this->load($key, fn() => $function(...array_slice($key, 1)));
1✔
307
        }
308

309

310
        /**
311
         * Caches results of function/method calls.
312
         */
313
        public function wrap(callable $function, ?array $dependencies = null): \Closure
1✔
314
        {
315
                return function () use ($function, $dependencies) {
1✔
316
                        $key = [$function, $args = func_get_args()];
1✔
317
                        if (is_array($function) && is_object($function[0])) {
1✔
318
                                $key[0][0] = get_class($function[0]);
1✔
319
                        }
320

321
                        return $this->load($key, function (&$deps) use ($function, $args, $dependencies) {
1✔
322
                                $deps = $dependencies;
1✔
323
                                return $function(...$args);
1✔
324
                        });
1✔
325
                };
1✔
326
        }
327

328

329
        /**
330
         * Starts the output cache.
331
         */
332
        public function capture(mixed $key): ?OutputHelper
1✔
333
        {
334
                $data = $this->load($key);
1✔
335
                if ($data === null) {
1✔
336
                        return new OutputHelper($this, $key);
1✔
337
                }
338

339
                echo $data;
1✔
340
                return null;
1✔
341
        }
342

343

344
        /**
345
         * @deprecated  use capture()
346
         */
347
        public function start($key): ?OutputHelper
348
        {
349
                trigger_error(__METHOD__ . '() was renamed to capture()', E_USER_DEPRECATED);
×
350
                return $this->capture($key);
×
351
        }
352

353

354
        /**
355
         * Generates internal cache key.
356
         */
357
        protected function generateKey($key): string
358
        {
359
                return $this->namespace . md5(is_scalar($key) ? (string) $key : serialize($key));
1✔
360
        }
361

362

363
        /********************* dependency checkers ****************d*g**/
364

365

366
        /**
367
         * Checks CALLBACKS dependencies.
368
         */
369
        public static function checkCallbacks(array $callbacks): bool
1✔
370
        {
371
                foreach ($callbacks as $callback) {
1✔
372
                        if (!array_shift($callback)(...$callback)) {
1✔
373
                                return false;
1✔
374
                        }
375
                }
376

377
                return true;
1✔
378
        }
379

380

381
        /**
382
         * Checks CONSTS dependency.
383
         */
384
        private static function checkConst(string $const, $value): bool
1✔
385
        {
386
                return defined($const) && constant($const) === $value;
1✔
387
        }
388

389

390
        /**
391
         * Checks FILES dependency.
392
         */
393
        private static function checkFile(string $file, ?int $time): bool
1✔
394
        {
395
                return @filemtime($file) == $time; // @ - stat may fail
1✔
396
        }
397
}
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