AbstractForm   C
last analyzed

Complexity

Total Complexity 57

Size/Duplication

Total Lines 443
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 57
eloc 144
c 0
b 0
f 0
dl 0
loc 443
rs 5.04

50 Methods

Rating   Name   Duplication   Size   Complexity  
A policies() 0 4 1
A getHandle() 0 3 1
A fieldKeyAsName() 0 3 1
A redirectTo() 0 3 1
A processInitiationContext() 0 3 1
A formErrors() 0 17 3
A failedValidationCacheKey() 0 3 1
A build() 0 16 1
A getExtension() 0 3 1
A simpleCache() 0 3 1
A formMethod() 0 3 1
A actionFormCheck() 0 6 1
A processes() 0 5 1
A process() 0 11 1
A validate() 0 5 1
A fieldKeysAsNames() 0 3 1
A redirectDefault() 0 3 2
A formAction() 0 3 1
A cacheKey() 0 3 1
A transientFieldCacheKey() 0 3 1
A getCachedViolations() 0 10 1
A refererFormCheck() 0 3 1
A processor() 0 6 1
A initiationContexts() 0 6 1
A buildInitiationContext() 0 3 1
A fieldControllers() 0 19 2
A resetStateProps() 0 3 1
A formChecks() 0 3 1
A dataManagers() 0 9 2
A action() 0 3 1
A redirect() 0 5 1
A getAction() 0 3 1
A formFields() 0 25 2
A csrfPrinter() 0 3 1
A token() 0 3 1
A formatting() 0 3 1
A checks() 0 6 1
A __construct() 0 4 1
A transientFieldCacheCleaner() 0 6 1
A fieldNameAsKey() 0 5 1
A mappedResults() 0 9 2
A csrfAuthenticator() 0 3 1
A postProcess() 0 3 1
A validation() 0 3 1
A constructInitiationContext() 0 3 1
A csrfTokenFormCheck() 0 3 1
A transientDataManager() 0 5 1
A fieldNamesAsKeys() 0 3 1
A failedValidationCache() 0 6 1
A shield() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like AbstractForm often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractForm, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Leonidas\Framework\Site\Form;
4
5
use Leonidas\Contracts\Auth\CsrfFieldPrinterInterface;
6
use Leonidas\Contracts\Auth\CsrfManagerInterface;
7
use Leonidas\Contracts\Extension\WpExtensionInterface;
8
use Leonidas\Contracts\Http\Form\FormInterface;
9
use Leonidas\Framework\Abstracts\AccessesSimpleCacheTrait;
10
use Leonidas\Framework\Abstracts\FluentlySetsPropertiesTrait;
11
use Leonidas\Framework\Abstracts\TranslatesTextTrait;
12
use Leonidas\Framework\Abstracts\UtilizesExtensionTrait;
13
use Leonidas\Library\Core\Auth\CsrfFieldPrinter;
14
use Leonidas\Library\Core\Auth\Nonce;
15
use Leonidas\Library\Core\Http\Policy\CsrfCheck;
16
use Psr\Http\Message\ServerRequestInterface;
17
use Psr\SimpleCache\CacheInterface;
18
use WebTheory\HttpPolicy\ServerRequestPolicyInterface;
19
use WebTheory\Saveyour\Auth\FormShield;
20
use WebTheory\Saveyour\Contracts\Auth\FormShieldInterface;
21
use WebTheory\Saveyour\Contracts\Controller\FormFieldControllerInterface;
22
use WebTheory\Saveyour\Contracts\Controller\FormSubmissionManagerInterface;
23
use WebTheory\Saveyour\Contracts\Data\FieldDataManagerInterface;
24
use WebTheory\Saveyour\Contracts\Field\FormFieldInterface;
25
use WebTheory\Saveyour\Contracts\Formatting\DataFormatterInterface;
26
use WebTheory\Saveyour\Contracts\Processor\FormDataProcessorInterface;
27
use WebTheory\Saveyour\Contracts\Report\ProcessedFormReportInterface;
28
use WebTheory\Saveyour\Contracts\Report\ValidationReportInterface;
29
use WebTheory\Saveyour\Contracts\Validation\ValidatorInterface;
30
use WebTheory\Saveyour\Controller\Builder\FormFieldControllerBuilder;
31
use WebTheory\Saveyour\Controller\FormSubmissionManager;
32
use WebTheory\Saveyour\Data\SimpleCacheDataManager;
33
use WebTheory\Saveyour\Field\Type\Hidden;
34
use WebTheory\Saveyour\Processor\FailedValidationSimpleCache;
35
use WebTheory\Saveyour\Processor\SimpleCacheSweeper;
36
37
abstract class AbstractForm implements FormInterface
38
{
39
    use AccessesSimpleCacheTrait;
40
    use UtilizesExtensionTrait;
41
    use FluentlySetsPropertiesTrait;
42
    use TranslatesTextTrait;
43
44
    protected string $handle;
45
46
    protected string $action;
47
48
    /**
49
     * Fields being handled for the current request
50
     *
51
     * @var array<string>
52
     */
53
    protected array $processing;
54
55
    protected WpExtensionInterface $extension;
56
57
    public function __construct(WpExtensionInterface $extension)
58
    {
59
        $this->extension = $extension;
60
        $this->init('construct');
61
    }
62
63
    public function getHandle(): string
64
    {
65
        return $this->handle;
66
    }
67
68
    public function getAction(): string
69
    {
70
        return $this->action;
71
    }
72
73
    public function build(ServerRequestInterface $request): array
74
    {
75
        $this->init('build');
76
        $this->processing = $this->fields($request);
77
78
        $data = [
79
            'method' => $this->formMethod(),
80
            'action' => $this->formAction($request),
81
            'checks' => $this->formChecks($request),
82
            'fields' => $this->formFields($request),
83
            'errors' => $this->formErrors($request),
84
        ];
85
86
        $this->resetStateProps();
87
88
        return $data;
89
    }
90
91
    public function process(ServerRequestInterface $request): void
92
    {
93
        $this->init('process');
94
        $this->processing = $this->fields($request);
95
96
        $processed = $this->postProcess(
97
            $this->processor($request)->process($request),
98
            $request
99
        );
100
101
        $this->redirect($this->redirectTo($request, $processed));
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->redirectTo($request, $processed) targeting Leonidas\Framework\Site\...tractForm::redirectTo() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
102
    }
103
104
    public function validate(ServerRequestInterface $request, string $field, $value): ValidationReportInterface
105
    {
106
        $validators = $this->validation($request);
107
108
        return $validators[$field]->inspect($value);
109
    }
110
111
    protected function getExtension(): WpExtensionInterface
112
    {
113
        return $this->extension;
114
    }
115
116
    protected function processor(ServerRequestInterface $request): FormSubmissionManagerInterface
117
    {
118
        return new FormSubmissionManager(
119
            $this->fieldControllers($request),
120
            $this->processes($request),
121
            $this->shield($request),
122
        );
123
    }
124
125
    /**
126
     * @return array<FormFieldControllerInterface>
127
     */
128
    protected function fieldControllers(ServerRequestInterface $request): array
129
    {
130
        $data = $this->dataManagers($request);
131
        $validation = $this->validation($request);
132
        $formatting = $this->formatting($request);
133
134
        $controllers = [];
135
136
        foreach ($this->processing as $field) {
137
            $name = $this->fieldKeyAsName($field);
138
139
            $controllers[] = FormFieldControllerBuilder::for($name)
140
                ->dataManager($data[$field] ?? null)
141
                ->formatter($formatting[$field] ?? null)
142
                ->validator($validation[$field] ?? null)
143
                ->get();
144
        }
145
146
        return $controllers;
147
    }
148
149
    /**
150
     * @return array<string|array|FormFieldInterface>
151
     */
152
    protected function formFields(ServerRequestInterface $request): array
153
    {
154
        $attributes = $this->attributes($request);
155
        $data = $this->dataManagers($request);
156
        $formatting = $this->formatting($request);
157
158
        $controllers = [];
159
160
        foreach ($this->processing as $field) {
161
            $name = $this->fieldKeyAsName($field);
162
163
            $controller = FormFieldControllerBuilder::for($name)
164
                ->dataManager($data[$field] ?? null)
165
                ->formatter($formatting[$field] ?? null)
166
                ->get();
167
168
            $definition = $attributes[$field] ?? [];
169
170
            $definition['name'] = $name;
171
            $definition['value'] = $controller->getPresetValue($request);
172
173
            $controllers[$field] = $definition;
174
        }
175
176
        return $controllers;
177
    }
178
179
    /**
180
     * @return array<string,array<int,string>>
181
     */
182
    protected function formErrors(ServerRequestInterface $request): array
183
    {
184
        $errors = [];
185
        $messages = $this->errorMessages($request);
186
        $violations = $this->getCachedViolations($request);
187
188
        foreach ($violations as $field => $violations) {
189
            $field = $this->fieldNameAsKey($field);
190
            $errors[$field] = [];
191
192
            foreach ($violations as $violation) {
193
                $message = $messages[$field][$violation];
194
                $errors[$field][$violation] = $this->translate($message);
195
            }
196
        }
197
198
        return $errors;
199
    }
200
201
    protected function getCachedViolations(ServerRequestInterface $request): array
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

201
    protected function getCachedViolations(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
202
    {
203
        $cache = $this->simpleCache();
204
        $key = $this->failedValidationCacheKey();
205
206
        $violations = $cache->get($key, []);
207
208
        $cache->delete($key);
209
210
        return $violations;
211
    }
212
213
    protected function fieldKeyAsName(string $key): string
214
    {
215
        return $this->prefix(str_replace('_', '-', $key), '-');
216
    }
217
218
    protected function fieldKeysAsNames(array $keys): array
219
    {
220
        return array_map([$this, 'fieldKeyAsName'], $keys);
221
    }
222
223
    protected function fieldNameAsKey(string $name): string
224
    {
225
        $stripped = substr($name, strlen($this->extension->getPrefix()) + 1);
226
227
        return str_replace('-', '_', $stripped);
228
    }
229
230
    protected function fieldNamesAsKeys(array $names): array
231
    {
232
        return array_map([$this, 'fieldNameAsKey'], $names);
233
    }
234
235
    protected function mappedResults(array $results): array
236
    {
237
        $mapped = [];
238
239
        foreach ($results as $name => $value) {
240
            $mapped[$this->fieldNameAsKey($name)] = $value;
241
        }
242
243
        return $mapped;
244
    }
245
246
    protected function formMethod(): string
247
    {
248
        return 'post';
249
    }
250
251
    protected function formAction(ServerRequestInterface $request): string
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

251
    protected function formAction(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
252
    {
253
        return esc_url(admin_url('admin-post.php'));
0 ignored issues
show
Bug introduced by
The function admin_url was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

253
        return esc_url(/** @scrutinizer ignore-call */ admin_url('admin-post.php'));
Loading history...
254
    }
255
256
    protected function formChecks(ServerRequestInterface $request): string
257
    {
258
        return implode("\n", $this->checks($request));
259
    }
260
261
    /**
262
     * @return array<string>
263
     */
264
    protected function checks(ServerRequestInterface $request): array
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

264
    protected function checks(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
265
    {
266
        return [
267
            $this->actionFormCheck(),
268
            $this->refererFormCheck(),
269
            $this->csrfTokenFormCheck(),
270
        ];
271
    }
272
273
    protected function actionFormCheck(): string
274
    {
275
        return (new Hidden())
276
            ->setName('action')
277
            ->setValue($this->action)
278
            ->toHtml();
279
    }
280
281
    protected function refererFormCheck(): string
282
    {
283
        return wp_referer_field(false);
0 ignored issues
show
Bug introduced by
The function wp_referer_field was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

283
        return /** @scrutinizer ignore-call */ wp_referer_field(false);
Loading history...
284
    }
285
286
    protected function csrfTokenFormCheck(): string
287
    {
288
        return $this->csrfPrinter()->print($this->token());
289
    }
290
291
    protected function shield(ServerRequestInterface $request): FormShieldInterface
292
    {
293
        return new FormShield($this->policies($request));
294
    }
295
296
    /**
297
     * @return array<string,ServerRequestPolicyInterface>
298
     */
299
    protected function policies(ServerRequestInterface $request): array
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

299
    protected function policies(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
300
    {
301
        return [
302
            'csrf' => $this->csrfAuthenticator(),
303
        ];
304
    }
305
306
    protected function csrfAuthenticator(): ServerRequestPolicyInterface
307
    {
308
        return new CsrfCheck($this->token());
309
    }
310
311
    protected function token(): CsrfManagerInterface
312
    {
313
        return new Nonce($action = $this->getAction(), $action, Nonce::EXP_12);
314
    }
315
316
    /**
317
     * @return array<FormDataProcessorInterface>
318
     */
319
    protected function processes(ServerRequestInterface $request): array
320
    {
321
        return [
322
            $this->failedValidationCache(),
323
            $this->transientFieldCacheCleaner($request),
324
        ];
325
    }
326
327
    protected function failedValidationCache(): FormDataProcessorInterface
328
    {
329
        return new FailedValidationSimpleCache(
330
            'cached_violations',
331
            $this->simpleCache(),
332
            $this->failedValidationCacheKey()
333
        );
334
    }
335
336
    protected function transientFieldCacheCleaner(ServerRequestInterface $request): FormDataProcessorInterface
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

336
    protected function transientFieldCacheCleaner(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): FormDataProcessorInterface

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
337
    {
338
        return new SimpleCacheSweeper(
339
            'transient_fields',
340
            $this->simpleCache(),
341
            array_map([$this, 'transientFieldCacheKey'], $this->processing)
342
        );
343
    }
344
345
    /**
346
     * @return array<string,null|FieldDataManagerInterface>
347
     */
348
    protected function dataManagers(ServerRequestInterface $request): array
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

348
    protected function dataManagers(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
349
    {
350
        $managers = [];
351
352
        foreach ($this->processing as $key) {
353
            $managers[$key] = $this->transientDataManager($key);
354
        }
355
356
        return $managers;
357
    }
358
359
    protected function transientDataManager(string $key): FieldDataManagerInterface
360
    {
361
        return new SimpleCacheDataManager(
362
            $this->getSimpleCache(),
363
            $this->transientFieldCacheKey($key)
364
        );
365
    }
366
367
    protected function redirect(?string $location = null): void
368
    {
369
        wp_safe_redirect($location ?? $this->redirectDefault());
0 ignored issues
show
Bug introduced by
The function wp_safe_redirect was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

369
        /** @scrutinizer ignore-call */ 
370
        wp_safe_redirect($location ?? $this->redirectDefault());
Loading history...
370
371
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
372
    }
373
374
    protected function redirectDefault(): string
375
    {
376
        return wp_get_referer() ?: home_url();
0 ignored issues
show
Bug introduced by
The function home_url was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

376
        return wp_get_referer() ?: /** @scrutinizer ignore-call */ home_url();
Loading history...
Bug introduced by
The function wp_get_referer was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

376
        return /** @scrutinizer ignore-call */ wp_get_referer() ?: home_url();
Loading history...
377
    }
378
379
    protected function simpleCache(): CacheInterface
380
    {
381
        return $this->getService('transients_channel');
382
    }
383
384
    protected function csrfPrinter(): CsrfFieldPrinterInterface
385
    {
386
        return new CsrfFieldPrinter();
387
    }
388
389
    protected function redirectTo(ServerRequestInterface $request, ProcessedFormReportInterface $report): ?string
0 ignored issues
show
Unused Code introduced by
The parameter $report is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

389
    protected function redirectTo(ServerRequestInterface $request, /** @scrutinizer ignore-unused */ ProcessedFormReportInterface $report): ?string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

389
    protected function redirectTo(/** @scrutinizer ignore-unused */ ServerRequestInterface $request, ProcessedFormReportInterface $report): ?string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
390
    {
391
        return null;
392
    }
393
394
    protected function postProcess(ProcessedFormReportInterface $report, ServerRequestInterface $request): ProcessedFormReportInterface
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

394
    protected function postProcess(ProcessedFormReportInterface $report, /** @scrutinizer ignore-unused */ ServerRequestInterface $request): ProcessedFormReportInterface

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
395
    {
396
        return $report;
397
    }
398
399
    protected function resetStateProps(): void
400
    {
401
        unset($this->processing);
402
    }
403
404
    protected function initiationContexts(): array
405
    {
406
        return [
407
            'construct' => $this->constructInitiationContext(),
408
            'build' => $this->buildInitiationContext(),
409
            'process' => $this->processInitiationContext(),
410
        ];
411
    }
412
413
    protected function cacheKey(string $sub): string
414
    {
415
        return "form:{$this->handle}:{$sub}";
416
    }
417
418
    protected function failedValidationCacheKey(): string
419
    {
420
        return $this->cacheKey('errors');
421
    }
422
423
    protected function transientFieldCacheKey(string $key): string
424
    {
425
        return $this->cacheKey("submitted.{$key}");
426
    }
427
428
    protected function constructInitiationContext(): array
429
    {
430
        return ['handle', 'action'];
431
    }
432
433
    protected function buildInitiationContext(): array
434
    {
435
        return ['simpleCache', 'localizer'];
436
    }
437
438
    protected function processInitiationContext(): array
439
    {
440
        return ['simpleCache'];
441
    }
442
443
    /**
444
     * @return array<string,null|ValidatorInterface>
445
     */
446
    protected function validation(ServerRequestInterface $request): array
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

446
    protected function validation(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
447
    {
448
        return [];
449
    }
450
451
    /**
452
     * @return array<string,null|DataFormatterInterface>
453
     */
454
    protected function formatting(ServerRequestInterface $request): array
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

454
    protected function formatting(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
455
    {
456
        return [];
457
    }
458
459
    protected function action(): string
460
    {
461
        return $this->prefix($this->getHandle(), '_');
462
    }
463
464
    abstract protected function handle(): string;
465
466
    /**
467
     * @return array<string>
468
     */
469
    abstract protected function fields(ServerRequestInterface $request): array;
470
471
    /**
472
     * @return array<string,array<string,mixed>|FormFieldInterface>
473
     */
474
    abstract protected function attributes(ServerRequestInterface $request): array;
475
476
    /**
477
     * @return array<string,array<string,string>>
478
     */
479
    abstract protected function errorMessages(ServerRequestInterface $request): array;
480
}
481