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

nette / caching / 3908216513

pending completion
3908216513

push

github

David Grudl
added $dependencies as a Cache::load() 3rd parameter

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

528 of 590 relevant lines covered (89.49%)

0.89 hits per line

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

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

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

76

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

83

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

92

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

101

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

110

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

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

130
                return $data;
1✔
131
        }
132

133

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

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

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

160
                        return $result;
1✔
161
                }
162

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

176
                return $result;
1✔
177
        }
178

179

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

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

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

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

222

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

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

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

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

246
                        unset($dp[self::Files]);
1✔
247
                }
248

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

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

260
                        unset($dp[self::Constants]);
1✔
261
                }
262

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

267
                return $dp;
1✔
268
        }
269

270

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

279

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

294
                $this->storage->clean($conditions);
1✔
295
        }
1✔
296

297

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

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

311

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

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

330

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

341
                echo $data;
1✔
342
                return null;
1✔
343
        }
344

345

346
        /**
347
         * @deprecated  use capture()
348
         */
349
        public function start($key): ?OutputHelper
350
        {
351
                return $this->capture($key);
×
352
        }
353

354

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

363

364
        /********************* dependency checkers ****************d*g**/
365

366

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

378
                return true;
1✔
379
        }
380

381

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

390

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