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

CPS-IT / handlebars / 25100815934

29 Apr 2026 09:19AM UTC coverage: 89.817% (+0.04%) from 89.782%
25100815934

push

github

web-flow
Merge pull request #559 from CPS-IT/task/partial-resolver

26 of 26 new or added lines in 5 files covered. (100.0%)

1 existing line in 1 file now uncovered.

1376 of 1532 relevant lines covered (89.82%)

7.12 hits per line

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

97.01
/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(
27✔
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
    ) {}
27✔
49

50
    public function renderTemplate(RenderingContext $context): string
15✔
51
    {
52
        $template = $context->getTemplate($this->templateResolver);
15✔
53

54
        return $this->render($template, $context);
12✔
55
    }
56

57
    public function renderPartial(RenderingContext $context): string
12✔
58
    {
59
        $partial = $context->getPartial($this->templateResolver);
12✔
60

61
        return $this->render($partial, $context);
9✔
62
    }
63

64
    protected function render(string $template, RenderingContext $context): string
21✔
65
    {
66
        $compileResult = $this->compile($template);
21✔
67

68
        // Early return if template is empty
69
        if ($compileResult === null) {
19✔
70
            return '';
4✔
71
        }
72

73
        // Merge variables with default variables
74
        $mergedVariables = array_merge($this->variableBag->get(), $context->getVariables());
15✔
75

76
        // Dispatch before rendering event
77
        $beforeRenderingEvent = new Event\BeforeRenderingEvent($context, $mergedVariables, $this);
15✔
78
        $this->eventDispatcher->dispatch($beforeRenderingEvent);
15✔
79

80
        // Render content
81
        $renderer = Handlebars\Handlebars::template($compileResult);
15✔
82
        $content = $renderer($beforeRenderingEvent->getVariables(), [
15✔
83
            'data' => [
15✔
84
                'renderingContext' => $context,
15✔
85
            ],
15✔
86
            'helpers' => $this->helperRegistry->getAll(),
15✔
87
            'partialResolver' => $this->resolvePartial(...),
15✔
88
        ]);
15✔
89

90
        // Dispatch after rendering event
91
        $afterRenderingEvent = new Event\AfterRenderingEvent($context, $content, $this);
12✔
92
        $this->eventDispatcher->dispatch($afterRenderingEvent);
12✔
93

94
        return $afterRenderingEvent->getContent();
12✔
95
    }
96

97
    /**
98
     * Compile given template by Handlebars compiler.
99
     */
100
    protected function compile(string $template): ?string
21✔
101
    {
102
        // Early return if template is empty
103
        if (trim($template) === '') {
21✔
104
            return null;
4✔
105
        }
106

107
        // Disable cache if debugging is enabled or caching is disabled
108
        if ($this->isDebugModeEnabled() || $this->isCachingDisabled()) {
17✔
109
            $cache = new Cache\NullCache();
6✔
110
        } else {
111
            $cache = $this->cache;
11✔
112
        }
113

114
        // Get compile result from cache
115
        $compileResult = $cache->get($template);
17✔
116
        if ($compileResult !== null) {
17✔
117
            return $compileResult;
2✔
118
        }
119

120
        $compileResult = Handlebars\Handlebars::precompile($template, $this->getCompileOptions());
15✔
121

122
        // Write compiled template into cache
123
        $cache->set($template, $compileResult);
13✔
124

125
        return $compileResult;
13✔
126
    }
127

128
    protected function getCompileOptions(): Handlebars\Options
15✔
129
    {
130
        return new Handlebars\Options(
15✔
131
            knownHelpers: $this->getKnownHelpers(),
15✔
132
            strict: $this->isDebugModeEnabled(),
15✔
133
        );
15✔
134
    }
135

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

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

172
        if ($compileResult === null) {
2✔
UNCOV
173
            return null;
×
174
        }
175

176
        return Handlebars\Handlebars::template($compileResult);
2✔
177
    }
178

179
    protected function isCachingDisabled(): bool
13✔
180
    {
181
        $cacheInstruction = $this->getServerRequest()->getAttribute('frontend.cache.instruction');
13✔
182

183
        if ($cacheInstruction instanceof Frontend\Cache\CacheInstruction) {
13✔
184
            return !$cacheInstruction->isCachingAllowed();
13✔
185
        }
186

187
        return false;
×
188
    }
189

190
    protected function isDebugModeEnabled(): bool
17✔
191
    {
192
        if ($this->debugMode !== null) {
17✔
193
            return $this->debugMode;
13✔
194
        }
195

196
        $typoScript = $this->getServerRequest()->getAttribute('frontend.typoscript');
17✔
197

198
        if ($typoScript instanceof Core\TypoScript\FrontendTypoScript && (bool)($typoScript->getConfigArray()['debug'] ?? false)) {
17✔
199
            return true;
4✔
200
        }
201

202
        return $this->debugMode = (bool)($GLOBALS['TYPO3_CONF_VARS']['FE']['debug'] ?? false);
15✔
203
    }
204

205
    protected function getServerRequest(): Message\ServerRequestInterface
17✔
206
    {
207
        /** @var Message\ServerRequestInterface $serverRequest */
208
        $serverRequest = $GLOBALS['TYPO3_REQUEST'];
17✔
209

210
        return $serverRequest;
17✔
211
    }
212
}
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