1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* spiral |
4
|
|
|
* |
5
|
|
|
* @author Wolfy-J |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace Spiral\Views; |
9
|
|
|
|
10
|
|
|
use Spiral\Core\Component; |
11
|
|
|
use Spiral\Core\Container; |
12
|
|
|
use Spiral\Core\Container\SingletonInterface; |
13
|
|
|
use Spiral\Core\ContainerInterface; |
14
|
|
|
use Spiral\Debug\Traits\BenchmarkTrait; |
15
|
|
|
use Spiral\Files\FileManager; |
16
|
|
|
use Spiral\Files\FilesInterface; |
17
|
|
|
use Spiral\Views\Configs\ViewsConfig; |
18
|
|
|
use Spiral\Views\Exceptions\ViewsException; |
19
|
|
|
use Spiral\Views\Loaders\FileLoader; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Provides ability to manage view engines, loaders and environment (cache dependencies). |
23
|
|
|
* Attention, this is immutable class. |
24
|
|
|
*/ |
25
|
|
|
class ViewManager extends Component implements ViewsInterface, SingletonInterface |
26
|
|
|
{ |
27
|
|
|
use BenchmarkTrait; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Active view environment might define behaviour of engines and etc. |
31
|
|
|
* |
32
|
|
|
* @var EnvironmentInterface |
33
|
|
|
*/ |
34
|
|
|
private $environment = null; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Loader used to locate view files using simple notation (where no extension is included). |
38
|
|
|
* |
39
|
|
|
* @var LoaderInterface |
40
|
|
|
*/ |
41
|
|
|
private $loader = null; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* View engines cache. |
45
|
|
|
* |
46
|
|
|
* @var EngineInterface[] |
47
|
|
|
*/ |
48
|
|
|
private $engines = []; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var ViewsConfig |
52
|
|
|
*/ |
53
|
|
|
protected $config = null; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @var FilesInterface |
57
|
|
|
*/ |
58
|
|
|
protected $files = null; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* @invisible |
62
|
|
|
* @var ContainerInterface |
63
|
|
|
*/ |
64
|
|
|
protected $container = null; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* @param ViewsConfig $config |
68
|
|
|
* @param FilesInterface $files |
69
|
|
|
* @param ContainerInterface $container |
70
|
|
|
*/ |
71
|
|
|
public function __construct( |
72
|
|
|
ViewsConfig $config, |
73
|
|
|
FilesInterface $files = null, |
74
|
|
|
ContainerInterface $container = null |
75
|
|
|
) { |
76
|
|
|
$this->config = $config; |
77
|
|
|
$this->files = $files ?? new FileManager(); |
78
|
|
|
$this->container = $container ?? new Container(); |
|
|
|
|
79
|
|
|
|
80
|
|
|
//Define engine's behaviour |
81
|
|
|
$this->loader = new FileLoader($config->getNamespaces(), $files, $container); |
|
|
|
|
82
|
|
|
$this->environment = $this->createEnvironment($config); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Creates copy of view manager with new environment. |
87
|
|
|
* |
88
|
|
|
* @param EnvironmentInterface $environment |
89
|
|
|
* |
90
|
|
|
* @return ViewManager |
91
|
|
|
*/ |
92
|
|
View Code Duplication |
public function withEnvironment(EnvironmentInterface $environment): ViewManager |
|
|
|
|
93
|
|
|
{ |
94
|
|
|
$views = clone $this; |
95
|
|
|
$views->loader = clone $this->loader; |
96
|
|
|
$views->environment = $environment; |
97
|
|
|
|
98
|
|
|
foreach ($this->engines as $name => $engine) { |
99
|
|
|
$views->engines[$name] = $engine->withEnvironment($environment); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
return $views; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Current view environment. View environment defines isolated cache version which provides |
107
|
|
|
* ability to create multiple cached versions for some views and improve application |
108
|
|
|
* performance. |
109
|
|
|
* |
110
|
|
|
* Example: |
111
|
|
|
* $this->views->compile('home'); |
112
|
|
|
* |
113
|
|
|
* $this->translator->setLocale('ru'); |
114
|
|
|
* |
115
|
|
|
* //Different cache id |
116
|
|
|
* $this->views->compile('home'); |
117
|
|
|
* |
118
|
|
|
* @return EnvironmentInterface |
119
|
|
|
*/ |
120
|
|
|
public function getEnvironment(): EnvironmentInterface |
121
|
|
|
{ |
122
|
|
|
return $this->environment; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* @param LoaderInterface $loader |
127
|
|
|
* |
128
|
|
|
* @return ViewManager |
129
|
|
|
*/ |
130
|
|
View Code Duplication |
public function withLoader(LoaderInterface $loader): ViewManager |
|
|
|
|
131
|
|
|
{ |
132
|
|
|
$views = clone $this; |
133
|
|
|
$views->loader = $loader; |
134
|
|
|
$views->environment = clone $this->environment; |
135
|
|
|
|
136
|
|
|
//Not carrying already built engines with us |
137
|
|
|
foreach ($this->engines as $name => $engine) { |
138
|
|
|
$views->engines[$name] = $engine->withLoader($views->engineLoader($name)); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
return $views; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* View loader. |
146
|
|
|
* |
147
|
|
|
* @return LoaderInterface |
148
|
|
|
*/ |
149
|
|
|
public function getLoader(): LoaderInterface |
150
|
|
|
{ |
151
|
|
|
return $this->loader; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* {@inheritdoc} |
156
|
|
|
*/ |
157
|
|
|
public function get(string $path): ViewInterface |
158
|
|
|
{ |
159
|
|
|
$engine = $this->detectEngine($path); |
160
|
|
|
|
161
|
|
|
return $this->engine($engine)->get($path); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* {@inheritdoc} |
166
|
|
|
*/ |
167
|
|
|
public function render(string $path, array $context = []): string |
168
|
|
|
{ |
169
|
|
|
$engine = $this->detectEngine($path); |
170
|
|
|
|
171
|
|
|
return $this->engine($engine)->render($path, $context); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Pre-compile desired view file. |
176
|
|
|
* |
177
|
|
|
* @param string $path |
178
|
|
|
*/ |
179
|
|
|
public function compile(string $path) |
180
|
|
|
{ |
181
|
|
|
$this->engine($this->detectEngine($path))->compile($path); |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Get engine by it's type. |
186
|
|
|
* |
187
|
|
|
* @param string $engine |
188
|
|
|
* |
189
|
|
|
* @return EngineInterface |
190
|
|
|
*/ |
191
|
|
|
public function engine(string $engine): EngineInterface |
192
|
|
|
{ |
193
|
|
|
//Checking for an instance in cache |
194
|
|
|
if (!isset($this->engines[$engine])) { |
195
|
|
|
$this->engines[$engine] = $this->createEngine($engine); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
return $this->engines[$engine]; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Detect engine by view path (automatically resolved based on extension). Method require |
203
|
|
|
* improvements and 2nd level caching. |
204
|
|
|
* |
205
|
|
|
* @param string $path |
206
|
|
|
* |
207
|
|
|
* @return string |
208
|
|
|
*/ |
209
|
|
|
protected function detectEngine(string $path): string |
210
|
|
|
{ |
211
|
|
|
//File extension can help us to detect engine faster (attention, does not work with complex |
212
|
|
|
//extensions at this moment). |
213
|
|
|
$extension = $this->files->extension($path); |
214
|
|
|
|
215
|
|
|
$result = null; |
216
|
|
|
$previousMatch = 0; |
217
|
|
|
foreach ($this->config->getEngines() as $engine) { |
218
|
|
|
if (!empty($extension)) { |
219
|
|
|
if ($extension == $this->config->engineExtension($engine)) { |
220
|
|
|
return $engine; |
221
|
|
|
} else { |
222
|
|
|
continue; |
223
|
|
|
} |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
if ( |
227
|
|
|
strlen($this->config->engineExtension($engine)) > $previousMatch |
228
|
|
|
&& $this->engineLoader($engine)->exists($path) |
229
|
|
|
) { |
230
|
|
|
$previousMatch = strlen($this->config->engineExtension($engine)); |
231
|
|
|
$result = $engine; |
232
|
|
|
} |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
if (empty($result)) { |
236
|
|
|
throw new ViewsException("Unable to detect view engine for '{$path}'"); |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
return $result; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* Create engine instance. |
244
|
|
|
* |
245
|
|
|
* @param string $engine |
246
|
|
|
* |
247
|
|
|
* @return EngineInterface |
248
|
|
|
* |
249
|
|
|
* @throws ViewsException |
250
|
|
|
*/ |
251
|
|
|
protected function createEngine(string $engine): EngineInterface |
252
|
|
|
{ |
253
|
|
|
if (!$this->config->hasEngine($engine)) { |
254
|
|
|
throw new ViewsException("Undefined engine '{$engine}'"); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
//Populating constructor parameters |
258
|
|
|
$options = $this->config->engineOptions($engine); |
259
|
|
|
$options += [ |
260
|
|
|
'loader' => $this->engineLoader($engine), |
261
|
|
|
'environment' => $this->getEnvironment() |
262
|
|
|
]; |
263
|
|
|
|
264
|
|
|
//We have to create an engine |
265
|
|
|
$benchmark = $this->benchmark('engine', $engine); |
266
|
|
|
try { |
267
|
|
|
//Creating engine instance |
268
|
|
|
return $this->container->make($this->config->engineClass($engine), $options); |
269
|
|
|
} finally { |
270
|
|
|
$this->benchmark($benchmark); |
271
|
|
|
} |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* @param ViewsConfig $config |
276
|
|
|
* |
277
|
|
|
* @return EnvironmentInterface |
278
|
|
|
*/ |
279
|
|
|
protected function createEnvironment(ViewsConfig $config): EnvironmentInterface |
280
|
|
|
{ |
281
|
|
|
return new DynamicEnvironment( |
282
|
|
|
$config->environmentDependencies(), |
283
|
|
|
$config->cacheEnabled(), |
284
|
|
|
$config->cacheDirectory(), |
285
|
|
|
$this->container |
286
|
|
|
); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* Getting isolated view loader (class responsible for locating files and isolating view |
291
|
|
|
* namespaces). Isolation is done by forcing specific file extension. MUST NOT return same |
292
|
|
|
* instance for different engines! |
293
|
|
|
* |
294
|
|
|
* @param string $engine Forced extension value. |
295
|
|
|
* |
296
|
|
|
* @return LoaderInterface |
297
|
|
|
*/ |
298
|
|
|
private function engineLoader(string $engine = null): LoaderInterface |
299
|
|
|
{ |
300
|
|
|
return $this->loader->withExtension($this->config->engineExtension($engine)); |
301
|
|
|
} |
302
|
|
|
} |
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 theid
property of an instance of theAccount
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.