1
|
|
|
<?php |
2
|
|
|
namespace TYPO3Fluid\Fluid\Core\Cache; |
3
|
|
|
|
4
|
|
|
/* |
5
|
|
|
* This file belongs to the package "TYPO3 Fluid". |
6
|
|
|
* See LICENSE.txt that was shipped with this package. |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
use TYPO3Fluid\Fluid\Core\Compiler\FailedCompilingState; |
10
|
|
|
use TYPO3Fluid\Fluid\Core\Compiler\StopCompilingException; |
11
|
|
|
use TYPO3Fluid\Fluid\Core\Parser\ParsedTemplateInterface; |
12
|
|
|
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\Expression\ExpressionException; |
13
|
|
|
use TYPO3Fluid\Fluid\Core\Parser\TemplateParser; |
14
|
|
|
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; |
15
|
|
|
use TYPO3Fluid\Fluid\View\Exception; |
16
|
|
|
use TYPO3Fluid\Fluid\View\TemplatePaths; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Class StandardCacheWarmer |
20
|
|
|
* |
21
|
|
|
* Responsible for performing a full warmup process. |
22
|
|
|
* Receives just the RenderingContext (which can be custom for the |
23
|
|
|
* framework that invokes the warmup) and resolves all possible |
24
|
|
|
* template files in all supported formats and triggers compiling |
25
|
|
|
* of those templates. |
26
|
|
|
* |
27
|
|
|
* The compiling process can be supported in detail in templates |
28
|
|
|
* directly through using the `f:cache.*` collection of ViewHelpers. |
29
|
|
|
* The compiler is put into a special warmup mode which can in turn |
30
|
|
|
* be checked by ViewHelpers when compiling which allows third-party |
31
|
|
|
* ViewHelpers to more closely control how they are compiled, if |
32
|
|
|
* they are at all compilable. |
33
|
|
|
* |
34
|
|
|
* The result of the warmup process is returned as a |
35
|
|
|
* FluidCacheWarmupResult instance with reports for every template |
36
|
|
|
* file that was detected duringthe process; detailing whether or |
37
|
|
|
* not the template file was compiled, some metadata about the |
38
|
|
|
* template such as which Layout it uses, if any, and finally adds |
39
|
|
|
* mitigation suggestions when a template cannot be compiled. |
40
|
|
|
* |
41
|
|
|
* The mitigation suggestions are specifically generated by this |
42
|
|
|
* class and can be elaborated or changed completely by any third- |
43
|
|
|
* party implementation of FluidCacheWarmerInterface which allows |
44
|
|
|
* them to be specific to the framework in which Fluid is used. |
45
|
|
|
* The default set of mitigation suggestions are based on the |
46
|
|
|
* standard errors which can be thrown by the Fluid engine. |
47
|
|
|
*/ |
48
|
|
|
class StandardCacheWarmer implements FluidCacheWarmerInterface |
49
|
|
|
{ |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* Template file formats (file extensions) supported by this |
53
|
|
|
* cache warmer implementation. |
54
|
|
|
* |
55
|
|
|
* @var array |
56
|
|
|
*/ |
57
|
|
|
protected $formats = ['html', 'xml', 'txt', 'json', 'rtf', 'atom', 'rss']; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Warm up an entire collection of templates based on the |
61
|
|
|
* provided RenderingContext (the TemplatePaths carried by |
62
|
|
|
* the RenderingContext, to be precise). |
63
|
|
|
* |
64
|
|
|
* Returns a FluidCacheWarmupResult with result information |
65
|
|
|
* about all detected template files and the compiling of |
66
|
|
|
* those files. If a template fails to compile or throws an |
67
|
|
|
* error, a mitigation suggestion is included for that file. |
68
|
|
|
* |
69
|
|
|
* @param RenderingContextInterface $renderingContext |
70
|
|
|
* @return FluidCacheWarmupResult |
71
|
|
|
*/ |
72
|
|
|
public function warm(RenderingContextInterface $renderingContext) |
73
|
|
|
{ |
74
|
|
|
$renderingContext->getTemplateCompiler()->enterWarmupMode(); |
75
|
|
|
$result = new FluidCacheWarmupResult(); |
76
|
|
|
$result->merge( |
77
|
|
|
$this->warmupTemplateRootPaths($renderingContext), |
78
|
|
|
$this->warmupPartialRootPaths($renderingContext), |
79
|
|
|
$this->warmupLayoutRootPaths($renderingContext) |
80
|
|
|
); |
81
|
|
|
return $result; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Warm up _templateRootPaths_ of the RenderingContext's |
86
|
|
|
* TemplatePaths instance. |
87
|
|
|
* |
88
|
|
|
* Scans for template files recursively in all template root |
89
|
|
|
* paths while respecting overlays, e.g. if a path replaces |
90
|
|
|
* the template file of a lower priority path then only |
91
|
|
|
* one result is returned - the overlayed template file. In |
92
|
|
|
* other words the resolving happens exactly as if you were |
93
|
|
|
* attempting to render each detected controller, so that the |
94
|
|
|
* compiled template will be the same that is resolved when |
95
|
|
|
* rendering that controller. |
96
|
|
|
* |
97
|
|
|
* Also scans the root level of all templateRootPaths for |
98
|
|
|
* controller-less/fallback-action template files, e.g. files |
99
|
|
|
* which would be rendered if a specified controller's action |
100
|
|
|
* template does not exist (fallback-action) or if no controller |
101
|
|
|
* name was specified in the context (controller-less). |
102
|
|
|
* |
103
|
|
|
* Like other methods, returns a FluidCacheWarmupResult instance |
104
|
|
|
* which can be merged with other result instances. |
105
|
|
|
* |
106
|
|
|
* @param RenderingContextInterface $renderingContext |
107
|
|
|
* @return FluidCacheWarmupResult |
108
|
|
|
*/ |
109
|
|
|
protected function warmupTemplateRootPaths(RenderingContextInterface $renderingContext) |
110
|
|
|
{ |
111
|
|
|
$result = new FluidCacheWarmupResult(); |
112
|
|
|
$paths = $renderingContext->getTemplatePaths(); |
113
|
|
|
foreach ($this->formats as $format) { |
114
|
|
|
$paths->setFormat($format); |
115
|
|
|
foreach ($this->detectControllerNamesInTemplateRootPaths($paths->getTemplateRootPaths()) as $controllerName) { |
116
|
|
View Code Duplication |
foreach ($paths->resolveAvailableTemplateFiles($controllerName, $format) as $templateFile) { |
|
|
|
|
117
|
|
|
$state = $this->warmSingleFile( |
118
|
|
|
$templateFile, |
119
|
|
|
$paths->getTemplateIdentifier( |
120
|
|
|
$controllerName, |
121
|
|
|
basename($templateFile, '.' . $format) |
122
|
|
|
), |
123
|
|
|
$renderingContext |
124
|
|
|
); |
125
|
|
|
$result->add($state, $templateFile); |
126
|
|
|
} |
127
|
|
|
} |
128
|
|
View Code Duplication |
foreach ($paths->resolveAvailableTemplateFiles(null, $format) as $templateFile) { |
|
|
|
|
129
|
|
|
$state = $this->warmSingleFile( |
130
|
|
|
$templateFile, |
131
|
|
|
$paths->getTemplateIdentifier( |
132
|
|
|
'Default', |
133
|
|
|
basename($templateFile, '.' . $format) |
134
|
|
|
), |
135
|
|
|
$renderingContext |
136
|
|
|
); |
137
|
|
|
$result->add($state, $templateFile); |
138
|
|
|
} |
139
|
|
|
} |
140
|
|
|
return $result; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Warm up _partialRootPaths_ of the provided RenderingContext's |
145
|
|
|
* TemplatePaths instance. Simple, recursive processing of all |
146
|
|
|
* supported format template files in path(s), compiling only |
147
|
|
|
* the topmost (override) template file if the same template |
148
|
|
|
* exists in multiple partial root paths. |
149
|
|
|
* |
150
|
|
|
* Like other methods, returns a FluidCacheWarmupResult instance |
151
|
|
|
* which can be merged with other result instances. |
152
|
|
|
* |
153
|
|
|
* @param RenderingContextInterface $renderingContext |
154
|
|
|
* @return FluidCacheWarmupResult |
155
|
|
|
*/ |
156
|
|
View Code Duplication |
protected function warmupPartialRootPaths(RenderingContextInterface $renderingContext) |
|
|
|
|
157
|
|
|
{ |
158
|
|
|
$result = new FluidCacheWarmupResult(); |
159
|
|
|
$paths = $renderingContext->getTemplatePaths(); |
160
|
|
|
foreach ($this->formats as $format) { |
161
|
|
|
foreach ($paths->resolveAvailablePartialFiles($format) as $partialFile) { |
162
|
|
|
$paths->setFormat($format); |
163
|
|
|
$state = $this->warmSingleFile( |
164
|
|
|
$partialFile, |
165
|
|
|
$paths->getPartialIdentifier(basename($partialFile, '.' . $format)), |
166
|
|
|
$renderingContext |
167
|
|
|
); |
168
|
|
|
$result->add($state, $partialFile); |
169
|
|
|
} |
170
|
|
|
} |
171
|
|
|
return $result; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Warm up _layoutRootPaths_ of the provided RenderingContext's |
176
|
|
|
* TemplatePaths instance. Simple, recursive processing of all |
177
|
|
|
* supported format template files in path(s), compiling only |
178
|
|
|
* the topmost (override) template file if the same template |
179
|
|
|
* exists in multiple layout root paths. |
180
|
|
|
* |
181
|
|
|
* Like other methods, returns a FluidCacheWarmupResult instance |
182
|
|
|
* which can be merged with other result instances. |
183
|
|
|
* |
184
|
|
|
* @param RenderingContextInterface $renderingContext |
185
|
|
|
* @return FluidCacheWarmupResult |
186
|
|
|
*/ |
187
|
|
View Code Duplication |
protected function warmupLayoutRootPaths(RenderingContextInterface $renderingContext) |
|
|
|
|
188
|
|
|
{ |
189
|
|
|
$result = new FluidCacheWarmupResult(); |
190
|
|
|
$paths = $renderingContext->getTemplatePaths(); |
191
|
|
|
foreach ($this->formats as $format) { |
192
|
|
|
foreach ($paths->resolveAvailableLayoutFiles($format) as $layoutFile) { |
193
|
|
|
$paths->setFormat($format); |
194
|
|
|
$state = $this->warmSingleFile( |
195
|
|
|
$layoutFile, |
196
|
|
|
$paths->getLayoutIdentifier(basename($layoutFile, '.' . $layoutFile)), |
197
|
|
|
$renderingContext |
198
|
|
|
); |
199
|
|
|
$result->add($state, $layoutFile); |
200
|
|
|
} |
201
|
|
|
} |
202
|
|
|
return $result; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Detect all available controller names in provided TemplateRootPaths |
207
|
|
|
* array, returning the "basename" components of controller-template |
208
|
|
|
* directories encountered, as an array. |
209
|
|
|
* |
210
|
|
|
* @param string $templateRootPaths |
211
|
|
|
* @return \Generator |
212
|
|
|
*/ |
213
|
|
|
protected function detectControllerNamesInTemplateRootPaths(array $templateRootPaths) |
214
|
|
|
{ |
215
|
|
|
foreach ($templateRootPaths as $templateRootPath) { |
216
|
|
|
foreach ((array) glob(rtrim($templateRootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '*') as $pathName) { |
217
|
|
|
if (is_dir($pathName)) { |
218
|
|
|
yield basename($pathName); |
219
|
|
|
} |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Warm up a single template file. |
226
|
|
|
* |
227
|
|
|
* Performs reading, parsing and attempts compiling of a single |
228
|
|
|
* template file. Catches errors that may occur and reports them |
229
|
|
|
* in a FailedCompilingState (which can then be `add()`'ed to |
230
|
|
|
* the FluidCacheWarmupResult to assimilate the information within. |
231
|
|
|
* |
232
|
|
|
* Adds basic mitigation suggestions for each specific type of error, |
233
|
|
|
* giving hints to developers if a certain template fails to compile. |
234
|
|
|
* |
235
|
|
|
* @param string $templatePathAndFilename |
236
|
|
|
* @param string $identifier |
237
|
|
|
* @param RenderingContextInterface $renderingContext |
238
|
|
|
* @return ParsedTemplateInterface |
239
|
|
|
*/ |
240
|
|
|
protected function warmSingleFile($templatePathAndFilename, $identifier, RenderingContextInterface $renderingContext) |
241
|
|
|
{ |
242
|
|
|
$parsedTemplate = new FailedCompilingState(); |
243
|
|
|
$parsedTemplate->setVariableProvider($renderingContext->getVariableProvider()); |
244
|
|
|
$parsedTemplate->setCompilable(false); |
245
|
|
|
$parsedTemplate->setIdentifier($identifier); |
246
|
|
|
try { |
247
|
|
|
$parsedTemplate = $renderingContext->getTemplateParser()->getOrParseAndStoreTemplate( |
248
|
|
|
$identifier, |
249
|
|
|
$this->createClosure($templatePathAndFilename) |
250
|
|
|
); |
251
|
|
|
} catch (StopCompilingException $error) { |
252
|
|
|
$parsedTemplate->setFailureReason(sprintf('Compiling is intentionally disabled. Specific reason unknown. Message: "%s"', $error->getMessage())); |
253
|
|
|
$parsedTemplate->setMitigations([ |
254
|
|
|
'Can be caused by specific ViewHelpers. If this is is not intentional: avoid ViewHelpers which disable caches.', |
255
|
|
|
'If cache is intentionally disabled: consider using `f:cache.static` to cause otherwise uncompilable ViewHelpers\' output to be replaced with a static string in compiled templates.' |
256
|
|
|
]); |
257
|
|
|
} catch (ExpressionException $error) { |
258
|
|
|
$parsedTemplate->setFailureReason(sprintf('ExpressionNode evaluation error: %s', $error->getMessage())); |
259
|
|
|
$parsedTemplate->setMitigations([ |
260
|
|
|
'Emulate variables used in ExpressionNode using `f:cache.warmup` or assign in warming RenderingContext' |
261
|
|
|
]); |
262
|
|
|
} catch (\TYPO3Fluid\Fluid\Core\Parser\Exception $error) { |
263
|
|
|
$parsedTemplate->setFailureReason($error->getMessage()); |
264
|
|
|
$parsedTemplate->setMitigations([ |
265
|
|
|
'Fix possible syntax errors.', |
266
|
|
|
'Check that all ViewHelpers are correctly referenced and namespaces loaded (note: namespaces may be added externally!)', |
267
|
|
|
'Check that all ExpressionNode types used by the template are loaded (note: may depend on RenderingContext implementation!)', |
268
|
|
|
'Emulate missing variables used in expressions by using `f:cache.warmup` around your template code.' |
269
|
|
|
]); |
270
|
|
|
} catch (\TYPO3Fluid\Fluid\Core\ViewHelper\Exception $error) { |
271
|
|
|
$parsedTemplate->setFailureReason(sprintf('ViewHelper threw Exception: %s', $error->getMessage())); |
272
|
|
|
$parsedTemplate->setMitigations([ |
273
|
|
|
'Emulate missing variables using `f:cache.warmup` around failing ViewHelper.', |
274
|
|
|
'Emulate globals / context required by ViewHelper.', |
275
|
|
|
'Disable caching for template if ViewHelper depends on globals / context that cannot be emulated.' |
276
|
|
|
]); |
277
|
|
|
} catch (\TYPO3Fluid\Fluid\Core\Exception $error) { |
278
|
|
|
$parsedTemplate->setFailureReason(sprintf('Fluid engine error: %s', $error->getMessage())); |
279
|
|
|
$parsedTemplate->setMitigations([ |
280
|
|
|
'Search online for additional information about specific error.' |
281
|
|
|
]); |
282
|
|
|
} catch (Exception $error) { |
283
|
|
|
$parsedTemplate->setFailureReason(sprintf('Fluid view error: %s', $error->getMessage())); |
284
|
|
|
$parsedTemplate->setMitigations([ |
285
|
|
|
'Investigate reported error in View class for missing variable checks, missing configuration etc.', |
286
|
|
|
'Consider using a different View class for rendering in warmup mode (a custom rendering context can provide it)' |
287
|
|
|
]); |
288
|
|
|
} catch (\RuntimeException $error) { |
289
|
|
|
$parsedTemplate->setFailureReason( |
290
|
|
|
sprintf( |
291
|
|
|
'General error: %s line %s threw %s (code: %d)', |
292
|
|
|
get_class($error), |
293
|
|
|
$error->getFile(), |
294
|
|
|
$error->getLine(), |
295
|
|
|
$error->getMessage(), |
296
|
|
|
$error->getCode() |
297
|
|
|
) |
298
|
|
|
); |
299
|
|
|
$parsedTemplate->setMitigations([ |
300
|
|
|
'There are no automated suggestions for mitigating this issue. An online search may yield more information.' |
301
|
|
|
]); |
302
|
|
|
} |
303
|
|
|
return $parsedTemplate; |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
/** |
307
|
|
|
* @param string $templatePathAndFilename |
308
|
|
|
* @return \Closure |
309
|
|
|
*/ |
310
|
|
|
protected function createClosure($templatePathAndFilename) |
311
|
|
|
{ |
312
|
|
|
return function(TemplateParser $parser, TemplatePaths $templatePaths) use ($templatePathAndFilename) { |
|
|
|
|
313
|
|
|
return file_get_contents($templatePathAndFilename, FILE_TEXT); |
314
|
|
|
}; |
315
|
|
|
} |
316
|
|
|
} |
317
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.