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

NIT-Administrative-Systems / dynamic-forms / 8332724504

18 Mar 2024 07:48PM UTC coverage: 95.514% (+3.2%) from 92.343%
8332724504

push

github

web-flow
PHPUnit 10 Shift (#446)

1235 of 1293 relevant lines covered (95.51%)

145.15 hits per line

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

92.42
/src/Forms/ValidatedForm.php
1
<?php
2

3
namespace Northwestern\SysDev\DynamicForms\Forms;
4

5
use Illuminate\Contracts\Support\MessageBag;
6
use Illuminate\Contracts\Translation\Translator;
7
use Illuminate\Contracts\Validation\Validator;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Collection;
10
use Illuminate\Support\MessageBag as MessageBagImpl;
11
use Northwestern\SysDev\DynamicForms\Components\ComponentInterface;
12
use Northwestern\SysDev\DynamicForms\FileComponentRegistry;
13
use Northwestern\SysDev\DynamicForms\Forms\Concerns\HandlesTree;
14

15
class ValidatedForm implements Validator
16
{
17
    use HandlesTree;
18

19
    protected array $components;
20
    protected array $flatComponents;
21
    protected array $forgetComponentKeys = [];
22

23
    protected Collection $values;
24
    protected MessageBag $messages;
25

26
    public function __construct(array $components, array $values)
27
    {
28
        $this->components = $components;
34✔
29
        $messageBag = new MessageBagImpl;
34✔
30
        $transformedValues = collect();
34✔
31

32
        $this->populateComponentTreeWithData($this->components, $values);
34✔
33
        $this->flatComponents = $this->flattenComponents($this->components);
34✔
34

35
        $this->processComponentTreeCalculations($this->components);
34✔
36
        $this->processComponentTreeConditionals($this->components, shouldForget: false);
34✔
37

38
        $this->flatComponents = $this->flatValidatableComponents($this->components);
34✔
39

40
        foreach ($this->flatComponents as $component) {
34✔
41
            $messageBag->merge($component->validate());
34✔
42
            $transformedValues->put($component->key(), $component->submissionValue());
34✔
43
        }
44

45
        // If any components are unknown or were removed by conditional logic, discard the corresponding value
46
        $this->values = $transformedValues->only(array_keys($this->flatComponents));
34✔
47
        $this->messages = $messageBag;
34✔
48
    }
49

50
    /**
51
     * Gets validation failure messages.
52
     */
53
    public function messages(): MessageBag
54
    {
55
        return $this->messages;
32✔
56
    }
57

58
    /**
59
     * Whether or not the submission is valid.
60
     */
61
    public function isValid(): bool
62
    {
63
        return $this->messages()->isEmpty();
32✔
64
    }
65

66
    /**
67
     * Returns the cleaned & transformed form submission data.
68
     *
69
     * Any keys submitted for components that are either unknown or
70
     * hidden will be stripped out, similar to how $request->validate()
71
     * will only return fields that have been explicitly given rules.
72
     */
73
    public function values(): array
74
    {
75
        return $this->values->all();
32✔
76
    }
77

78
    /**
79
     * Returns the list of File objects in a validated request.
80
     */
81
    public function allFiles(): array
82
    {
83
        $list = [];
2✔
84
        foreach ($this->values as $component) {
2✔
85
            if (is_array($component)) { //files always present as multivalued
2✔
86
                foreach ($component as $subComponent) {
2✔
87
                    if (is_array($subComponent) && array_key_exists('storage', $subComponent)) {
2✔
88
                        //get storage driver and check if file exists
89
                        $storageDriver = resolve(resolve(FileComponentRegistry::class)->get($subComponent['storage']));
2✔
90
                        if ($storageDriver->findObject($subComponent['key'])) {
2✔
91
                            $list[] = $subComponent;
2✔
92
                        }
93
                    }
94
                }
95
            }
96
        }
97

98
        return $list;
2✔
99
    }
100

101
    /**
102
     * @param ComponentInterface[] $components
103
     */
104
    private function populateComponentTreeWithData(array $components, array $data): void
105
    {
106
        foreach ($components as $component) {
32✔
107
            if (Arr::has($data, $component->key())) {
32✔
108
                $component->setSubmissionValue(Arr::get($data, $component->key()));
32✔
109
            }
110

111
            $this->populateComponentTreeWithData($component->components(), $data);
32✔
112
        }
113
    }
114

115
    /**
116
     * @param ComponentInterface[] $components
117
     */
118
    private function processComponentTreeCalculations(array $components): void
119
    {
120
        $values = $this->valuesWhileProcessingForm();
32✔
121

122
        foreach ($components as $component) {
32✔
123
            if ($component->isCalculated()) {
32✔
124
                $calculation = $component->calculation();
4✔
125

126
                $component->setSubmissionValue($calculation($values));
4✔
127
            }
128

129
            $this->processComponentTreeCalculations($component->components());
32✔
130
        }
131
    }
132

133
    /**
134
     * @param ComponentInterface[] $components
135
     */
136
    private function processComponentTreeConditionals(array $components, bool $shouldForget): void
137
    {
138
        $values = $this->valuesWhileProcessingForm();
32✔
139

140
        foreach ($components as $component) {
32✔
141
            // Once we're in forget mode, forget all the children recursively.
142
            if ($shouldForget) {
32✔
143
                $this->forgetComponentKeys[] = $component->key();
2✔
144
                $this->processComponentTreeConditionals($component->components(), shouldForget: true);
2✔
145

146
                continue;
2✔
147
            }
148

149
            if ($component->hasConditional()) {
32✔
150
                $condition = $component->conditional();
12✔
151

152
                if ($condition) {
12✔
153
                    if (! $condition($values)) {
12✔
154
                        $this->forgetComponentKeys[] = $component->key();
10✔
155
                        $this->processComponentTreeConditionals($component->components(), shouldForget: true);
10✔
156

157
                        continue;
10✔
158
                    }
159
                }
160
            }
161

162
            $this->processComponentTreeConditionals($component->components(), shouldForget: false);
32✔
163
        }
164
    }
165

166
    /**
167
     * @param ComponentInterface[] $components
168
     */
169
    private function flatValidatableComponents(array $components): array
170
    {
171
        return collect($this->flattenComponents($components))
34✔
172
            ->reject(fn (ComponentInterface $c) => in_array($c->key(), $this->forgetComponentKeys))
34✔
173
            ->filter(fn (ComponentInterface $c) => $c->canValidate())
34✔
174
            ->all();
34✔
175
    }
176

177
    /**
178
     * Generates the values from the components' submissionValue.
179
     *
180
     * This can be run multiple times during a recursive function so the freshest values are available at each step.
181
     */
182
    private function valuesWhileProcessingForm(): array
183
    {
184
        return collect($this->flatComponents)
32✔
185
            ->mapWithKeys(fn (ComponentInterface $c, string $key) => [$key => $c->submissionValue()])
32✔
186
            ->all();
32✔
187
    }
188

189
    /**
190
     * Return a list of components that should be validated.
191
     *
192
     * Calculations will be run on the unvalidated data before processing
193
     * the conditionals (just as they would be in the UI), since those
194
     * values may be needed for the conditional logic.
195
     *
196
     * Conditional logic that excludes fields will be evaluated here
197
     * and may remove components from consideration.
198
     *
199
     * @return ComponentInterface[]
200
     */
201
    protected function processComponentTree(array $components, array $values): array
202
    {
203
        // Populate the components with their data so we can evaluate conditionals
204
        $data = collect($values)->only($components->keys());
×
205
    }
206

207
    /**
208
     * @internal
209
     */
210
    public function getMessageBag()
211
    {
212
        return $this->messages();
2✔
213
    }
214

215
    /**
216
     * @internal
217
     */
218
    public function validate()
219
    {
220
        throw new \Exception('Not implemented');
×
221
    }
222

223
    /**
224
     * @internal
225
     */
226
    public function validated()
227
    {
228
        return $this->values();
2✔
229
    }
230

231
    /**
232
     * @internal
233
     */
234
    public function fails()
235
    {
236
        return ! $this->isValid();
2✔
237
    }
238

239
    /**
240
     * @internal
241
     */
242
    public function failed()
243
    {
244
        return $this->messages->keys();
2✔
245
    }
246

247
    /**
248
     * @internal
249
     */
250
    public function sometimes($attribute, $rules, callable $callback)
251
    {
252
        throw new \Exception('Not implemented');
×
253
    }
254

255
    /**
256
     * @internal
257
     */
258
    public function after($callback)
259
    {
260
        throw new \Exception('Not implemented');
×
261
    }
262

263
    /**
264
     * @internal
265
     */
266
    public function errors()
267
    {
268
        return $this->messages();
2✔
269
    }
270

271
    /**
272
     * Gets the Laravel translator class.
273
     *
274
     * DO NOT CALL THIS! This exists to address a bug present in some later versions of Laravel 9.x and all versions
275
     * of 10.x. The method exists for compatibility with the impacted versions of Laravel.
276
     *
277
     * @see https://github.com/laravel/framework/pull/46378
278
     * @internal
279
     */
280
    public function getTranslator(): Translator
281
    {
282
        return resolve(Translator::class);
×
283
    }
284
}
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