• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CPS-IT / handlebars / 25095124980

29 Apr 2026 06:54AM UTC coverage: 89.586% (-0.3%) from 89.844%
25095124980

Pull #559

github

web-flow
Merge 12114452a into ff3fa7384
Pull Request #559: [TASK] Streamline resolving and rendering of templates and partials

13 of 17 new or added lines in 2 files covered. (76.47%)

1 existing line in 1 file now uncovered.

1385 of 1546 relevant lines covered (89.59%)

6.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.77
/Classes/Renderer/HandlebarsRenderer.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the TYPO3 CMS extension "handlebars".
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17

18
namespace CPSIT\Typo3Handlebars\Renderer;
19

20
use CPSIT\Typo3Handlebars\Cache;
21
use CPSIT\Typo3Handlebars\Event;
22
use CPSIT\Typo3Handlebars\Exception;
23
use DevTheorem\Handlebars;
24
use Psr\EventDispatcher;
25
use Psr\Http\Message;
26
use Symfony\Component\DependencyInjection;
27
use TYPO3\CMS\Core;
28
use TYPO3\CMS\Frontend;
29

30
/**
31
 * HandlebarsRenderer
32
 *
33
 * @author Elias Häußler <e.haeussler@familie-redlich.de>
34
 * @license GPL-2.0-or-later
35
 */
36
#[DependencyInjection\Attribute\AsAlias(Renderer::class)]
37
#[DependencyInjection\Attribute\Autoconfigure(tags: ['handlebars.renderer'])]
38
class HandlebarsRenderer implements Renderer
39
{
40
    protected ?bool $debugMode = null;
41

42
    public function __construct(
14✔
43
        protected readonly Cache\Cache $cache,
44
        protected readonly EventDispatcher\EventDispatcherInterface $eventDispatcher,
45
        protected readonly Helper\HelperRegistry $helperRegistry,
46
        protected readonly Template\TemplateResolver $templateResolver,
47
        protected readonly Variables\VariableBag $variableBag,
48
    ) {}
14✔
49

50
    /**
51
     * @throws Exception\TemplateFileIsInvalid
52
     * @throws Exception\TemplateFormatIsNotSupported
53
     * @throws Exception\TemplatePathIsNotResolvable
54
     * @throws Exception\ViewIsNotProperlyInitialized
55
     */
56
    public function render(RenderingContext $context): string
14✔
57
    {
58
        $template = $context->getTemplate($this->templateResolver);
14✔
59
        $compileResult = $this->compile($template);
11✔
60

61
        // Early return if template is empty
62
        if ($compileResult === null) {
10✔
63
            return '';
2✔
64
        }
65

66
        // Merge variables with default variables
67
        $mergedVariables = array_merge($this->variableBag->get(), $context->getVariables());
8✔
68

69
        // Dispatch before rendering event
70
        $beforeRenderingEvent = new Event\BeforeRenderingEvent($context, $mergedVariables, $this);
8✔
71
        $this->eventDispatcher->dispatch($beforeRenderingEvent);
8✔
72

73
        // Render content
74
        $renderer = Handlebars\Handlebars::template($compileResult);
8✔
75
        $content = $renderer($beforeRenderingEvent->getVariables(), [
8✔
76
            'data' => [
8✔
77
                'renderingContext' => $context,
8✔
78
            ],
8✔
79
            'helpers' => $this->helperRegistry->getAll(),
8✔
80
            'partialResolver' => $this->resolvePartial(...),
8✔
81
        ]);
8✔
82

83
        // Dispatch after rendering event
84
        $afterRenderingEvent = new Event\AfterRenderingEvent($context, $content, $this);
6✔
85
        $this->eventDispatcher->dispatch($afterRenderingEvent);
6✔
86

87
        return $afterRenderingEvent->getContent();
6✔
88
    }
89

90
    /**
91
     * Compile given template by Handlebars compiler.
92
     */
93
    protected function compile(string $template): ?string
11✔
94
    {
95
        // Early return if template is empty
96
        if (trim($template) === '') {
11✔
97
            return null;
2✔
98
        }
99

100
        // Disable cache if debugging is enabled or caching is disabled
101
        if ($this->isDebugModeEnabled() || $this->isCachingDisabled()) {
9✔
102
            $cache = new Cache\NullCache();
3✔
103
        } else {
104
            $cache = $this->cache;
6✔
105
        }
106

107
        // Get compile result from cache
108
        $compileResult = $cache->get($template);
9✔
109
        if ($compileResult !== null) {
9✔
110
            return $compileResult;
1✔
111
        }
112

113
        $compileResult = Handlebars\Handlebars::precompile($template, $this->getCompileOptions());
8✔
114

115
        // Write compiled template into cache
116
        $cache->set($template, $compileResult);
7✔
117

118
        return $compileResult;
7✔
119
    }
120

121
    protected function getCompileOptions(): Handlebars\Options
8✔
122
    {
123
        return new Handlebars\Options(
8✔
124
            knownHelpers: $this->getKnownHelpers(),
8✔
125
            strict: $this->isDebugModeEnabled(),
8✔
126
        );
8✔
127
    }
128

129
    /**
130
     * Get currently supported helpers as stubs.
131
     *
132
     * Returns an array of available (= known) helpers to provide a list of available
133
     * helpers for the compiler. This is recommended to improve the usage of those
134
     * helpers during compile time, whereas the concrete helper callables are
135
     * provided during runtime.
136
     *
137
     * @return array<string, true>
138
     */
139
    protected function getKnownHelpers(): array
8✔
140
    {
141
        return array_fill_keys(array_keys($this->helperRegistry->getAll()), true);
8✔
142
    }
143

144
    /**
145
     * Resolve given partial using partial resolver.
146
     *
147
     * Tries to resolve the given partial using the {@see $templateResolver}. If
148
     * no partial resolver is registered, `null` is returned. Otherwise, the
149
     * compiled partial is returned. Returning `null` will be handled as "partial
150
     * not found" by the renderer.
151
     *
152
     * @param string $name Name of the partial to be resolved
153
     * @return \Closure|null Compiled partial if partial could be resolved, `null` otherwise
154
     * @throws Exception\PartialPathIsNotResolvable
155
     * @throws Exception\TemplateFileIsInvalid
156
     * @throws Exception\TemplateFormatIsNotSupported
157
     * @throws Exception\ViewIsNotProperlyInitialized
158
     */
159
    protected function resolvePartial(string $name): ?\Closure
2✔
160
    {
161
        $context = new RenderingContext($name);
2✔
162
        $template = $context->getPartial($this->templateResolver);
2✔
163
        $compileResult = $this->compile($template);
1✔
164

165
        if ($compileResult === null) {
1✔
UNCOV
166
            return null;
×
167
        }
168

169
        return Handlebars\Handlebars::template($compileResult);
1✔
170
    }
171

172
    protected function isCachingDisabled(): bool
7✔
173
    {
174
        $cacheInstruction = $this->getServerRequest()->getAttribute('frontend.cache.instruction');
7✔
175

176
        if ($cacheInstruction instanceof Frontend\Cache\CacheInstruction) {
7✔
177
            return !$cacheInstruction->isCachingAllowed();
7✔
178
        }
179

180
        return false;
×
181
    }
182

183
    protected function isDebugModeEnabled(): bool
9✔
184
    {
185
        if ($this->debugMode !== null) {
9✔
186
            return $this->debugMode;
7✔
187
        }
188

189
        $typoScript = $this->getServerRequest()->getAttribute('frontend.typoscript');
9✔
190

191
        if ($typoScript instanceof Core\TypoScript\FrontendTypoScript && (bool)($typoScript->getConfigArray()['debug'] ?? false)) {
9✔
192
            return true;
2✔
193
        }
194

195
        return $this->debugMode = (bool)($GLOBALS['TYPO3_CONF_VARS']['FE']['debug'] ?? false);
8✔
196
    }
197

198
    protected function getServerRequest(): Message\ServerRequestInterface
9✔
199
    {
200
        /** @var Message\ServerRequestInterface $serverRequest */
201
        $serverRequest = $GLOBALS['TYPO3_REQUEST'];
9✔
202

203
        return $serverRequest;
9✔
204
    }
205
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc