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

FluidTYPO3 / vhs / 30001290505

23 Jul 2026 10:57AM UTC coverage: 70.309% (-1.7%) from 72.022%
30001290505

push

github

NamelessCoder
[TER] 8.0.0

4819 of 6854 relevant lines covered (70.31%)

6.54 hits per line

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

79.37
/Classes/ViewHelpers/Iterator/ExtractViewHelper.php
1
<?php
2
namespace FluidTYPO3\Vhs\ViewHelpers\Iterator;
3

4
/*
5
 * This file is part of the FluidTYPO3/Vhs project under GPLv2 or later.
6
 *
7
 * For the full copyright and license information, please read the
8
 * LICENSE.md file that was distributed with this source code.
9
 */
10

11
use FluidTYPO3\Vhs\Core\ViewHelper\AbstractViewHelper;
12
use TYPO3\CMS\Core\Log\LogManager;
13
use TYPO3\CMS\Core\Utility\GeneralUtility;
14
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
15
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
16

17
/**
18
 * ### Iterator / Extract VieWHelper
19
 *
20
 * Loop through the iterator and extract a key, optionally join the
21
 * results if more than one value is found.
22
 *
23
 * #### Extract values from an array by key
24
 *
25
 * The extbase version of indexed_search returns an array of the
26
 * previous search, which cannot easily be shown in the input field
27
 * of the result page. This can be solved.
28
 *
29
 * #### Input from extbase version of indexed_search">
30
 *
31
 * ```
32
 * [
33
 *     0 => [
34
 *         'sword' => 'firstWord',
35
 *         'oper' => 'AND'
36
 *     ],
37
 *     1 => [
38
 *         'sword' => 'secondWord',
39
 *         'oper' => 'AND'
40
 *     ],
41
 *     3 => [
42
 *         'sword' => 'thirdWord',
43
 *         'oper' => 'AND'
44
 *     ]
45
 * ]
46
 * ```
47
 *
48
 * Show the previous search words in the search form of the
49
 * result page:
50
 *
51
 * #### Example
52
 *
53
 * ```
54
 * <f:form.textfield name="search[sword]"
55
 *     value="{v:iterator.extract(key:'sword', content: searchWords) -> v:iterator.implode(glue: ' ')}"
56
 *     class="tx-indexedsearch-searchbox-sword" />
57
 * ```
58
 *
59
 * #### Get the names of several users
60
 *
61
 * Provided we have a bunch of FrontendUsers and we need to show
62
 * their firstname combined into a string:
63
 *
64
 * ```
65
 * <h2>Welcome
66
 * <v:iterator.implode glue=", "><v:iterator.extract key="firstname" content="frontendUsers" /></v:iterator.implode>
67
 * <!-- alternative: -->
68
 * {frontendUsers -> v:iterator.extract(key: 'firstname') -> v:iterator.implode(glue: ', ')}
69
 * </h2>
70
 * ```
71
 *
72
 * #### Output
73
 *
74
 * ```
75
 * <h2>Welcome Peter, Paul, Marry</h2>
76
 * ```
77
 *
78
 * #### Complex example
79
 *
80
 * ```
81
 * {anArray->v:iterator.extract(path: 'childProperty.secondNestedChildObject')
82
 *     -> v:iterator.sort(direction: 'DESC', sortBy: 'propertyOnSecondChild')
83
 *     -> v:iterator.slice(length: 10)->v:iterator.extract(key: 'uid')}
84
 * ```
85
 *
86
 * #### Single return value
87
 *
88
 * Outputs the "uid" value of the first record in variable $someRecords without caring if there are more than
89
 * one records. Always extracts the first value and then stops. Equivalent of changing -> v:iterator.first().
90
 *
91
 * ```
92
 * {someRecords -> v:iterator.extract(key: 'uid', single: TRUE)}
93
 * ```
94
 */
95
class ExtractViewHelper extends AbstractViewHelper
96
{
97
    /**
98
     * @var boolean
99
     */
100
    protected $escapeChildren = false;
101

102
    /**
103
     * @var boolean
104
     */
105
    protected $escapeOutput = false;
106

107
    public function initializeArguments(): void
108
    {
109
        $this->registerArgument(
3✔
110
            'content',
3✔
111
            'mixed',
3✔
112
            'The array or Iterator that contains either the value or arrays of values'
3✔
113
        );
3✔
114
        $this->registerArgument(
3✔
115
            'key',
3✔
116
            'string',
3✔
117
            'The name of the key from which you wish to extract the value',
3✔
118
            true
3✔
119
        );
3✔
120
        $this->registerArgument(
3✔
121
            'recursive',
3✔
122
            'boolean',
3✔
123
            'If TRUE, attempts to extract the key from deep nested arrays',
3✔
124
            false,
3✔
125
            true
3✔
126
        );
3✔
127
        $this->registerArgument(
3✔
128
            'single',
3✔
129
            'boolean',
3✔
130
            'If TRUE, returns only one value - always the first one - instead of an array of values',
3✔
131
            false,
3✔
132
            false
3✔
133
        );
3✔
134
    }
135

136
    /**
137
     * @return mixed
138
     */
139
    public static function renderStatic(
140
        array $arguments,
141
        \Closure $renderChildrenClosure,
142
        RenderingContextInterface $renderingContext
143
    ) {
144
        /** @var array $content */
145
        $content = $arguments['content'] ?? $renderChildrenClosure();
3✔
146
        /** @var string $key */
147
        $key = $arguments['key'];
3✔
148
        $recursive = (bool) $arguments['recursive'];
3✔
149
        $single = (bool) $arguments['single'];
3✔
150
        try {
151
            // extraction from Iterators could potentially use a getter method which throws
152
            // exceptions - although this would be bad practice. Catch the exception here
153
            // and turn it into a WARNING log message so that output does not break.
154
            if ($recursive) {
3✔
155
                $result = static::recursivelyExtractKey($content, $key);
3✔
156
            } else {
157
                $result = static::extractByKey($content, $key);
3✔
158
            }
159
        } catch (\Exception $error) {
×
160
            if (class_exists(LogManager::class)) {
×
161
                /** @var LogManager $logManager */
162
                $logManager = GeneralUtility::makeInstance(LogManager::class);
×
163
                $logManager->getLogger(__CLASS__)->warning($error->getMessage(), ['content' => $content]);
×
164
            } else {
165
                GeneralUtility::sysLog($error->getMessage(), 'vhs', GeneralUtility::SYSLOG_SEVERITY_WARNING);
×
166
            }
167
            $result = [];
×
168
        }
169

170
        if ($single && ($result instanceof \Traversable || is_array($result))) {
3✔
171
            return reset($result);
×
172
        }
173

174
        return $result;
3✔
175
    }
176

177
    /**
178
     * Extract by key
179
     *
180
     * @param mixed $iterator
181
     * @return mixed NULL or whatever we found at $key
182
     * @throws \Exception
183
     */
184
    protected static function extractByKey($iterator, string $key)
185
    {
186
        if (!is_array($iterator) && !$iterator instanceof \Traversable) {
×
187
            throw new \Exception('Traversable object or array expected but received ' . gettype($iterator), 1361532490);
×
188
        }
189

190
        $result = ObjectAccess::getPropertyPath($iterator, $key);
×
191

192
        return $result;
×
193
    }
194

195
    /**
196
     * Recursively extract the key
197
     *
198
     * @param mixed $iterator
199
     * @return array
200
     * @throws \Exception
201
     */
202
    protected static function recursivelyExtractKey($iterator, string $key)
203
    {
204
        if (!is_array($iterator) && !$iterator instanceof \Traversable) {
3✔
205
            throw new \Exception('Traversable object or array expected but received ' . gettype($iterator), 1515498714);
×
206
        }
207

208
        $content = [];
3✔
209

210
        foreach ($iterator as $v) {
3✔
211
            // Lets see if we find something directly:
212
            $result = ObjectAccess::getPropertyPath($v, $key);
3✔
213
            if (null !== $result) {
3✔
214
                $content[] = $result;
3✔
215
            } elseif (is_array($v) || $v instanceof \Traversable) {
3✔
216
                $content[] = static::recursivelyExtractKey($v, $key);
3✔
217
            }
218
        }
219

220
        $content = static::flattenArray($content);
3✔
221

222
        return $content;
3✔
223
    }
224

225
    /**
226
     * Flatten the result structure, to iterate it cleanly in fluid
227
     */
228
    protected static function flattenArray(array $content, array $flattened = []): array
229
    {
230
        if (empty($content)) {
3✔
231
            return $content;
×
232
        }
233

234
        foreach ($content as $sub) {
3✔
235
            if (is_array($sub)) {
3✔
236
                $flattened = static::flattenArray($sub, $flattened);
3✔
237
            } else {
238
                $flattened[] = $sub;
3✔
239
            }
240
        }
241

242
        return $flattened;
3✔
243
    }
244
}
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