Completed
Push — master ( fbc505...96c8c7 )
by Christian
03:41 queued 01:40
created

StandardCacheWarmer::createClosure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
rs 9.4285
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
            foreach ($this->detectControllerNamesInTemplateRootPaths($paths->getTemplateRootPaths()) as $controllerName) {
116 View Code Duplication
                foreach ($paths->resolveAvailableTemplateFiles($controllerName, $format) as $templateFile) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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)
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...
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)
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...
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) {
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...
313
            return file_get_contents($templatePathAndFilename, FILE_TEXT);
314
        };
315
    }
316
}
317