/
ViewRenderer.php
250 lines (219 loc) · 8.92 KB
/
ViewRenderer.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
<?php
namespace flexibuild\phpsafe;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\base\Exception;
use yii\caching\Cache;
use yii\caching\Dependency;
use yii\caching\FileCache;
use yii\caching\FileDependency as YiiFileDependency;
use yii\helpers\VarDumper;
use flexibuild\phpsafe\caching\FileDependency;
use flexibuild\phpsafe\helpers\FileHelper;
/**
* ViewRenderer component for yii2 application.
*
* @author SeynovAM <sejnovalexey@gmail.com>
*/
class ViewRenderer extends \yii\base\ViewRenderer
{
/**
* @var string the directory or path alias pointing to where Php-Safe engine
* compiled files will be stored.
*/
public $compiledPath = '@runtime/flexibuild/phpsafe/compiled';
/**
* @var integer the permission to be set for newly created cache files.
* This value will be used by PHP chmod() function. No umask will be applied.
* If not set, the permission will be determined by the current environment.
*/
public $fileMode;
/**
* @var integer make directory that will be passed into [[FileHelper::createDirectory()]].
*/
public $mkDirMode = 0777;
/**
* @var mixed string|yii\caching\Cache|null|false. The name of Yii application
* cache component for caching rendered files. Or instance of yii\caching\Cache class.
* Null or array meaning create and use yii\caching\FileCache component,
* array will be used as FileCache config (null by default).
* False meaning disabling caching.
*/
public $cacheComponent;
/**
* @var string Yii styled config for cache dependency object.
*/
public $cacheDependencyConfig = 'flexibuild\phpsafe\caching\FileDependency';
/**
* @var string the prefix of cache key for file.
*/
public $cacheKeyPrefix = 'phpsafe_';
/**
* @var array key => value pairs that will be passed as php-safe compiler config.
*/
public $compilerConfig = [];
/**
* Initializes the object.
* This method is invoked at the end of the constructor after the object is initialized with the
* given configuration.
* @throws InvalidConfigException if cacheComponent is incorrect.
*/
public function init()
{
parent::init();
$this->initCacheComponent();
}
/**
* Initialization of cache component.
* @throws InvalidConfigException if cacheComponent is incorrect.
*/
protected function initCacheComponent()
{
if ($this->cacheComponent === null || is_array($this->cacheComponent)) {
$this->cacheComponent = Yii::createObject(array_merge([
'class' => FileCache::className(),
'cachePath' => rtrim($this->compiledPath, '\/').'/cache',
'fileMode' => $this->fileMode,
'dirMode' => $this->mkDirMode,
], $this->cacheComponent ?: []));
} elseif (is_string($this->cacheComponent)) {
$this->cacheComponent = Yii::$app->get($this->cacheComponent);
}
if ($this->cacheComponent !== false && !($this->cacheComponent instanceof Cache)) {
throw new InvalidConfigException('Incorrect value of '.__CLASS__.'::$cacheComponent param. Calculated value of this param must be an instance of '.Cache::className().'.');
}
}
/**
* Renders a view file.
*
* This method is invoked by [[View]] whenever it tries to render a view.
* Child classes must implement this method to render the given view file.
*
* @param \yii\base\View $view the view object used for rendering the file.
* @param string $file the view file.
* @param array $params the parameters to be passed to the view file.
*
* @return string the rendering result
*/
public function render($view, $file, $params)
{
if (false === $hash = $this->loadHashFromCache($file)) {
$compiledFilePath = $this->compileFile($file, false);
} else {
$compiledFilePath = $this->getCompiledFilePath($hash);
}
return $view->renderPhpFile($compiledFilePath, $params);
}
/**
* Compiles file.
* @param string $file path to phpsafe file.
* @param bool $checkCache whether method must check hash. If true and cache
* consits hash for this file it will not recompiled. If false method will
* always recompile file.
* @return string compiled file path.
* @throws InvalidParamException if file does not exists.
* @throws Exception if cannot create dir.
*/
public function compileFile($file, $checkCache = true)
{
if ($checkCache && false !== $hash = $this->loadHashFromCache($file)) {
return $this->getCompiledFilePath($hash);
}
if ((false === $realFilePath = realpath($file)) || (false === $sourceContent = @file_get_contents($realFilePath))) {
throw new InvalidParamException("File '$file' was not found.");
}
Yii::beginProfile($profileToken = "Compile file $file.", __METHOD__);
$compilerConfig = array_merge($this->compilerConfig, ['compilingFilePath' => $realFilePath]);
$compiler = Compiler::createFromCode($sourceContent, $compilerConfig);
$content = $compiler->getCompiledCode();
Yii::endProfile($profileToken, __METHOD__);
while (file_exists($compiledFile = $this->getCompiledFilePath($hash = Yii::$app->security->generateRandomString(12))));
if (!FileHelper::createDirectory($dir = dirname($compiledFile), $this->mkDirMode, true)) {
$mode = '0' . base_convert($this->mkDirMode, 10, 8);
throw new Exception("Cannot create directory '$dir' with $mode mode.");
}
file_put_contents($compiledFile, $content);
if ($this->fileMode !== null && !@chmod($compiledFile, $this->fileMode)) {
$mode = '0' . base_convert($this->fileMode, 10, 8);
Yii::warning("Cannot change mode to $mode for file: $compiledFile.", __METHOD__);
}
if ($compiler->collectMap && $compiler->getLinesMap()) {
$mapFile = $this->getMapFilePath($hash);
file_put_contents($mapFile, '<?php return ' . VarDumper::export([
'source' => $realFilePath,
'map' => $compiler->getLinesMap(),
]) . ';');
if ($this->fileMode !== null && !@chmod($mapFile, $this->fileMode)) {
$mode = '0' . base_convert($this->fileMode, 10, 8);
Yii::warning("Cannot change mode to $mode for file: $mapFile.", __METHOD__);
}
}
$this->saveHashToCache($file, $hash);
return $this->getCompiledFilePath($hash);
}
/**
* Tries to load compiled file from cache.
* @param string $file the view file name.
* @return mixed string generated hash for compiled file name or
* false if did not found in cache.
*/
protected function loadHashFromCache($file)
{
if ($this->cacheComponent === false) {
return false;
}
if (false !== $hash = $this->cacheComponent->get($this->getCacheKey($file))) {
return file_exists($this->getCompiledFilePath($hash)) ? $hash : false;
}
return false;
}
/**
* Tries to save compiled hash to cache.
* @param string $file the view file.
* @param string $hash generated hash for compiled file name that will save.
* @return boolean whether the value is successfully stored into cache.
*/
protected function saveHashToCache($file, $hash)
{
if ($this->cacheComponent === false) {
return false;
}
$dependency = $this->cacheDependencyConfig;
if ($dependency !== null && !($dependency instanceof Dependency)) {
$dependency = Yii::createObject($dependency);
}
if ($dependency instanceof YiiFileDependency) {
$dependency->fileName = $file;
}
if ($dependency instanceof FileDependency) {
$dependency->compiledFileName = $this->getCompiledFilePath($hash);
}
return $this->cacheComponent->set($this->getCacheKey($file), $hash, 0, $dependency);
}
/**
* Get unique key that can represent this file uniquely among other files.
* @param string $file
* @return string
*/
public function getCacheKey($file)
{
return $this->cacheKeyPrefix.str_replace('\\', '/', $file);
}
/**
* @param string $hash generated hash for file.
* @return string the full path to compiled file.
*/
public function getCompiledFilePath($hash)
{
return rtrim(Yii::getAlias($this->compiledPath), '\/').'/'.substr($hash, 0, 2).'/'.substr($hash, 2).'.php';
}
/**
* @param string $hash generated hash for file.
* @return string the full path to map file.
*/
public function getMapFilePath($hash)
{
return rtrim(Yii::getAlias($this->compiledPath), '\/').'/'.substr($hash, 0, 2).'/'.substr($hash, 2).'.map';
}
}