Completed
Push — master ( a67b31...972668 )
by Mathias
08:05
created

StandardCacheWarmer::warmupTemplateRootPaths()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 8
nop 1
dl 0
loc 39
rs 8.6737
c 0
b 0
f 0
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
            $formatCutoffPoint = - (strlen($format) + 1);
116
            foreach ($paths->getTemplateRootPaths() as $templateRootPath) {
117
                $pathCutoffPoint = strlen($templateRootPath);
118
                foreach ($this->detectControllerNamesInTemplateRootPaths([$templateRootPath]) as $controllerName) {
119
                    foreach ($paths->resolveAvailableTemplateFiles($controllerName, $format) as $templateFile) {
120
                        $state = $this->warmSingleFile(
121
                            $templateFile,
122
                            $paths->getTemplateIdentifier(
123
                                $controllerName,
124
                                substr($templateFile, $pathCutoffPoint, $formatCutoffPoint)
125
                            ),
126
                            $renderingContext
127
                        );
128
                        $result->add($state, $templateFile);
129
                    }
130
                }
131
                $limitedPaths = clone $paths;
132
                $limitedPaths->setTemplateRootPaths([$templateRootPath]);
133
                foreach ($limitedPaths->resolveAvailableTemplateFiles(null, $format) as $templateFile) {
134
                    $state = $this->warmSingleFile(
135
                        $templateFile,
136
                        $paths->getTemplateIdentifier(
137
                            'Default',
138
                            substr($templateFile, $pathCutoffPoint, $formatCutoffPoint)
139
                        ),
140
                        $renderingContext
141
                    );
142
                    $result->add($state, $templateFile);
143
                }
144
            }
145
        }
146
        return $result;
147
    }
148
149
    /**
150
     * Warm up _partialRootPaths_ of the provided RenderingContext's
151
     * TemplatePaths instance. Simple, recursive processing of all
152
     * supported format template files in path(s), compiling only
153
     * the topmost (override) template file if the same template
154
     * exists in multiple partial root paths.
155
     *
156
     * Like other methods, returns a FluidCacheWarmupResult instance
157
     * which can be merged with other result instances.
158
     *
159
     * @param RenderingContextInterface $renderingContext
160
     * @return FluidCacheWarmupResult
161
     */
162 View Code Duplication
    protected function warmupPartialRootPaths(RenderingContextInterface $renderingContext)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
163
    {
164
        $result = new FluidCacheWarmupResult();
165
        $paths = $renderingContext->getTemplatePaths();
166
        foreach ($this->formats as $format) {
167
            $formatCutoffPoint = - (strlen($format) + 1);
168
            foreach ($paths->getPartialRootPaths() as $partialRootPath) {
169
                $limitedPaths = clone $paths;
170
                $limitedPaths->setPartialRootPaths([$partialRootPath]);
171
                $pathCutoffPoint = strlen($partialRootPath);
172
                foreach ($limitedPaths->resolveAvailablePartialFiles($format) as $partialFile) {
173
                    $paths->setFormat($format);
174
                    $state = $this->warmSingleFile(
175
                        $partialFile,
176
                        $paths->getPartialIdentifier(substr($partialFile, $pathCutoffPoint, $formatCutoffPoint)),
177
                        $renderingContext
178
                    );
179
                    $result->add($state, $partialFile);
180
                }
181
            }
182
        }
183
        return $result;
184
    }
185
186
    /**
187
     * Warm up _layoutRootPaths_ of the provided RenderingContext's
188
     * TemplatePaths instance. Simple, recursive processing of all
189
     * supported format template files in path(s), compiling only
190
     * the topmost (override) template file if the same template
191
     * exists in multiple layout root paths.
192
     *
193
     * Like other methods, returns a FluidCacheWarmupResult instance
194
     * which can be merged with other result instances.
195
     *
196
     * @param RenderingContextInterface $renderingContext
197
     * @return FluidCacheWarmupResult
198
     */
199 View Code Duplication
    protected function warmupLayoutRootPaths(RenderingContextInterface $renderingContext)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
200
    {
201
        $result = new FluidCacheWarmupResult();
202
        $paths = $renderingContext->getTemplatePaths();
203
        foreach ($this->formats as $format) {
204
            $formatCutoffPoint = - (strlen($format) + 1);
205
            foreach ($paths->getLayoutRootPaths() as $layoutRootPath) {
206
                $limitedPaths = clone $paths;
207
                $limitedPaths->setLayoutRootPaths([$layoutRootPath]);
208
                $pathCutoffPoint = strlen($layoutRootPath);
209
                foreach ($limitedPaths->resolveAvailableLayoutFiles($format) as $layoutFile) {
210
                    $paths->setFormat($format);
211
                    $state = $this->warmSingleFile(
212
                        $layoutFile,
213
                        $paths->getLayoutIdentifier(substr($layoutFile, $pathCutoffPoint, $formatCutoffPoint)),
214
                        $renderingContext
215
                    );
216
                    $result->add($state, $layoutFile);
217
                }
218
            }
219
        }
220
        return $result;
221
    }
222
223
    /**
224
     * Detect all available controller names in provided TemplateRootPaths
225
     * array, returning the "basename" components of controller-template
226
     * directories encountered, as an array.
227
     *
228
     * @param array $templateRootPaths
229
     * @return \Generator
230
     */
231
    protected function detectControllerNamesInTemplateRootPaths(array $templateRootPaths)
232
    {
233
        foreach ($templateRootPaths as $templateRootPath) {
234
            foreach ((array) glob(rtrim($templateRootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '*') as $pathName) {
235
                if (is_dir($pathName)) {
236
                    yield basename($pathName);
237
                }
238
            }
239
        }
240
    }
241
242
    /**
243
     * Warm up a single template file.
244
     *
245
     * Performs reading, parsing and attempts compiling of a single
246
     * template file. Catches errors that may occur and reports them
247
     * in a FailedCompilingState (which can then be `add()`'ed to
248
     * the FluidCacheWarmupResult to assimilate the information within.
249
     *
250
     * Adds basic mitigation suggestions for each specific type of error,
251
     * giving hints to developers if a certain template fails to compile.
252
     *
253
     * @param string $templatePathAndFilename
254
     * @param string $identifier
255
     * @param RenderingContextInterface $renderingContext
256
     * @return ParsedTemplateInterface
257
     */
258
    protected function warmSingleFile($templatePathAndFilename, $identifier, RenderingContextInterface $renderingContext)
259
    {
260
        $parsedTemplate = new FailedCompilingState();
261
        $parsedTemplate->setVariableProvider($renderingContext->getVariableProvider());
262
        $parsedTemplate->setCompilable(false);
263
        $parsedTemplate->setIdentifier($identifier);
264
        try {
265
            $parsedTemplate = $renderingContext->getTemplateParser()->getOrParseAndStoreTemplate(
266
                $identifier,
267
                $this->createClosure($templatePathAndFilename)
268
            );
269
        } catch (StopCompilingException $error) {
270
            $parsedTemplate->setFailureReason(sprintf('Compiling is intentionally disabled. Specific reason unknown. Message: "%s"', $error->getMessage()));
271
            $parsedTemplate->setMitigations([
272
                'Can be caused by specific ViewHelpers. If this is is not intentional: avoid ViewHelpers which disable caches.',
273
                '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.'
274
            ]);
275
        } catch (ExpressionException $error) {
276
            $parsedTemplate->setFailureReason(sprintf('ExpressionNode evaluation error: %s', $error->getMessage()));
277
            $parsedTemplate->setMitigations([
278
                'Emulate variables used in ExpressionNode using `f:cache.warmup` or assign in warming RenderingContext'
279
            ]);
280
        } catch (\TYPO3Fluid\Fluid\Core\Parser\Exception $error) {
281
            $parsedTemplate->setFailureReason($error->getMessage());
282
            $parsedTemplate->setMitigations([
283
                'Fix possible syntax errors.',
284
                'Check that all ViewHelpers are correctly referenced and namespaces loaded (note: namespaces may be added externally!)',
285
                'Check that all ExpressionNode types used by the template are loaded (note: may depend on RenderingContext implementation!)',
286
                'Emulate missing variables used in expressions by using `f:cache.warmup` around your template code.'
287
            ]);
288
        } catch (\TYPO3Fluid\Fluid\Core\ViewHelper\Exception $error) {
289
            $parsedTemplate->setFailureReason(sprintf('ViewHelper threw Exception: %s', $error->getMessage()));
290
            $parsedTemplate->setMitigations([
291
                'Emulate missing variables using `f:cache.warmup` around failing ViewHelper.',
292
                'Emulate globals / context required by ViewHelper.',
293
                'Disable caching for template if ViewHelper depends on globals / context that cannot be emulated.'
294
            ]);
295
        } catch (\TYPO3Fluid\Fluid\Core\Exception $error) {
296
            $parsedTemplate->setFailureReason(sprintf('Fluid engine error: %s', $error->getMessage()));
297
            $parsedTemplate->setMitigations([
298
                'Search online for additional information about specific error.'
299
            ]);
300
        } catch (Exception $error) {
301
            $parsedTemplate->setFailureReason(sprintf('Fluid view error: %s', $error->getMessage()));
302
            $parsedTemplate->setMitigations([
303
                'Investigate reported error in View class for missing variable checks, missing configuration etc.',
304
                'Consider using a different View class for rendering in warmup mode (a custom rendering context can provide it)'
305
            ]);
306
        } catch (\RuntimeException $error) {
307
            $parsedTemplate->setFailureReason(
308
                sprintf(
309
                    'General error: %s line %s threw %s (code: %d)',
310
                    get_class($error),
311
                    $error->getFile(),
312
                    $error->getLine(),
313
                    $error->getMessage(),
314
                    $error->getCode()
315
                )
316
            );
317
            $parsedTemplate->setMitigations([
318
                'There are no automated suggestions for mitigating this issue. An online search may yield more information.'
319
            ]);
320
        }
321
        return $parsedTemplate;
322
    }
323
324
    /**
325
     * @param string $templatePathAndFilename
326
     * @return \Closure
327
     */
328
    protected function createClosure($templatePathAndFilename)
329
    {
330
        return function(TemplateParser $parser, TemplatePaths $templatePaths) use ($templatePathAndFilename) {
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $templatePaths is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
331
            return file_get_contents($templatePathAndFilename, FILE_TEXT);
332
        };
333
    }
334
}
335