Passed
Push — master ( 9e997b...1490b2 )
by Rustam
02:32
created

FragmentCache::render()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 4
nop 0
dl 0
loc 22
ccs 12
cts 12
cp 1
crap 5
rs 9.5555
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Widgets;
6
7
use RuntimeException;
8
use Yiisoft\Cache\CacheInterface;
9
use Yiisoft\Cache\Dependency\Dependency;
10
use Yiisoft\View\Cache\CachedContent;
11
use Yiisoft\View\Cache\DynamicContent;
12
use Yiisoft\Widget\Widget;
13
14
use function ob_end_clean;
15
use function ob_get_clean;
16
use function ob_start;
17
18
/**
19
 * FragmentCache caches a fragment of content.
20
 *
21
 * @see CachedContent
22
 * @see DynamicContent
23
 *
24
 * Example of use:
25
 *
26
 * ```php
27
 * $dynamicContent = new Yiisoft\View\Cache\DynamicContent('dynamic-id', static function (array $parameters): string {
28
 *     return strtoupper("{$parameters['a']} - {$parameters['b']}");
29
 * }, ['a' => 'string-a', 'b' => 'string-b']);
30
 *
31
 * FragmentCache::widget()
32
 *     ->id('cache-id')
33
 *     ->ttl(30)
34
 *     ->dynamicContents($dynamicContent)
35
 *     ->begin();
36
 *     echo 'Content to be cached ...';
37
 *     echo $dynamicContent->placeholder();
38
 *     echo 'Content to be cached ...';
39
 * FragmentCache::end();
40
 * ```
41
 */
42
final class FragmentCache extends Widget
43
{
44
    private Dependency|null $dependency = null;
45
    private string $id = '';
46
    private int $ttl = 60;
47
    /** @psalm-var string[] */
48
    private array $variations = [];
49
50
    /**
51
     * @var array<string, DynamicContent>
52 8
     */
53
    private array $dynamicContents = [];
54 8
55
    public function __construct(private CacheInterface $cache)
56
    {
57
    }
58
59
    /**
60
     * Returns a new instance with the specified dynamic contents.
61
     *
62
     * @param DynamicContent ...$value The dynamic content instances.
63
     */
64 7
    public function dynamicContents(DynamicContent ...$value): self
65
    {
66 7
        $new = clone $this;
67 7
68 7
        foreach ($value as $dynamicContent) {
69
            $new->dynamicContents[$dynamicContent->id()] = $dynamicContent;
70
        }
71
72
        return $new;
73
    }
74
75
    /**
76
     * Returns a new instance with the specified Widget ID.
77
     *
78
     * @param string $value The unique identifier of the cache fragment.
79
     */
80
    public function id(string $value): self
81
    {
82
        $new = clone $this;
83 2
        $new->id = $value;
84
85 2
        return $new;
86 2
    }
87 2
88
    /**
89
     * Returns a new instance with the specified dependency.
90
     *
91
     * @param Dependency $value The dependency that the cached content depends on.
92
     *
93
     * This can be either a {@see Dependency} object or a configuration array for creating the dependency object.
94
     *
95
     * Would make the output cache depends on the last modified time of all posts. If any post has its modification time
96
     * changed, the cached content would be invalidated.
97 3
     */
98
    public function dependency(Dependency $value): self
99 3
    {
100 3
        $new = clone $this;
101 3
        $new->dependency = $value;
102
103
        return $new;
104
    }
105
106
    /**
107
     * Returns a new instance with the specified TTL.
108
     *
109
     * @param int $value The number of seconds that the data can remain valid in cache.
110
     */
111 4
    public function ttl(int $value): self
112
    {
113 4
        $new = clone $this;
114
        $new->ttl = $value;
115 4
116 4
        return $new;
117
    }
118 4
119
    /**
120
     * Returns a new instance with the specified variations.
121
     *
122
     * @param string ...$value The factors that would cause the variation of the content being cached.
123
     *
124
     * Each factor is a string representing a variation (e.g. the language, a GET parameter). The following variation
125
     * setting will cause the content to be cached in different versions according to the current application language:
126
     *
127
     * ```php
128
     * $fragmentCache->variations('en');
129
     * ```
130
     */
131
    public function variations(string ...$value): self
132
    {
133
        $new = clone $this;
134
        $new->variations = $value;
135 2
136
        return $new;
137 2
    }
138 2
139 2
    /**
140
     * Starts recording a fragment cache.
141
     */
142
    public function begin(): ?string
143
    {
144
        parent::begin();
145 7
146
        ob_start();
147 7
148 7
        return null;
149
    }
150 7
151 7
    /**
152
     * Marks the end of content to be cached.
153
     *
154
     * Content displayed before this method call and after {@see begin()} will be captured and saved in cache.
155
     *
156
     * This method does nothing if valid content is already found in cache.
157
     *
158
     * @return string The result of widget execution to be outputted.
159
     */
160
    public function render(): string
161
    {
162
        if ($this->id === '') {
163 7
            ob_end_clean();
164
            throw new RuntimeException('You must assign the "id" using the "id()" setter.');
165 7
        }
166 1
167 1
        $cachedContent = new CachedContent($this->id, $this->cache, $this->dynamicContents, $this->variations);
168
        $content = $cachedContent->get();
169
170 6
        if ($content !== null) {
171 6
            ob_end_clean();
172
            return $content;
173 6
        }
174 4
175 4
        $content = ob_get_clean();
176
177
        if ($content === false || $content === '') {
178 6
            return '';
179
        }
180 6
181 1
        return $cachedContent->cache($content, $this->ttl, $this->dependency);
182
    }
183
}
184