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

NIT-Administrative-Systems / dynamic-forms / 13506631453

24 Feb 2025 07:54PM UTC coverage: 94.075% (+0.07%) from 94.003%
13506631453

push

github

web-flow
Laravel 12 (#480)

1302 of 1384 relevant lines covered (94.08%)

209.68 hits per line

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

88.1
/src/Components/BaseComponent.php
1
<?php
2

3
namespace Northwestern\SysDev\DynamicForms\Components;
4

5
use Illuminate\Contracts\Support\MessageBag;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\MessageBag as MessageBagImpl;
8
use Illuminate\Validation\Factory;
9
use Northwestern\SysDev\DynamicForms\Calculation\CalculationInterface;
10
use Northwestern\SysDev\DynamicForms\Calculation\JSONCalculation;
11
use Northwestern\SysDev\DynamicForms\Conditional\ConditionalInterface;
12
use Northwestern\SysDev\DynamicForms\Conditional\JSONConditional;
13
use Northwestern\SysDev\DynamicForms\Conditional\SimpleConditional;
14
use Northwestern\SysDev\DynamicForms\Errors\CalculationNotImplemented;
15
use Northwestern\SysDev\DynamicForms\Errors\ConditionalNotImplemented;
16
use Northwestern\SysDev\DynamicForms\Errors\InvalidDefinitionError;
17
use Northwestern\SysDev\DynamicForms\Errors\ValidationNotImplementedError;
18
use Northwestern\SysDev\DynamicForms\Validation\JSONValidation;
19
use Northwestern\SysDev\DynamicForms\Validation\ValidationInterface;
20

21
/**
22
 * Implements common functionality for all components.
23
 */
24
abstract class BaseComponent implements ComponentInterface
25
{
26
    protected mixed $submissionValue = null;
27

28
    public static function type(): string
29
    {
30
        return static::TYPE;
111✔
31
    }
32

33
    public function __construct(
34
        protected string $key,
35
        protected ?string $label,
36
        protected ?string $errorLabel,
37
        protected array $components,
38
        protected array $validations,
39
        protected bool $hasMultipleValues,
40
        protected ?array $conditional,
41
        protected ?string $customConditional,
42
        protected string $case,
43
        protected null|array|string $calculateValue,
44
        protected mixed $defaultValue,
45
        protected array $additional,
46
    ) {
47
        //
48
    }
1,464✔
49

50
    public function canValidate(): bool
51
    {
52
        return true;
1,032✔
53
    }
54

55
    public function key(): string
56
    {
57
        return $this->key;
1,068✔
58
    }
59

60
    public function label(): ?string
61
    {
62
        return $this->label;
1,050✔
63
    }
64

65
    public function errorLabel(): ?string
66
    {
67
        return $this->errorLabel;
1,056✔
68
    }
69

70
    public function components(): array
71
    {
72
        return $this->components;
147✔
73
    }
74

75
    public function hasMultipleValues(): bool
76
    {
77
        return $this->hasMultipleValues;
1,209✔
78
    }
79

80
    /**
81
     * Informs the validation code if this should be treated as a multi-value field.
82
     *
83
     * There are cases where components always store data as multiple values, even in single-
84
     * value mode. The validator needs to handle that correctly, but we do not want to inadvertently
85
     * perform a transformation that makes the data incompatible with viewing/editing it again.
86
     */
87
    protected function hasMultipleValuesForValidation(): bool
88
    {
89
        return $this->hasMultipleValues();
918✔
90
    }
91

92
    public function hasConditional(): bool
93
    {
94
        return $this->conditional || $this->customConditional;
135✔
95
    }
96

97
    public function conditional(): ?ConditionalInterface
98
    {
99
        if (! $this->hasConditional()) {
99✔
100
            return null;
81✔
101
        }
102

103
        if ($this->customConditional) {
18✔
104
            throw new ConditionalNotImplemented($this->key(), ConditionalNotImplemented::CUSTOM_JS);
×
105
        }
106

107
        if (Arr::get($this->conditional, 'json')) {
18✔
108
            return new JSONConditional(Arr::get($this->conditional, 'json'));
×
109
        }
110

111
        if (Arr::exists($this->conditional, 'show') && Arr::exists($this->conditional, 'when') && Arr::exists($this->conditional, 'eq')) {
18✔
112
            return new SimpleConditional(
18✔
113
                Arr::get($this->conditional, 'show'),
18✔
114
                Arr::get($this->conditional, 'when'),
18✔
115
                Arr::get($this->conditional, 'eq'),
18✔
116
            );
18✔
117
        }
118
        // otherwise ignore the condition
119
        return null;
3✔
120
    }
121

122
    public function isCalculated(): bool
123
    {
124
        return $this->calculateValue !== null;
135✔
125
    }
126

127
    public function calculation(): ?CalculationInterface
128
    {
129
        if (! $this->calculateValue) {
87✔
130
            return null;
81✔
131
        }
132

133
        if (is_string($this->calculateValue)) {
6✔
134
            throw new CalculationNotImplemented($this->key(), CalculationNotImplemented::CUSTOM_JS);
×
135
        }
136

137
        return new JSONCalculation($this->calculateValue);
6✔
138
    }
139

140
    public function submissionValue(): mixed
141
    {
142
        $cleaner = function (mixed $value) {
822✔
143
            foreach ($this->transformations() as $transform) {
777✔
144
                $value = $transform($value);
63✔
145
            }
146

147
            return $value;
777✔
148
        };
822✔
149

150
        return $this->hasMultipleValues()
822✔
151
            ? collect($this->submissionValue)->map($cleaner)->all()
345✔
152
            : $cleaner($this->submissionValue);
822✔
153
    }
154

155
    public function setSubmissionValue(mixed $value): void
156
    {
157
        $this->submissionValue = $value;
1,485✔
158
    }
159

160
    public function validate(): MessageBag
161
    {
162
        $fieldLabel = $this->errorLabel() ?? $this->label() ?? $this->key();
975✔
163

164
        $validator = app()->make('validator');
975✔
165
        $bag = new MessageBagImpl;
975✔
166

167
        if (! $this->canValidate()) {
975✔
168
            return $bag;
×
169
        }
170

171
        // Some components made up of sub-components can have a null submission value
172
        if ($this->hasMultipleValuesForValidation()) {
975✔
173
            foreach ($this->submissionValue() as $index => $submissionValue) {
519✔
174
                $bag = $this->mergeErrorBags($bag, $this->processValidations(
438✔
175
                    $this->key(),
438✔
176
                    $this->errorLabel() ?? sprintf('%s (%s)', $fieldLabel, $index + 1),
438✔
177
                    $submissionValue,
438✔
178
                    $validator
438✔
179
                ));
438✔
180
            }
181

182
            return $bag->merge($this->postProcessValidationsForMultiple($this->key()));
519✔
183
        }
184

185
        return $this->mergeErrorBags(
459✔
186
            new MessageBagImpl,
459✔
187
            $this->processValidations($this->key(), $fieldLabel, $this->submissionValue(), $validator)
459✔
188
        );
459✔
189
    }
190

191
    /**
192
     * Handles merging validation error MessageBags together, accounting for custom error messages.
193
     *
194
     * A custom error message overwrites all other error messages, so any number of errors in the
195
     * $mergeFrom bag will be consolidated down into one error using the custom message.
196
     *
197
     * If no custom message is set for the component, this just merges the two bags together without
198
     * any other modification.
199
     */
200
    protected function mergeErrorBags(MessageBag $mergeInto, MessageBag $mergeFrom): MessageBag
201
    {
202
        if ($this->validation('customMessage') && $mergeFrom->isNotEmpty()) {
894✔
203
            $mergeFrom = new MessageBagImpl([
6✔
204
                Arr::first($mergeFrom->keys()) => $this->validation('customMessage'),
6✔
205
            ]);
6✔
206
        }
207

208
        return $mergeInto->merge($mergeFrom);
894✔
209
    }
210

211
    public function transformations(): array
212
    {
213
        $transformations = [];
666✔
214

215
        if ($this->case === CaseEnum::UPPER) {
666✔
216
            $transformations['case'] = fn ($value) => is_string($value) ? strtoupper($value) : $value;
33✔
217
        } elseif ($this->case === CaseEnum::LOWER) {
633✔
218
            $transformations['case'] = fn ($value) => is_string($value) ? strtolower($value) : $value;
30✔
219
        }
220

221
        return $transformations;
666✔
222
    }
223

224
    public function defaultValue(): mixed
225
    {
226
        return $this->defaultValue;
81✔
227
    }
228

229
    /**
230
     * Populates the error bag with validation failures.
231
     *
232
     * If you have a layout component (canValidate() returns false),
233
     * this does not need to be implemented -- the validate() method
234
     * is smart enough not to call this.
235
     *
236
     * When ::hasMultipleValues() is true, the validate() method will
237
     * call this once for each submitted value. The ::TODO() method is
238
     * called afterwards to do any special processing on the values in
239
     * aggregate; logic like that does *not* belong in this method.
240
     *
241
     * @param string $fieldKey Field key, taking into account custom labels
242
     * @param mixed $submissionValue One single value
243
     * @param Factory $validator Illuminate validation factory, usage of which is optional (but common!)
244
     * @return MessageBag
245
     */
246
    protected function processValidations(string $fieldKey, string $fieldLabel, mixed $submissionValue, Factory $validator): MessageBag
247
    {
248
        throw new ValidationNotImplementedError($this->type());
×
249
    }
250

251
    /**
252
     * When in multiple values mode, performs additional validations on the submissionValues.
253
     *
254
     * This will ensure required multiple-value fields have at least one value. Extend this method
255
     * if more intricate logic is needed.
256
     *
257
     * @param string $fieldKey Field key, taking into account custom labels
258
     * @return MessageBag
259
     */
260
    protected function postProcessValidationsForMultiple(string $fieldKey): MessageBag
261
    {
262
        if (! $this->hasMultipleValuesForValidation()) {
519✔
263
            throw new InvalidDefinitionError(
×
264
                sprintf('%s called but component is multiple => false; cannot process', __METHOD__),
×
265
                $this->key()
×
266
            );
×
267
        }
268

269
        $bag = new MessageBagImpl;
519✔
270
        if ($this->validation('required') && count($this->submissionValue ?? []) == 0) {
519✔
271
            $bag->add($fieldKey, __('validation.required', ['attribute' => $fieldKey]));
6✔
272
        }
273

274
        return $bag;
519✔
275
    }
276

277
    public function validation(string $name): mixed
278
    {
279
        return Arr::get($this->validations, $name);
1,056✔
280
    }
281

282
    public function validations(): array
283
    {
284
        return $this->validations;
81✔
285
    }
286

287
    public function advancedValidations(): ?ValidationInterface
288
    {
289
        if ($this->validation('json')) {
135✔
290
            return new JSONValidation($this->validation('json'));
×
291
        }
292

293
        return null;
135✔
294
    }
295

296
    public function additional(string $key): mixed
297
    {
298
        return Arr::get($this->additional, $key);
84✔
299
    }
300
}
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

© 2025 Coveralls, Inc