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

CPS-IT / handlebars / 34103667826

07 Sep 2026 09:00AM UTC coverage: 89.692% (-0.4%) from 90.104%
34103667826

push

github

web-flow
Merge pull request #635 from CPS-IT/feature/ds-streamline

[!!!][FEATURE] Streamline data source handling across all data processors

53 of 65 new or added lines in 4 files covered. (81.54%)

5 existing lines in 3 files now uncovered.

1662 of 1853 relevant lines covered (89.69%)

6.81 hits per line

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

87.5
/Classes/DataProcessing/IterableToArrayProcessor.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 Psr\Log;
22
use Symfony\Component\DependencyInjection;
23
use TYPO3\CMS\Extbase;
24
use TYPO3\CMS\Frontend;
25

26
/**
27
 * Data processor to convert a given iterable (e.g. a {@see Extbase\Persistence\QueryResultInterface}
28
 * as returned by an Extbase repository, an {@see Extbase\Persistence\ObjectStorage}, an
29
 * {@see \Iterator} or a {@see \Generator}) into a plain array.
30
 *
31
 * Example:
32
 * ========
33
 *
34
 * Given an Extbase controller assigns a repository query result to the view, e.g.:
35
 *
36
 *   $view->assign('news', $this->newsRepository->findAll());
37
 *
38
 * the resulting {@see Extbase\Persistence\QueryResultInterface} is available as
39
 * "news" variable in the processed data and can be converted into a plain array for use
40
 * within the Handlebars template. Each converted item is made available as "currentValue"
41
 * and can be further transformed using nested data processors:
42
 *
43
 * plugin.tx_news {
44
 *   handlebars {
45
 *     News::list {
46
 *       # ...
47
 *
48
 *       dataProcessing {
49
 *         10 = iterable-to-array
50
 *         10 {
51
 *           iterable = processedData:news
52
 *           as = newsItems
53
 *
54
 *           dataProcessing {
55
 *             10 = object-access
56
 *             10 {
57
 *               object = contentObjectConfiguration:currentValue
58
 *               path = title
59
 *               as = title
60
 *             }
61
 *           }
62
 *         }
63
 *       }
64
 *     }
65
 *   }
66
 * }
67
 *
68
 * @author Elias Häußler <e.haeussler@familie-redlich.de>
69
 * @license GPL-2.0-or-later
70
 */
71
#[DependencyInjection\Attribute\AutoconfigureTag('data.processor', ['identifier' => 'iterable-to-array'])]
72
final readonly class IterableToArrayProcessor implements Frontend\ContentObject\DataProcessorInterface
73
{
74
    public function __construct(
11✔
75
        private Log\LoggerInterface $logger,
76
        private Frontend\ContentObject\ContentDataProcessor $contentDataProcessor,
77
        private DataSource\DataSourceProvider $dataSourceProvider,
78
    ) {}
11✔
79

80
    /**
81
     * @param array<string, mixed> $contentObjectConfiguration
82
     * @param array<string, mixed> $processorConfiguration
83
     * @param array<string|int, mixed> $processedData
84
     * @return array<string|int, mixed>
85
     */
86
    public function process(
11✔
87
        Frontend\ContentObject\ContentObjectRenderer $cObj,
88
        array $contentObjectConfiguration,
89
        array $processorConfiguration,
90
        array $processedData,
91
    ): array {
92
        $collection = DataSource\DataSourceCollection::for(
11✔
93
            $cObj,
11✔
94
            $contentObjectConfiguration,
11✔
95
            $processorConfiguration,
11✔
96
            $processedData,
11✔
97
        );
11✔
98

99
        /** @var string $as */
100
        $as = $collection->resolve('as', DataSource\DataSource::ProcessorConfiguration, 'result');
11✔
101
        $preserveKeys = (bool)$collection->resolve('preserveKeys', DataSource\DataSource::ProcessorConfiguration, false);
11✔
102
        $dataProcessing = $collection->resolve('dataProcessing.', DataSource\DataSource::ProcessorConfiguration);
11✔
103
        $iterable = null;
11✔
104

105
        try {
106
            $iterable = $this->dataSourceProvider->provide($collection, 'iterable');
11✔
107
        } catch (Exception\DataSourceIsMissingInCollection $exception) {
3✔
UNCOV
108
            $this->logger->warning(
×
NEW
109
                'No variables provided for data source "{source}" while processing {table}:{uid}.',
×
UNCOV
110
                [
×
NEW
111
                    'source' => $exception->dataSource->value,
×
NEW
112
                    'table' => $cObj->getCurrentTable(),
×
NEW
113
                    'uid' => $collection->resolveCurrentUid(),
×
NEW
114
                ],
×
NEW
115
            );
×
116
        } catch (Exception\DataSourceIsNotSupported $exception) {
3✔
117
            $this->logger->warning(
2✔
118
                'Invalid data source keyword "{source}" passed while processing {table}:{uid}.',
2✔
119
                [
2✔
120
                    'source' => $exception->dataSourceIdentifier,
2✔
121
                    'table' => $cObj->getCurrentTable(),
2✔
122
                    'uid' => $collection->resolveCurrentUid(),
2✔
123
                ],
2✔
124
            );
2✔
125
        } catch (Exception\PathIsMissingInDataSource $exception) {
1✔
126
            $this->logger->warning(
1✔
127
                'Invalid path "{path}" for data source "{source}" passed while processing {table}:{uid}.',
1✔
128
                [
1✔
129
                    'path' => $exception->path,
1✔
130
                    'source' => $exception->dataSource->value,
1✔
131
                    'table' => $cObj->getCurrentTable(),
1✔
132
                    'uid' => $collection->resolveCurrentUid(),
1✔
133
                ],
1✔
134
            );
1✔
135
        }
136

137
        // Early return if resolved value is not iterable
138
        if (!is_iterable($iterable)) {
11✔
139
            $this->logger->warning(
5✔
140
                'Invalid iterable configured for "iterable-to-array" data processor while processing {table}:{uid}.',
5✔
141
                [
5✔
142
                    'table' => $cObj->getCurrentTable(),
5✔
143
                    'uid' => $collection->resolveCurrentUid(),
5✔
144
                ],
5✔
145
            );
5✔
146

147
            return $processedData;
5✔
148
        }
149

150
        $array = is_array($iterable) ? $iterable : iterator_to_array($iterable, $preserveKeys);
6✔
151

152
        // Process additional data processors for each item
153
        if (is_array($dataProcessing)) {
6✔
154
            foreach ($array as $key => $item) {
1✔
155
                $array[$key] = $this->contentDataProcessor->process(
1✔
156
                    $cObj,
1✔
157
                    [
1✔
158
                        'dataProcessing.' => $dataProcessing,
1✔
159
                        'currentValue' => $item,
1✔
160
                    ],
1✔
161
                    [],
1✔
162
                );
1✔
163
            }
164
        }
165

166
        $processedData[$as] = $preserveKeys ? $array : array_values($array);
6✔
167

168
        return $processedData;
6✔
169
    }
170
}
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