Passed
Pull Request — master (#9)
by Wilmer
03:54 queued 02:41
created

FragmentCache::cache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
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\WebView;
12
use Yiisoft\Widget\Widget;
13
14
use function array_merge;
15
use function ob_get_clean;
16
use function ob_implicit_flush;
17
use function ob_start;
18
19
/**
20
 * FragmentCache is used by {@see \Yiisoft\View\View} to provide caching of page fragments.
21
 *
22
 * @property-read string|false $cachedContent The cached content. False is returned if valid content is not found in the
23
 * cache. This property is read-only.
24
 */
25
final class FragmentCache extends Widget implements DynamicContentAwareInterface
26
{
27
    use DynamicContentAwareTrait;
28
29
    private string $id;
30
31
    /**
32
     * @var CacheInterface|null the cache object or the application component ID of the cache object.
33
     *
34
     * After the FragmentCache object is created, if you want to change this property, you should only assign it with
35
     * a cache object.
36
     */
37
    private CacheInterface $cache;
38
39
    /**
40
     * @var int number of seconds that the data can remain valid in cache.
41
     *
42
     * Use 0 to indicate that the cached data will never expire.
43
     */
44
    private int $duration = 60;
45
46
    /**
47
     * @var Dependency|null the dependency that the cached content depends on.
48
     *
49
     * This can be either a {@see Dependency} object or a configuration array for creating the dependency object.
50
     *
51
     * Would make the output cache depends on the last modified time of all posts. If any post has its modification time
52
     * changed, the cached content would be invalidated.
53
     */
54
    private ?Dependency $dependency = null;
55
56
    /**
57
     * @var string[]|string list of factors that would cause the variation of the content being cached.
58
     *
59
     * Each factor is a string representing a variation (e.g. the language, a GET parameter). The following variation
60
     * setting will cause the content to be cached in different versions according to the current application language:
61
     */
62
    private $variations;
63
64
    private bool $noCache = false;
65
66
    /**
67
     * @var string|null the cached content. Null if the content is not cached.
68
     */
69
    private $content;
70
71
    private WebView $webView;
72
73 7
    public function __construct(CacheInterface $cache, WebView $webview)
74
    {
75 7
        $this->cache = $cache;
76 7
        $this->webView = $webview;
77 7
    }
78
79
    /**
80
     * Initializes the FragmentCache object.
81
     */
82 7
    public function start(): void
83
    {
84 7
        if ($this->getCachedContent() === null) {
85 7
            $this->webView->pushDynamicContent($this);
86 7
            ob_start();
87 7
            PHP_VERSION_ID >= 80000 ? ob_implicit_flush(false) : ob_implicit_flush(0);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer expected by parameter $flag of ob_implicit_flush(). ( Ignorable by Annotation )

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

87
            PHP_VERSION_ID >= 80000 ? ob_implicit_flush(/** @scrutinizer ignore-type */ false) : ob_implicit_flush(0);
Loading history...
88
        }
89 7
    }
90
91
    /**
92
     * Marks the end of content to be cached.
93
     *
94
     * Content displayed before this method call and after {@see init()} will be captured and saved in cache.
95
     *
96
     * This method does nothing if valid content is already found in cache.
97
     *
98
     * @return string the result of widget execution to be outputted.
99
     */
100 7
    public function run(): string
101
    {
102 7
        if (($content = $this->getCachedContent()) !== null) {
103 4
            return $content;
104
        }
105
106 7
        if ($this->cache instanceof CacheInterface && $this->noCache === false) {
107 6
            $this->webView->popDynamicContent();
108
109 6
            $content = ob_get_clean();
110
111 6
            if ($content === false || $content === '') {
112
                return '';
113
            }
114
115 6
            $data = [$content, $this->getDynamicPlaceholders()];
116 6
            $this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
117
118 6
            return $this->updateDynamicContent($content, $this->getDynamicPlaceholders());
119
        }
120
121 2
        return ob_get_clean();
122
    }
123
124
    /**
125
     * Returns the cached content if available.
126
     *
127
     * @return string|null the cached content. False is returned if valid content is not found in the cache.
128
     */
129 7
    public function getCachedContent(): ?string
130
    {
131 7
        if (!($this->cache instanceof CacheInterface) || $this->noCache) {
0 ignored issues
show
introduced by
$this->cache is always a sub-type of Yiisoft\Cache\CacheInterface.
Loading history...
132 2
            return null;
133
        }
134
135 6
        $key = $this->calculateKey();
136
137 6
        if (!$this->cache->has($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::has(). ( Ignorable by Annotation )

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

137
        if (!$this->cache->has(/** @scrutinizer ignore-type */ $key)) {
Loading history...
138 6
            return null;
139
        }
140
141 4
        $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

141
        $data = $this->cache->get(/** @scrutinizer ignore-type */ $key);
Loading history...
142
143 4
        [$this->content, $placeholders] = $data;
144
145 4
        $this->content = $this->updateDynamicContent($this->content, $placeholders, true);
146
147 4
        return $this->content;
148
    }
149
150
    /**
151
     * Generates a unique key used for storing the content in cache.
152
     *
153
     * The key generated depends on both {@see id} and {@see variations}.
154
     *
155
     * @return mixed a valid cache key
156
     */
157 6
    public function calculateKey()
158
    {
159 6
        return array_merge([__CLASS__, $this->id], (array) $this->variations);
160
    }
161
162
    /**
163
     * {@see $content}
164
     *
165
     * @param string|bool $value
166
     *
167
     * @return $this
168
     */
169 1
    public function content($value): self
170
    {
171 1
        $this->content = $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like $value can also be of type boolean. However, the property $content is declared as type null|string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
172
173 1
        return $this;
174
    }
175
176
    /**
177
     * {@see $dependency}
178
     *
179
     * @param Dependency $value
180
     *
181
     * @return $this
182
     */
183
    public function dependency(Dependency $value): self
184
    {
185
        $this->dependency = $value;
186
187
        return $this;
188
    }
189
190
    /**
191
     * {@see $duration}
192
     *
193
     * @param int $value
194
     *
195
     * @return $this
196
     */
197
    public function duration(int $value): self
198
    {
199
        $this->duration = $value;
200
201
        return $this;
202
    }
203
204
    /**
205
     * @see $id
206
     *
207
     * @param string $value
208
     *
209
     * @return $this
210
     */
211 7
    public function id(string $value): self
212
    {
213 7
        $this->id = $value;
214
215 7
        return $this;
216
    }
217
218
    /**
219
     * {@see $variations}
220
     *
221
     * @param array|string $value
222
     *
223
     * @return $this
224
     */
225 1
    public function variations($value): self
226
    {
227 1
        $this->variations = $value;
228
229 1
        return $this;
230
    }
231
232
    /**
233
     * Disabled cache.
234
     *
235
     * @param bool $value
236
     *
237
     * @return $this
238
     */
239 2
    public function noCache(bool $value = true): self
240
    {
241 2
        $this->noCache = $value;
242
243 2
        return $this;
244
    }
245
246 3
    protected function getView(): WebView
247
    {
248 3
        return $this->webView;
249
    }
250
}
251