Passed
Push — master ( 70dd87...6f141b )
by Alexander
08:24
created

FragmentCache::start()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 6
ccs 0
cts 6
cp 0
crap 6
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Widgets;
6
7
use Yiisoft\Cache\CacheInterface;
8
use Yiisoft\Cache\Dependency\Dependency;
9
use Yiisoft\View\DynamicContentAwareInterface;
10
use Yiisoft\View\DynamicContentAwareTrait;
11
use Yiisoft\View\View;
12
use Yiisoft\View\WebView;
13
use Yiisoft\Widget\Widget;
14
15
/**
16
 * FragmentCache is used by {@see \Yiisoft\View\View} to provide caching of page fragments.
17
 *
18
 * @property string|false $cachedContent The cached content. False is returned if valid content is not found in the
19
 * cache. This property is read-only.
20
 */
21
class FragmentCache extends Widget implements DynamicContentAwareInterface
22
{
23
    use DynamicContentAwareTrait;
24
25
    private string $id;
26
27
    /**
28
     * @var CacheInterface the cache object or the application component ID of the cache object.
29
     *
30
     * After the FragmentCache object is created, if you want to change this property, you should only assign it with
31
     * a cache object.
32
     */
33
    private ?CacheInterface $cache = null;
34
35
    /**
36
     * @var int number of seconds that the data can remain valid in cache.
37
     *
38
     * Use 0 to indicate that the cached data will never expire.
39
     */
40
    private int $duration = 60;
41
42
    /**
43
     * @var Dependency the dependency that the cached content depends on.
44
     *
45
     * This can be either a {@see Dependency} object or a configuration array for creating the dependency object.
46
     *
47
     * Would make the output cache depends on the last modified time of all posts. If any post has its modification time
48
     * changed, the cached content would be invalidated.
49
     */
50
    private ?Dependency $dependency = null;
51
52
    /**
53
     * @var string[]|string list of factors that would cause the variation of the content being cached.
54
     *
55
     * Each factor is a string representing a variation (e.g. the language, a GET parameter). The following variation
56
     * setting will cause the content to be cached in different versions according to the current application language:
57
     */
58
    private $variations;
59
60
    /**
61
     * @var string|bool the cached content. Null if the content is not cached.
62
     */
63
    private $content = false;
64
65
    private WebView $webView;
66
67
    public function __construct(CacheInterface $cache, WebView $webview)
68
    {
69
        $this->cache = $cache;
70
        $this->webView = $webview;
71
    }
72
73
    /**
74
     * Initializes the FragmentCache object.
75
     */
76
    public function start(): void
77
    {
78
        if ($this->getCachedContent() === false) {
79
            $this->webView->pushDynamicContent($this);
80
            ob_start();
81
            ob_implicit_flush(0);
82
        }
83
    }
84
85
    /**
86
     * Marks the end of content to be cached.
87
     *
88
     * Content displayed before this method call and after {@see init()} will be captured and saved in cache.
89
     *
90
     * This method does nothing if valid content is already found in cache.
91
     *
92
     * @return string the result of widget execution to be outputted.
93
     */
94
    public function run(): string
95
    {
96
        if (($content = $this->getCachedContent()) !== false) {
97
            return $content;
98
        }
99
100
        if ($this->cache instanceof CacheInterface) {
101
            $this->webView->popDynamicContent();
102
103
            $content = ob_get_clean();
104
105
            if ($content === false || $content === '') {
106
                return '';
107
            }
108
109
            $data = [$content, $this->getDynamicPlaceholders()];
110
            $this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
111
112
            return $this->updateDynamicContent($content, $this->getDynamicPlaceholders());
113
        }
114
115
        return '';
116
    }
117
118
    /**
119
     * Returns the cached content if available.
120
     *
121
     * @return string|false the cached content. False is returned if valid content is not found in the cache.
122
     */
123
    public function getCachedContent()
124
    {
125
        if ($this->content !== null) {
126
            return $this->content;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->content also could return the type boolean which is incompatible with the documented return type false|string.
Loading history...
127
        }
128
129
        if (!($this->cache instanceof CacheInterface)) {
130
            return $this->content;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->content returns the type void which is incompatible with the documented return type false|string.
Loading history...
131
        }
132
133
        $key = $this->calculateKey();
134
        $data = $this->cache->get($key);
0 ignored issues
show
Bug introduced by
$key of type array is incompatible with the type string expected by parameter $key of Psr\SimpleCache\CacheInterface::get(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

134
        $data = $this->cache->get(/** @scrutinizer ignore-type */ $key);
Loading history...
135
136
        if (!\is_array($data) || count($data) !== 2) {
137
            return $this->content;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->content returns the type void which is incompatible with the documented return type false|string.
Loading history...
138
        }
139
140
        [$this->content, $placeholders] = $data;
141
142
        if (!\is_array($placeholders) || count($placeholders) === 0) {
143
            return $this->content;
144
        }
145
146
        $this->content = $this->updateDynamicContent($this->content, $placeholders, true);
147
148
        return $this->content;
149
    }
150
151
    /**
152
     * Generates a unique key used for storing the content in cache.
153
     *
154
     * The key generated depends on both {@see id} and {@see variations}.
155
     *
156
     * @return mixed a valid cache key
157
     */
158
    public function calculateKey()
159
    {
160
        return array_merge([__CLASS__, $this->id], (array) $this->variations);
161
    }
162
163
    /**
164
     * {@see $cache}
165
     *
166
     * @param CacheInterface|null $value
167
     *
168
     * @return FragmentCache
169
     */
170
    public function cache(?CacheInterface $value): FragmentCache
171
    {
172
        $this->cache = $value;
173
174
        return $this;
175
    }
176
177
    /**
178
     * {@see $content}
179
     *
180
     * @param string|bool $value
181
     *
182
     * @return FragmentCache
183
     */
184
    public function content($value): FragmentCache
185
    {
186
        $this->content = $value;
187
188
        return $this;
189
    }
190
191
    /**
192
     * {@see $dependency}
193
     *
194
     * @param Dependency $value
195
     *
196
     * @return FragmentCache
197
     */
198
    public function dependency(Dependency $value): FragmentCache
199
    {
200
        $this->dependency = $value;
201
202
        return $this;
203
    }
204
205
    /**
206
     * {@see $duration}
207
     *
208
     * @param int $value
209
     *
210
     * @return FragmentCache
211
     */
212
    public function duration(int $value): FragmentCache
213
    {
214
        $this->duration = $value;
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return Yiisoft\Yii\Widgets\FragmentCache. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
215
    }
216
217
    /**
218
     * @param string $value
219
     *
220
     * @return FragmentCache
221
     */
222
    public function id(string $value): FragmentCache
223
    {
224
        $this->id = $value;
225
226
        return $this;
227
    }
228
229
    /**
230
     * {@see $variations}
231
     *
232
     * @param string[]|string $value
233
     *
234
     * @return FragmentCache
235
     */
236
    public function variations($value): FragmentCache
237
    {
238
        $this->variations = $value;
239
240
        return $this;
241
    }
242
243
    /**
244
     * @inheritDoc
245
     */
246
    protected function getView(): webView
247
    {
248
        return $this->webView;
249
    }
250
}
251