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

CPS-IT / handlebars / 23363698435

20 Mar 2026 09:40PM UTC coverage: 89.861% (-0.4%) from 90.285%
23363698435

Pull #543

github

web-flow
Merge 223a31852 into 86597a598
Pull Request #543: [TASK] Update codebase to match PHPStan max level

139 of 157 new or added lines in 43 files covered. (88.54%)

26 existing lines in 11 files now uncovered.

1356 of 1509 relevant lines covered (89.86%)

6.82 hits per line

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

98.33
/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
        $compileResult = $this->compile($context);
14✔
59

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

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

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

72
        // Render content
73
        $renderer = Handlebars\Handlebars::template($compileResult);
7✔
74
        /** @var string $content */
75
        $content = $renderer($beforeRenderingEvent->getVariables(), [
7✔
76
            'helpers' => $this->helperRegistry->getAll(),
7✔
77
            'data' => [
7✔
78
                'renderingContext' => $context,
7✔
79
            ],
7✔
80
        ]);
7✔
81

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

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

89
    /**
90
     * Compile given template by Handlebars compiler.
91
     *
92
     * @throws Exception\TemplateFileIsInvalid
93
     * @throws Exception\TemplateFormatIsNotSupported
94
     * @throws Exception\TemplatePathIsNotResolvable
95
     * @throws Exception\ViewIsNotProperlyInitialized
96
     */
97
    protected function compile(RenderingContext $context): ?string
14✔
98
    {
99
        $template = $context->getTemplate($this->templateResolver);
14✔
100

101
        // Early return if template is empty
102
        if (trim($template) === '') {
11✔
103
            return null;
2✔
104
        }
105

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

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

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

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

124
        return $compileResult;
6✔
125
    }
126

127
    protected function getCompileOptions(): Handlebars\Options
8✔
128
    {
129
        return new Handlebars\Options(
8✔
130
            strict: $this->isDebugModeEnabled(),
8✔
131
            helpers: $this->getHelperStubs(),
8✔
132
            partialResolver: fn(Handlebars\Context $context, string $name) => $this->resolvePartial($name),
8✔
133
        );
8✔
134
    }
135

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

151
    /**
152
     * Resolve path to 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
     * partials' file contents are returned. Returning `null` will be handled as
157
     * "partial not found" by the renderer.
158
     *
159
     * This method is called by {@see Handlebars\Partial::resolve()}.
160
     *
161
     * @param string $name Name of the partial to be resolved
162
     * @return string|null Partial file contents if partial could be resolved, `null` otherwise
163
     * @throws Exception\PartialPathIsNotResolvable
164
     * @throws Exception\TemplateFormatIsNotSupported
165
     */
166
    protected function resolvePartial(string $name): ?string
2✔
167
    {
168
        $partial = @file_get_contents($this->templateResolver->resolvePartialPath($name));
2✔
169

170
        if ($partial === false) {
2✔
171
            return null;
1✔
172
        }
173

174
        return $partial;
1✔
175
    }
176

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

181
        if ($cacheInstruction instanceof Frontend\Cache\CacheInstruction) {
7✔
182
            return !$cacheInstruction->isCachingAllowed();
7✔
183
        }
184

UNCOV
185
        return false;
×
186
    }
187

188
    protected function isDebugModeEnabled(): bool
9✔
189
    {
190
        if ($this->debugMode !== null) {
9✔
191
            return $this->debugMode;
7✔
192
        }
193

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

196
        if ($typoScript instanceof Core\TypoScript\FrontendTypoScript && (bool)($typoScript->getConfigArray()['debug'] ?? false)) {
9✔
197
            return true;
2✔
198
        }
199

200
        return $this->debugMode = (bool)($GLOBALS['TYPO3_CONF_VARS']['FE']['debug'] ?? false);
8✔
201
    }
202

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

208
        return $serverRequest;
9✔
209
    }
210
}
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