Completed
Push — cache-closure ( 7d9053...380edd )
by Dmitry
35:54
created

FragmentCache::run()   C

Complexity

Conditions 8
Paths 7

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 9.1669

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 6.1403
c 0
b 0
f 0
ccs 14
cts 19
cp 0.7368
cc 8
eloc 15
nc 7
nop 0
crap 9.1669
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\Cache;
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 Cache|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',
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 3
    public function init()
84
    {
85 3
        parent::init();
86
87 3
        $this->cache = $this->enabled ? Instance::ensure($this->cache, Cache::className()) : null;
88
89 3
        if ($this->cache instanceof Cache && $this->getCachedContent() === false) {
90 2
            $this->getView()->cacheStack[] = $this;
91 2
            ob_start();
92 2
            ob_implicit_flush(false);
93 2
        }
94 3
    }
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
     */
102 3
    public function run()
103
    {
104 3
        if (($content = $this->getCachedContent()) !== false) {
105 1
            echo $content;
106 3
        } elseif ($this->cache instanceof Cache) {
107 2
            array_pop($this->getView()->cacheStack);
108
            
109 2
            $content = ob_get_clean();
110 2
            if ($content === false || $content === '') {
111
                return;
112
            }
113 2
            if (is_array($this->dependency)) {
114
                $this->dependency = Yii::createObject($this->dependency);
115
            }
116 2
            $data = [$content, $this->dynamicPlaceholders];
117 2
            $this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
118
119 2
            if (empty($this->getView()->cacheStack) && !empty($this->dynamicPlaceholders)) {
120
                $content = $this->updateDynamicContent($content, $this->dynamicPlaceholders);
121
            }
122 2
            echo $content;
123 2
        }
124 3
    }
125
126
    /**
127
     * @var string|bool the cached content. False if the content is not cached.
128
     */
129
    private $_content;
130
131
    /**
132
     * Returns the cached content if available.
133
     * @return string|false the cached content. False is returned if valid content is not found in the cache.
134
     */
135 3
    public function getCachedContent()
136
    {
137 3
        if ($this->_content === null) {
138 3
            $this->_content = false;
139 3
            if ($this->cache instanceof Cache) {
140 2
                $key = $this->calculateKey();
141 2
                $data = $this->cache->get($key);
142 2
                if (is_array($data) && count($data) === 2) {
143 1
                    list ($content, $placeholders) = $data;
144 1
                    if (is_array($placeholders) && count($placeholders) > 0) {
145
                        if (empty($this->getView()->cacheStack)) {
146
                            // outermost cache: replace placeholder with dynamic content
147
                            $content = $this->updateDynamicContent($content, $placeholders);
148
                        }
149
                        foreach ($placeholders as $name => $statements) {
150
                            $this->getView()->addDynamicPlaceholder($name, $statements);
151
                        }
152
                    }
153 1
                    $this->_content = $content;
154 1
                }
155 2
            }
156 3
        }
157
158 3
        return $this->_content;
159
    }
160
161
    /**
162
     * Replaces placeholders in content by results of evaluated dynamic statements.
163
     *
164
     * @param string $content
165
     * @param array $placeholders
166
     * @return string final content
167
     */
168
    protected function updateDynamicContent($content, $placeholders)
169
    {
170
        foreach ($placeholders as $name => $statements) {
171
            $placeholders[$name] = $this->getView()->evaluateDynamicContent($statements);
172
        }
173
174
        return strtr($content, $placeholders);
175
    }
176
177
    /**
178
     * Generates a unique key used for storing the content in cache.
179
     * The key generated depends on both [[id]] and [[variations]].
180
     * @return mixed a valid cache key
181
     */
182 2
    protected function calculateKey()
183
    {
184 2
        $factors = [__CLASS__, $this->getId()];
185 2
        if (is_array($this->variations)) {
186
            foreach ($this->variations as $factor) {
187
                $factors[] = $factor;
188
            }
189
        }
190
191 2
        return $factors;
192
    }
193
}
194