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

CPS-IT / handlebars / 21130750439

19 Jan 2026 08:40AM UTC coverage: 88.873% (-0.9%) from 89.774%
21130750439

Pull #518

github

web-flow
Merge c5e2e396c into 8a9dcca7f
Pull Request #518: [FEATURE] Allow pre- and post-processing in `process-variables` DP

17 of 33 new or added lines in 2 files covered. (51.52%)

1246 of 1402 relevant lines covered (88.87%)

6.22 hits per line

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

81.71
/Classes/DataProcessing/ProcessVariablesProcessor.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\DataProcessing;
19

20
use CPSIT\Typo3Handlebars\Exception;
21
use CPSIT\Typo3Handlebars\Renderer;
22
use Psr\Log;
23
use Symfony\Component\DependencyInjection;
24
use TYPO3\CMS\Core;
25
use TYPO3\CMS\Frontend;
26

27
/**
28
 * Data processor to process given variables, especially useful in combination with other data processors.
29
 *
30
 * Example (standalone):
31
 * =====================
32
 *
33
 * hero = HANDLEBARSTEMPLATE
34
 * hero {
35
 *   templateName = @hero
36
 *
37
 *   # ...
38
 *
39
 *   dataProcessing {
40
 *     10 = process-variables
41
 *     10 {
42
 *       if.isTrue.field = title
43
 *
44
 *       variables {
45
 *         header = TEXT
46
 *         header.field = title
47
 *
48
 *         teaser = TEXT
49
 *         teaser.field = teaser
50
 *         teaser.parseFunc < lib.parseFunc_RTE
51
 *
52
 *         # ...
53
 *       }
54
 *     }
55
 *   }
56
 * }
57
 *
58
 * Example (with other data processor):
59
 * ====================================
60
 *
61
 * accordion = HANDLEBARSTEMPLATE
62
 * accordion {
63
 *   templateName = @accordion
64
 *
65
 *   # ...
66
 *
67
 *   dataProcessing {
68
 *     10 = database-query
69
 *     10 {
70
 *       # ...
71
 *
72
 *       dataProcessing {
73
 *         10 = process-variables
74
 *         10 {
75
 *           table = tx_mysitepackage_domain_model_accordion_element
76
 *           as = accordionItem
77
 *           variables {
78
 *             template = @accordion-item
79
 *
80
 *             header = TEXT
81
 *             header.field = title
82
 *
83
 *             bodytext = TEXT
84
 *             bodytext.field = bodytext
85
 *             bodytext.parseFunc < lib.parseFunc_RTE
86
 *
87
 *             # ...
88
 *           }
89
 *         }
90
 *       }
91
 *     }
92
 *   }
93
 * }
94
 *
95
 * @author Elias Häußler <e.haeussler@familie-redlich.de>
96
 * @license GPL-2.0-or-later
97
 */
98
#[DependencyInjection\Attribute\AutoconfigureTag('data.processor', ['identifier' => 'process-variables'])]
99
final readonly class ProcessVariablesProcessor implements Frontend\ContentObject\DataProcessorInterface
100
{
101
    public function __construct(
7✔
102
        private DataSource\DataSourceProvider $dataSourceProvider,
103
        private Log\LoggerInterface $logger,
104
    ) {}
7✔
105

106
    /**
107
     * @param array<string, mixed> $contentObjectConfiguration
108
     * @param array<string, mixed> $processorConfiguration
109
     * @param array<string|int, mixed> $processedData
110
     * @return array<string|int, mixed>
111
     * @throws Frontend\ContentObject\Exception\ContentRenderingException
112
     * @throws Exception\ConfiguredProcessorIsUnsupported
113
     * @throws Exception\ReservedVariableCannotBeUsed
114
     */
115
    public function process(
7✔
116
        Frontend\ContentObject\ContentObjectRenderer $cObj,
117
        array $contentObjectConfiguration,
118
        array $processorConfiguration,
119
        array $processedData,
120
    ): array {
121
        $data = null;
7✔
122

123
        $collection = new DataSource\DataSourceCollection();
7✔
124
        $collection->addDataSource(DataSource\DataSource::ContentObjectRenderer, $cObj->data);
7✔
125
        $collection->addDataSource(DataSource\DataSource::ContentObjectConfiguration, $contentObjectConfiguration);
7✔
126
        $collection->addDataSource(DataSource\DataSource::ProcessedData, $processedData);
7✔
127
        $collection->addDataSource(DataSource\DataSource::ProcessorConfiguration, $processorConfiguration);
7✔
128

129
        try {
130
            $data = $this->dataSourceProvider->provide($collection);
7✔
131
        } catch (Exception\DataSourceIsMissingInCollection $exception) {
2✔
132
            $this->logger->warning(
×
133
                'No data provided for data source "{source}" while processing {table}:{uid}.',
×
134
                [
×
135
                    'source' => $exception->dataSource->value,
×
136
                    'table' => $cObj->getCurrentTable(),
×
137
                    'uid' => $collection->resolve('uid', DataSource\DataSource::ContentObjectRenderer, '*unknown*'),
×
138
                ],
×
139
            );
×
140
        } catch (Exception\DataSourceIsNotSupported $exception) {
2✔
141
            $this->logger->warning(
1✔
142
                'Invalid data source keyword "{source}" passed while processing {table}:{uid}.',
1✔
143
                [
1✔
144
                    'source' => $exception->dataSourceIdentifier,
1✔
145
                    'table' => $cObj->getCurrentTable(),
1✔
146
                    'uid' => $collection->resolve('uid', DataSource\DataSource::ContentObjectRenderer, '*unknown*'),
1✔
147
                ],
1✔
148
            );
1✔
149
        } catch (Exception\PathIsMissingInDataSource $exception) {
1✔
150
            $this->logger->warning(
1✔
151
                'Invalid path "{path}" for data source "{source}" passed while processing {table}:{uid}.',
1✔
152
                [
1✔
153
                    'path' => $exception->path,
1✔
154
                    'source' => $exception->dataSource->value,
1✔
155
                    'table' => $cObj->getCurrentTable(),
1✔
156
                    'uid' => $collection->resolve('uid', DataSource\DataSource::ContentObjectRenderer, '*unknown*'),
1✔
157
                ],
1✔
158
            );
1✔
159
        }
160

161
        $data ??= $cObj->data;
7✔
162
        $table = $collection->resolve(
7✔
163
            'table',
7✔
164
            [DataSource\DataSource::ProcessorConfiguration, DataSource\DataSource::ProcessedData],
7✔
165
            $cObj->getCurrentTable(),
7✔
166
        );
7✔
167
        $variables = $collection->resolve('variables.', DataSource\DataSource::ProcessorConfiguration);
7✔
168
        $as = $collection->resolve('as', DataSource\DataSource::ProcessorConfiguration);
7✔
169

170
        // Early return if no variables to process are configured
171
        if (!\is_array($variables)) {
7✔
172
            return $processedData;
2✔
173
        }
174

175
        // Use temporary cObj for processing
176
        $cObj = clone $cObj;
5✔
177
        $cObj->start($data, $table);
5✔
178

179
        // Early return if processing should be skipped according to a configured condition
180
        if (\is_array($processorConfiguration['if.'] ?? null) && !$cObj->checkIf($processorConfiguration['if.'])) {
5✔
181
            return $processedData;
1✔
182
        }
183

184
        // Trigger pre-processors
185
        $variables = $this->triggerDataSourceAwareProcessors(
4✔
186
            $processorConfiguration,
4✔
187
            'preProcessing',
4✔
188
            $variables,
4✔
189
            $collection,
4✔
190
            $cObj,
4✔
191
        );
4✔
192

193
        // Process variables with temporary cObj
194
        $processor = Renderer\Variables\VariablesProcessor::for($cObj);
4✔
195
        $processedVariables = $processor->process($variables);
4✔
196

197
        // Trigger post-processors
198
        $processedVariables = $this->triggerDataSourceAwareProcessors(
4✔
199
            $processorConfiguration,
4✔
200
            'postProcessing',
4✔
201
            $processedVariables,
4✔
202
            $collection,
4✔
203
            $cObj,
4✔
204
        );
4✔
205

206
        // Apply processed variables, either override processed data (if no target variable name is given)
207
        // or merge with processed data using given target variable name ("as")
208
        if ($as === null) {
4✔
209
            $processedData = $processedVariables;
3✔
210
        } else {
211
            $processedData[$as] = $processedVariables;
1✔
212
        }
213

214
        return $processedData;
4✔
215
    }
216

217
    /**
218
     * @param array<string, mixed> $processorConfiguration
219
     * @param array<string|int, mixed> $variables
220
     * @return array<string|int, mixed>
221
     * @throws Exception\ConfiguredProcessorIsUnsupported
222
     */
223
    private function triggerDataSourceAwareProcessors(
4✔
224
        array $processorConfiguration,
225
        string $processorKey,
226
        array $variables,
227
        DataSource\DataSourceCollection $collection,
228
        Frontend\ContentObject\ContentObjectRenderer $contentObjectRenderer,
229
    ): array {
230
        // Early return if no processors are registered
231
        if (!\is_array($processorConfiguration[$processorKey . '.'] ?? null)) {
4✔
232
            return $variables;
4✔
233
        }
234

NEW
235
        \ksort($processorConfiguration[$processorKey . '.']);
×
236

237
        /** @var string $processorClassName */
NEW
238
        foreach ($processorConfiguration[$processorKey . '.'] as $processorClassName) {
×
NEW
239
            if (!\is_a($processorClassName, DataSource\DataSourceAwareProcessor::class, true)) {
×
NEW
240
                throw new Exception\ConfiguredProcessorIsUnsupported($processorClassName);
×
241
            }
242

NEW
243
            $processor = Core\Utility\GeneralUtility::makeInstance($processorClassName);
×
NEW
244
            $variables = $processor->process($variables, $collection, $contentObjectRenderer);
×
245
        }
246

NEW
247
        return $variables;
×
248
    }
249
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc