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

nette / caching / 3908142166

pending completion
3908142166

push

github

David Grudl
typo

534 of 596 relevant lines covered (89.6%)

0.9 hits per line

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

94.4
/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
        public const
36
                PRIORITY = self::Priority,
37
                EXPIRATION = self::Expire,
38
                EXPIRE = self::Expire,
39
                SLIDING = self::Sliding,
40
                TAGS = self::Tags,
41
                FILES = self::Files,
42
                ITEMS = self::Items,
43
                CONSTS = self::Constants,
44
                CALLBACKS = self::Callbacks,
45
                NAMESPACES = self::Namespaces,
46
                ALL = self::All;
47

48
        /** @internal */
49
        public const
50
                NamespaceSeparator = "\x00",
51
                NAMESPACE_SEPARATOR = self::NamespaceSeparator;
52

53
        /** @var Storage */
54
        private $storage;
55

56
        /** @var string */
57
        private $namespace;
58

59

60
        public function __construct(Storage $storage, ?string $namespace = null)
1✔
61
        {
62
                $this->storage = $storage;
1✔
63
                $this->namespace = $namespace . self::NamespaceSeparator;
1✔
64
        }
1✔
65

66

67
        /**
68
         * Returns cache storage.
69
         */
70
        final public function getStorage(): Storage
71
        {
72
                return $this->storage;
×
73
        }
74

75

76
        /**
77
         * Returns cache namespace.
78
         */
79
        final public function getNamespace(): string
80
        {
81
                return (string) substr($this->namespace, 0, -1);
1✔
82
        }
83

84

85
        /**
86
         * Returns new nested cache object.
87
         * @return static
88
         */
89
        public function derive(string $namespace)
1✔
90
        {
91
                return new static($this->storage, $this->namespace . $namespace);
1✔
92
        }
93

94

95
        /**
96
         * Reads the specified item from the cache or generate it.
97
         * @param  mixed  $key
98
         * @return mixed
99
         */
100
        public function load($key, ?callable $generator = null)
1✔
101
        {
102
                $storageKey = $this->generateKey($key);
1✔
103
                $data = $this->storage->read($storageKey);
1✔
104
                if ($data === null && $generator) {
1✔
105
                        $this->storage->lock($storageKey);
1✔
106
                        try {
107
                                $data = $generator(...[&$dependencies]);
1✔
108
                        } catch (\Throwable $e) {
1✔
109
                                $this->storage->remove($storageKey);
1✔
110
                                throw $e;
1✔
111
                        }
112

113
                        $this->save($key, $data, $dependencies);
1✔
114
                }
115

116
                return $data;
1✔
117
        }
118

119

120
        /**
121
         * Reads multiple items from the cache.
122
         */
123
        public function bulkLoad(array $keys, ?callable $generator = null): array
1✔
124
        {
125
                if (count($keys) === 0) {
1✔
126
                        return [];
×
127
                }
128

129
                foreach ($keys as $key) {
1✔
130
                        if (!is_scalar($key)) {
1✔
131
                                throw new Nette\InvalidArgumentException('Only scalar keys are allowed in bulkLoad()');
1✔
132
                        }
133
                }
134

135
                $result = [];
1✔
136
                if (!$this->storage instanceof BulkReader) {
1✔
137
                        foreach ($keys as $key) {
1✔
138
                                $result[$key] = $this->load(
1✔
139
                                        $key,
1✔
140
                                        $generator
1✔
141
                                                ? function (&$dependencies) use ($key, $generator) {
1✔
142
                                                        return $generator(...[$key, &$dependencies]);
1✔
143
                                                }
1✔
144
                                                : null
1✔
145
                                );
146
                        }
147

148
                        return $result;
1✔
149
                }
150

151
                $storageKeys = array_map([$this, 'generateKey'], $keys);
1✔
152
                $cacheData = $this->storage->bulkRead($storageKeys);
1✔
153
                foreach ($keys as $i => $key) {
1✔
154
                        $storageKey = $storageKeys[$i];
1✔
155
                        if (isset($cacheData[$storageKey])) {
1✔
156
                                $result[$key] = $cacheData[$storageKey];
1✔
157
                        } elseif ($generator) {
1✔
158
                                $result[$key] = $this->load($key, function (&$dependencies) use ($key, $generator) {
1✔
159
                                        return $generator(...[$key, &$dependencies]);
1✔
160
                                });
1✔
161
                        } else {
162
                                $result[$key] = null;
1✔
163
                        }
164
                }
165

166
                return $result;
1✔
167
        }
168

169

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

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

199
                if ($data === null) {
1✔
200
                        $this->storage->remove($key);
1✔
201
                } else {
202
                        $dependencies = $this->completeDependencies($dependencies);
1✔
203
                        if (isset($dependencies[self::Expire]) && $dependencies[self::Expire] <= 0) {
1✔
204
                                $this->storage->remove($key);
1✔
205
                        } else {
206
                                $this->storage->write($key, $data, $dependencies);
1✔
207
                        }
208

209
                        return $data;
1✔
210
                }
211
        }
1✔
212

213

214
        private function completeDependencies(?array $dp): array
1✔
215
        {
216
                // convert expire into relative amount of seconds
217
                if (isset($dp[self::Expire])) {
1✔
218
                        $dp[self::Expire] = Nette\Utils\DateTime::from($dp[self::Expire])->format('U') - time();
1✔
219
                }
220

221
                // make list from TAGS
222
                if (isset($dp[self::Tags])) {
1✔
223
                        $dp[self::Tags] = array_values((array) $dp[self::Tags]);
1✔
224
                }
225

226
                // make list from NAMESPACES
227
                if (isset($dp[self::Namespaces])) {
1✔
228
                        $dp[self::Namespaces] = array_values((array) $dp[self::Namespaces]);
×
229
                }
230

231
                // convert FILES into CALLBACKS
232
                if (isset($dp[self::Files])) {
1✔
233
                        foreach (array_unique((array) $dp[self::Files]) as $item) {
1✔
234
                                $dp[self::Callbacks][] = [[self::class, 'checkFile'], $item, @filemtime($item) ?: null]; // @ - stat may fail
1✔
235
                        }
236

237
                        unset($dp[self::Files]);
1✔
238
                }
239

240
                // add namespaces to items
241
                if (isset($dp[self::Items])) {
1✔
242
                        $dp[self::Items] = array_unique(array_map([$this, 'generateKey'], (array) $dp[self::Items]));
1✔
243
                }
244

245
                // convert CONSTS into CALLBACKS
246
                if (isset($dp[self::Constants])) {
1✔
247
                        foreach (array_unique((array) $dp[self::Constants]) as $item) {
1✔
248
                                $dp[self::Callbacks][] = [[self::class, 'checkConst'], $item, constant($item)];
1✔
249
                        }
250

251
                        unset($dp[self::Constants]);
1✔
252
                }
253

254
                if (!is_array($dp)) {
1✔
255
                        $dp = [];
1✔
256
                }
257

258
                return $dp;
1✔
259
        }
260

261

262
        /**
263
         * Removes item from the cache.
264
         * @param  mixed  $key
265
         */
266
        public function remove($key): void
267
        {
268
                $this->save($key, null);
1✔
269
        }
1✔
270

271

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

286
                $this->storage->clean($conditions);
1✔
287
        }
1✔
288

289

290
        /**
291
         * Caches results of function/method calls.
292
         * @return mixed
293
         */
294
        public function call(callable $function)
1✔
295
        {
296
                $key = func_get_args();
1✔
297
                if (is_array($function) && is_object($function[0])) {
1✔
298
                        $key[0][0] = get_class($function[0]);
1✔
299
                }
300

301
                return $this->load($key, function () use ($function, $key) {
1✔
302
                        return $function(...array_slice($key, 1));
1✔
303
                });
1✔
304
        }
305

306

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

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

325

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

337
                echo $data;
1✔
338
                return null;
1✔
339
        }
340

341

342
        /**
343
         * @deprecated  use capture()
344
         */
345
        public function start($key): ?OutputHelper
346
        {
347
                return $this->capture($key);
×
348
        }
349

350

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

359

360
        /********************* dependency checkers ****************d*g**/
361

362

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

374
                return true;
1✔
375
        }
376

377

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

386

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