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

CPS-IT / handlebars / 21024750977

15 Jan 2026 08:32AM UTC coverage: 89.818% (-1.2%) from 91.063%
21024750977

push

github

web-flow
Merge pull request #516 from CPS-IT/feature/data-sources

[FEATURE] Extract processor data source handling to a separate component

113 of 137 new or added lines in 7 files covered. (82.48%)

1235 of 1375 relevant lines covered (89.82%)

6.2 hits per line

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

86.21
/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\Frontend;
25

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

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

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

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

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

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

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

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

182
        // Process variables with temporary cObj
183
        $processor = Renderer\Variables\VariablesProcessor::for($cObj);
4✔
184
        $processedVariables = $processor->process($variables);
4✔
185

186
        // Apply processed variables, either override processed data (if no target variable name is given)
187
        // or merge with processed data using given target variable name ("as")
188
        if ($as === null) {
4✔
189
            $processedData = $processedVariables;
3✔
190
        } else {
191
            $processedData[$as] = $processedVariables;
1✔
192
        }
193

194
        return $processedData;
4✔
195
    }
196
}
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