Completed
Push — 2.1 ( 66095f...207f8c )
by Alexander
17:15
created

FragmentCache::getCachedContent()   D

Complexity

Conditions 9
Paths 8

Size

Total Lines 33
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9

Importance

Changes 0
Metric Value
dl 0
loc 33
ccs 18
cts 18
cp 1
rs 4.909
c 0
b 0
f 0
cc 9
eloc 18
nc 8
nop 0
crap 9
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\widgets;
9
10
use Yii;
11
use yii\base\Widget;
12
use yii\caching\CacheInterface;
13
use yii\caching\Dependency;
14
use yii\di\Instance;
15
16
/**
17
 * FragmentCache is used by [[\yii\base\View]] to provide caching of page fragments.
18
 *
19
 * @property string|false $cachedContent The cached content. False is returned if valid content is not found
20
 * in the cache. This property is read-only.
21
 *
22
 * @author Qiang Xue <[email protected]>
23
 * @since 2.0
24
 */
25
class FragmentCache extends Widget
26
{
27
    /**
28
     * @var CacheInterface|array|string the cache object or the application component ID of the cache object.
29
     * After the FragmentCache object is created, if you want to change this property,
30
     * you should only assign it with a cache object.
31
     * Starting from version 2.0.2, this can also be a configuration array for creating the object.
32
     */
33
    public $cache = 'cache';
34
    /**
35
     * @var int number of seconds that the data can remain valid in cache.
36
     * Use 0 to indicate that the cached data will never expire.
37
     */
38
    public $duration = 60;
39
    /**
40
     * @var array|Dependency the dependency that the cached content depends on.
41
     * This can be either a [[Dependency]] object or a configuration array for creating the dependency object.
42
     * For example,
43
     *
44
     * ```php
45
     * [
46
     *     'class' => \yii\caching\DbDependency::class,
47
     *     'sql' => 'SELECT MAX(updated_at) FROM post',
48
     * ]
49
     * ```
50
     *
51
     * would make the output cache depends on the last modified time of all posts.
52
     * If any post has its modification time changed, the cached content would be invalidated.
53
     */
54
    public $dependency;
55
    /**
56
     * @var array list of factors that would cause the variation of the content being cached.
57
     * Each factor is a string representing a variation (e.g. the language, a GET parameter).
58
     * The following variation setting will cause the content to be cached in different versions
59
     * according to the current application language:
60
     *
61
     * ```php
62
     * [
63
     *     Yii::$app->language,
64
     * ]
65
     * ```
66
     */
67
    public $variations;
68
    /**
69
     * @var bool whether to enable the fragment cache. You may use this property to turn on and off
70
     * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests).
71
     */
72
    public $enabled = true;
73
    /**
74
     * @var array a list of placeholders for embedding dynamic contents. This property
75
     * is used internally to implement the content caching feature. Do not modify it.
76
     */
77
    public $dynamicPlaceholders;
78
79
80
    /**
81
     * Initializes the FragmentCache object.
82
     */
83 7
    public function init()
84
    {
85 7
        parent::init();
86
87 7
        $this->cache = $this->enabled ? Instance::ensure($this->cache, CacheInterface::class) : null;
88
89 7
        if ($this->cache instanceof CacheInterface && $this->getCachedContent() === false) {
90 6
            $this->getView()->cacheStack[] = $this;
91 6
            ob_start();
92 6
            ob_implicit_flush(false);
93
        }
94 7
    }
95
96
    /**
97
     * Marks the end of content to be cached.
98
     * Content displayed before this method call and after [[init()]]
99
     * will be captured and saved in cache.
100
     * This method does nothing if valid content is already found in cache.
101
     * @return string the result of widget execution to be outputted.
102
     */
103 7
    public function run()
104
    {
105 7
        if (($content = $this->getCachedContent()) !== false) {
106 4
            return $content;
107 7
        } elseif ($this->cache instanceof CacheInterface) {
108 6
            array_pop($this->getView()->cacheStack);
109
110 6
            $content = ob_get_clean();
111 6
            if ($content === false || $content === '') {
112 1
                return '';
113
            }
114 5
            if (is_array($this->dependency)) {
115
                $this->dependency = Yii::createObject($this->dependency);
116
            }
117 5
            $data = [$content, $this->dynamicPlaceholders];
118 5
            $this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
119
120 5
            if (empty($this->getView()->cacheStack) && !empty($this->dynamicPlaceholders)) {
121 3
                $content = $this->updateDynamicContent($content, $this->dynamicPlaceholders);
122
            }
123 5
            return $content;
124
        }
125 2
        return '';
126
    }
127
128
    /**
129
     * @var string|bool the cached content. False if the content is not cached.
130
     */
131
    private $_content;
132
133
    /**
134
     * Returns the cached content if available.
135
     * @return string|false the cached content. False is returned if valid content is not found in the cache.
136
     */
137 7
    public function getCachedContent()
138
    {
139 7
        if ($this->_content !== null) {
140 7
            return $this->_content;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->_content; of type string|boolean adds the type boolean to the return on line 140 which is incompatible with the return type documented by yii\widgets\FragmentCache::getCachedContent of type string|false.
Loading history...
141
        }
142
143 7
        $this->_content = false;
144
145 7
        if (!($this->cache instanceof CacheInterface)) {
146 2
            return $this->_content;
147
        }
148
149 6
        $key = $this->calculateKey();
150 6
        $data = $this->cache->get($key);
151 6
        if (!is_array($data) || count($data) !== 2) {
152 6
            return $this->_content;
153
        }
154
155 4
        list($this->_content, $placeholders) = $data;
156 4
        if (!is_array($placeholders) || count($placeholders) === 0) {
157 1
            return $this->_content;
158
        }
159
160 3
        if (empty($this->getView()->cacheStack)) {
161
            // outermost cache: replace placeholder with dynamic content
162 3
            $this->_content = $this->updateDynamicContent($this->_content, $placeholders);
163
        }
164 3
        foreach ($placeholders as $name => $statements) {
165 3
            $this->getView()->addDynamicPlaceholder($name, $statements);
166
        }
167
168 3
        return $this->_content;
169
    }
170
171
    /**
172
     * Replaces placeholders in content by results of evaluated dynamic statements.
173
     *
174
     * @param string $content
175
     * @param array $placeholders
176
     * @return string final content
177
     */
178 3
    protected function updateDynamicContent($content, $placeholders)
179
    {
180 3
        foreach ($placeholders as $name => $statements) {
181 3
            $placeholders[$name] = $this->getView()->evaluateDynamicContent($statements);
182
        }
183
184 3
        return strtr($content, $placeholders);
185
    }
186
187
    /**
188
     * Generates a unique key used for storing the content in cache.
189
     * The key generated depends on both [[id]] and [[variations]].
190
     * @return mixed a valid cache key
191
     */
192 6
    protected function calculateKey()
193
    {
194 6
        $factors = [__CLASS__, $this->getId()];
195 6
        if (is_array($this->variations)) {
196
            foreach ($this->variations as $factor) {
197
                $factors[] = $factor;
198
            }
199
        }
200
201 6
        return $factors;
202
    }
203
}
204