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

FluidTYPO3 / vhs / 13566190336

27 Feb 2025 12:18PM UTC coverage: 72.127% (-0.6%) from 72.746%
13566190336

push

github

NamelessCoder
[TER] 7.1.0

5649 of 7832 relevant lines covered (72.13%)

20.01 hits per line

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

87.3
/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\Traits\CompileWithRenderStatic;
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
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
17

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

100
    /**
101
     * @var boolean
102
     */
103
    protected $escapeChildren = false;
104

105
    /**
106
     * @var boolean
107
     */
108
    protected $escapeOutput = false;
109

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

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

173
        if ($single && ($result instanceof \Traversable || is_array($result))) {
71✔
174
            return reset($result);
7✔
175
        }
176

177
        return $result;
64✔
178
    }
179

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

193
        $result = ObjectAccess::getPropertyPath($iterator, $key);
56✔
194

195
        return $result;
56✔
196
    }
197

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

211
        $content = [];
15✔
212

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

223
        $content = static::flattenArray($content);
15✔
224

225
        return $content;
15✔
226
    }
227

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

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

245
        return $flattened;
12✔
246
    }
247
}
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