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

CPS-IT / handlebars-forms / 22612387917

03 Mar 2026 07:13AM UTC coverage: 0.0%. Remained the same
22612387917

push

github

eliashaeussler
[FEATURE] Add `VALIDATION_RESULTS` value resolver

0 of 105 new or added lines in 1 file covered. (0.0%)

0 of 612 relevant lines covered (0.0%)

0.0 hits per line

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

0.0
/Classes/DataProcessing/Value/ValidationResultsValueResolver.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the TYPO3 CMS extension "handlebars_forms".
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\Typo3HandlebarsForms\DataProcessing\Value;
19

20
use CPSIT\Typo3HandlebarsForms\Domain;
21
use CPSIT\Typo3HandlebarsForms\Fluid;
22
use Psr\Http\Message;
23
use TYPO3\CMS\Extbase;
24
use TYPO3\CMS\Form;
25

26
/**
27
 * ValidationResultsValueResolver
28
 *
29
 * @author Elias Häußler <e.haeussler@familie-redlich.de>
30
 * @license GPL-2.0-or-later
31
 */
32
final readonly class ValidationResultsValueResolver implements ValueResolver
33
{
NEW
34
    public function __construct(
×
35
        private Fluid\ViewHelperInvoker $viewHelperInvoker,
NEW
36
    ) {}
×
37

NEW
38
    public function resolve(
×
39
        Form\Domain\Model\Renderable\RootRenderableInterface $renderable,
40
        Domain\Renderable\ViewModel\ViewModel $viewModel,
41
        ValueResolutionContext $context = new ValueResolutionContext(),
42
    ): mixed {
NEW
43
        $outputInstruction = $context['output'];
×
NEW
44
        $outputConfiguration = $context['output.'];
×
45

46
        // Resolve form definition from renderable
NEW
47
        if ($renderable instanceof Form\Domain\Model\Renderable\AbstractRenderable) {
×
NEW
48
            $property = $renderable->getRootForm()->getIdentifier() . '.' . $renderable->getIdentifier();
×
49
        } else {
NEW
50
            $property = $renderable->getIdentifier();
×
51
        }
52

NEW
53
        $request = $viewModel->renderingContext->getAttribute(Message\ServerRequestInterface::class);
×
NEW
54
        $extbaseRequestParameters = $request->getAttribute('extbase');
×
55

56
        // Early return when resolver was requested outside of extbase context
NEW
57
        if (!($extbaseRequestParameters instanceof Extbase\Mvc\ExtbaseRequestParameters)) {
×
NEW
58
            return null;
×
59
        }
60

NEW
61
        $validationResults = $extbaseRequestParameters->getOriginalRequestMappingResults()->forProperty($property);
×
62

63
        // Normalize output configuration
NEW
64
        if (!is_array($outputConfiguration)) {
×
NEW
65
            $outputConfiguration = null;
×
66
        }
67

68
        // Resolve validation results by a single rendering instruction
NEW
69
        if (is_string($outputInstruction)) {
×
NEW
70
            return $this->processRenderingInstruction(
×
NEW
71
                $renderable,
×
NEW
72
                $viewModel,
×
NEW
73
                $validationResults,
×
NEW
74
                $outputInstruction,
×
NEW
75
                $outputConfiguration ?? [],
×
NEW
76
            );
×
77
        }
78

79
        // Resolve complex rendering configuration
NEW
80
        if (!is_array($outputConfiguration)) {
×
NEW
81
            return $this->processRenderingConfiguration(
×
NEW
82
                $renderable,
×
NEW
83
                $viewModel,
×
NEW
84
                $validationResults,
×
NEW
85
                [],
×
NEW
86
            );
×
87
        }
88

89
        // Return raw results in case no rendering instructions are specified
NEW
90
        return $validationResults;
×
91
    }
92

93
    /**
94
     * @param array<string, mixed> $configuration
95
     * @return array<string, mixed>
96
     */
NEW
97
    private function processRenderingConfiguration(
×
98
        Form\Domain\Model\Renderable\RootRenderableInterface $renderable,
99
        Domain\Renderable\ViewModel\ViewModel $viewModel,
100
        Extbase\Error\Result $result,
101
        array $configuration,
102
    ): array {
NEW
103
        $processedData = [];
×
104

NEW
105
        foreach ($configuration as $key => $value) {
×
NEW
106
            $keyWithoutDot = rtrim($key, '.');
×
NEW
107
            $keyWithDot = $keyWithoutDot . '.';
×
108

NEW
109
            if (is_array($value) && !array_key_exists($keyWithoutDot, $processedData)) {
×
NEW
110
                $processedData[$keyWithoutDot] = $this->processRenderingConfiguration(
×
NEW
111
                    $renderable,
×
NEW
112
                    $viewModel,
×
NEW
113
                    $result,
×
NEW
114
                    $value,
×
NEW
115
                );
×
NEW
116
            } elseif (is_string($value)) {
×
NEW
117
                $processedData[$keyWithoutDot] = $this->processRenderingInstruction(
×
NEW
118
                    $renderable,
×
NEW
119
                    $viewModel,
×
NEW
120
                    $result,
×
NEW
121
                    $value,
×
NEW
122
                    $configuration[$keyWithDot] ?? [],
×
NEW
123
                );
×
124
            }
125
        }
126

NEW
127
        return $processedData;
×
128
    }
129

130
    /**
131
     * @param array<string, mixed> $configuration
132
     */
NEW
133
    private function processRenderingInstruction(
×
134
        Form\Domain\Model\Renderable\RootRenderableInterface $renderable,
135
        Domain\Renderable\ViewModel\ViewModel $viewModel,
136
        Extbase\Error\Result $result,
137
        string $value,
138
        array $configuration,
139
    ): mixed {
NEW
140
        return match ($value) {
×
NEW
141
            'EACH_ERROR' => $this->processErrors($renderable, $viewModel, $result, $configuration),
×
NEW
142
            'ERROR_MESSAGE' => $this->processErrorMessage($renderable, $viewModel, $result),
×
NEW
143
            'PROPERTY' => $this->processProperty($result, $configuration),
×
NEW
144
            default => $value,
×
NEW
145
        };
×
146
    }
147

148
    /**
149
     * @param array<string, mixed> $configuration
150
     * @return ($renderable is Form\Domain\Model\Renderable\CompositeRenderableInterface|Form\Domain\Runtime\FormRuntime ? array<string, list<array<string, mixed>>> : list<array<string, mixed>>)
151
     */
NEW
152
    private function processErrors(
×
153
        Form\Domain\Model\Renderable\RootRenderableInterface $renderable,
154
        Domain\Renderable\ViewModel\ViewModel $viewModel,
155
        Extbase\Error\Result $result,
156
        array $configuration,
157
    ): array {
NEW
158
        $processedErrors = [];
×
159

NEW
160
        foreach ($result->getFlattenedErrors() as $propertyPath => $errors) {
×
NEW
161
            $currentRenderable = $renderable;
×
162

NEW
163
            if ($propertyPath !== '' && $renderable instanceof Form\Domain\Runtime\FormRuntime) {
×
NEW
164
                $currentRenderable = $renderable->getFormDefinition()->getElementByIdentifier($propertyPath);
×
165
            }
166

167
            // Skip errors if current renderable could not be resolved
NEW
168
            if ($currentRenderable === null) {
×
NEW
169
                continue;
×
170
            }
171

NEW
172
            $processedResult = [];
×
173

NEW
174
            foreach ($errors as $error) {
×
NEW
175
                $errorResult = new Extbase\Error\Result();
×
NEW
176
                $errorResult->addError($error);
×
177

NEW
178
                $processedResult[] = $this->processRenderingConfiguration(
×
NEW
179
                    $currentRenderable,
×
NEW
180
                    $viewModel,
×
NEW
181
                    $errorResult,
×
NEW
182
                    $configuration,
×
NEW
183
                );
×
184
            }
185

NEW
186
            $processedErrors[$propertyPath] = $processedResult;
×
187
        }
188

NEW
189
        if ($renderable instanceof Form\Domain\Model\Renderable\CompositeRenderableInterface
×
NEW
190
            || $renderable instanceof Form\Domain\Runtime\FormRuntime
×
191
        ) {
NEW
192
            return $processedErrors;
×
193
        }
194

NEW
195
        $firstProcessedError = reset($processedErrors);
×
196

NEW
197
        if ($firstProcessedError !== false) {
×
NEW
198
            return $firstProcessedError;
×
199
        }
200

NEW
201
        return [];
×
202
    }
203

NEW
204
    private function processErrorMessage(
×
205
        Form\Domain\Model\Renderable\RootRenderableInterface $renderable,
206
        Domain\Renderable\ViewModel\ViewModel $viewModel,
207
        Extbase\Error\Result $result,
208
    ): mixed {
NEW
209
        $error = $result->getFirstError();
×
210

211
        // Early return if no error is attached to result
NEW
212
        if ($error === false) {
×
NEW
213
            return null;
×
214
        }
215

NEW
216
        $translationResult = $this->viewHelperInvoker->invoke(
×
NEW
217
            $viewModel->renderingContext,
×
NEW
218
            Form\ViewHelpers\TranslateElementErrorViewHelper::class,
×
NEW
219
            [
×
NEW
220
                'element' => $renderable,
×
NEW
221
                'error' => $error,
×
NEW
222
            ],
×
NEW
223
        );
×
224

NEW
225
        return $translationResult->content;
×
226
    }
227

228
    /**
229
     * @param array<string, mixed> $configuration
230
     */
NEW
231
    private function processProperty(Extbase\Error\Result $result, array $configuration): mixed
×
232
    {
NEW
233
        $path = $configuration['path'] ?? null;
×
234

NEW
235
        if (!is_string($path)) {
×
NEW
236
            return null;
×
237
        }
238

NEW
239
        return Extbase\Reflection\ObjectAccess::getProperty($result, $path);
×
240
    }
241

NEW
242
    public static function getName(): string
×
243
    {
NEW
244
        return 'VALIDATION_RESULTS';
×
245
    }
246
}
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