DaikonRequestValidator   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 159
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 79
dl 0
loc 159
ccs 0
cts 81
cp 0
rs 10
c 1
b 0
f 0
wmc 28

9 Methods

Rating   Name   Duplication   Size   Complexity  
A error() 0 3 1
A getValidationReport() 0 3 1
A notice() 0 3 1
A resolveValidator() 0 9 1
A critical() 0 3 1
F __invoke() 0 99 20
A register() 0 5 1
A silent() 0 3 1
A __construct() 0 4 1
1
<?php declare(strict_types=1);
2
3
namespace Daikon\Boot\Validator;
4
5
use Daikon\Boot\Middleware\Action\DaikonRequest;
6
use Daikon\Boot\Middleware\ResolvesDependency;
7
use Daikon\Interop\Assertion;
8
use Daikon\Interop\AssertionFailedException;
9
use Daikon\Interop\InvalidArgumentException;
10
use Daikon\Interop\LazyAssertionException;
11
use Daikon\Interop\RuntimeException;
12
use Daikon\Validize\Validation\ValidationIncident;
13
use Daikon\Validize\Validation\ValidationReport;
14
use Daikon\Validize\Validation\ValidatorDefinition;
15
use Daikon\Validize\Validator\ValidatorInterface;
16
use Daikon\Validize\ValueObject\Severity;
17
use DomainException;
18
use Fig\Http\Message\StatusCodeInterface;
19
use Psr\Container\ContainerInterface;
20
21
final class DaikonRequestValidator implements ValidatorInterface, StatusCodeInterface
22
{
23
    use ResolvesDependency;
24
25
    private ContainerInterface $container;
26
27
    private ValidationReport $validationReport;
28
29
    private array $validatorDefinitions = [];
30
31
    public function __construct(ContainerInterface $container)
32
    {
33
        $this->container = $container;
34
        $this->validationReport = new ValidationReport;
35
    }
36
37
    /** @return array */
38
    public function __invoke(ValidatorDefinition $requestValidatorDefinition)
39
    {
40
        $request = $requestValidatorDefinition->getArgument();
41
        Assertion::isInstanceOf($request, DaikonRequest::class);
42
        if (!empty($payload = $request->getPayload([]))) {
43
            throw new RuntimeException('Action payload already exists.');
44
        }
45
46
        $queryParams = [];
47
        parse_str($request->getUri()->getQuery(), $queryParams);
48
        $source = array_merge($queryParams, $request->getParsedBody(), $request->getAttributes());
49
50
        /**
51
         * @var string $implementor
52
         * @var ValidatorDefinition $validatorDefinition
53
         */
54
        foreach ($this->validatorDefinitions as list($implementor, $validatorDefinition)) {
55
            $path = $validatorDefinition->getPath();
56
            $severity = $validatorDefinition->getSeverity();
57
            $settings = $validatorDefinition->getSettings();
58
            try {
59
                // Check dependents are executed
60
                if (array_key_exists('depends', $settings)) {
61
                    foreach ((array)$settings['depends'] as $depends) {
62
                        if (!$this->validationReport->isProvided($depends)) {
63
                            throw new DomainException("Dependent validator '$depends' not provided.");
64
                        }
65
                    }
66
                }
67
                // Check imports set
68
                if (array_key_exists('import', $settings)) {
69
                    foreach ((array)$settings['import'] as $import) {
70
                        Assertion::keyExists($payload, $import, "Missing required import '$import'.");
71
                        $validatorDefinition = $validatorDefinition->withImport($import, $payload[$import]);
72
                    }
73
                }
74
                // Check argument set
75
                if (!array_key_exists($path, $source)) {
76
                    if ($settings['required'] ?? true) {
77
                        throw new InvalidArgumentException('Missing required input.');
78
                    } else {
79
                        $result = $settings['default'] ?? null; // Default value infers success
80
                    }
81
                } else {
82
                    // Run validation
83
                    $validator = $this->resolveValidator($this->container, $implementor, $validatorDefinition);
84
                    $result = $validator($validatorDefinition->withArgument($source[$path]));
85
                }
86
                // Export result
87
                if ($settings['export'] ?? true) {
88
                    $payload = array_merge_recursive($payload, [($settings['export'] ?? $path) => $result]);
89
                }
90
91
                $incident = new ValidationIncident($validatorDefinition, Severity::success());
92
                $this->validationReport = $this->validationReport->push($incident);
93
            } catch (DomainException $error) {
94
                $incident = new ValidationIncident($validatorDefinition, Severity::unprocessed());
95
                $this->validationReport = $this->validationReport->push($incident->addMessage($error->getMessage()));
96
            } catch (AssertionFailedException $error) {
97
                if ($severity->isLessThanOrEqual(Severity::silent())) {
98
                    continue;
99
                }
100
                $incident = new ValidationIncident($validatorDefinition, $severity);
101
                switch (true) {
102
                    case $error instanceof LazyAssertionException:
103
                        /** @var LazyAssertionException $error */
104
                        foreach ($error->getErrorExceptions() as $exception) {
105
                            $incident = $incident->addMessage($exception->getMessage());
106
                        }
107
                        break;
108
                    default:
109
                        $incident = $incident->addMessage($error->getMessage());
110
                }
111
                $this->validationReport = $this->validationReport->push($incident);
112
                if ($severity->isCritical()) {
113
                    break;
114
                }
115
            }
116
        }
117
118
        // Handle request validator reporting
119
        if (!$this->validationReport->getErrors()->isEmpty()) {
120
            $severity = $requestValidatorDefinition->getSeverity();
121
            if ($severity->isGreaterThanOrEqual(Severity::notice())) {
122
                $incident = new ValidationIncident($requestValidatorDefinition, $severity);
123
                $this->validationReport = $this->validationReport->unshift(
124
                    $incident->addMessage('Request validator reports errors.')
125
                );
126
                if ($severity->isCritical()) {
127
                    throw new InvalidArgumentException;
128
                }
129
            }
130
        } else {
131
            $this->validationReport = $this->validationReport->unshift(
132
                new ValidationIncident($requestValidatorDefinition, Severity::success())
133
            );
134
        }
135
136
        return $payload;
137
    }
138
139
    public function getValidationReport(): ValidationReport
140
    {
141
        return $this->validationReport;
142
    }
143
144
    public function critical(string $path, string $validator, array $settings = []): self
145
    {
146
        return $this->register(Severity::critical(), $path, $validator, $settings);
147
    }
148
149
    public function error(string $path, string $validator, array $settings = []): self
150
    {
151
        return $this->register(Severity::error(), $path, $validator, $settings);
152
    }
153
154
    public function notice(string $path, string $validator, array $settings = []): self
155
    {
156
        return $this->register(Severity::notice(), $path, $validator, $settings);
157
    }
158
159
    public function silent(string $path, string $validator, array $settings = []): self
160
    {
161
        return $this->register(Severity::silent(), $path, $validator, $settings);
162
    }
163
164
    private function register(Severity $severity, string $path, string $implementor, array $settings): self
165
    {
166
        $validatorDefinition = new ValidatorDefinition($path, $severity, $settings);
167
        $this->validatorDefinitions[] = [$implementor, $validatorDefinition];
168
        return $this;
169
    }
170
171
    private function resolveValidator(
172
        ContainerInterface $container,
173
        string $implementor,
174
        ValidatorDefinition $validatorDefinition
175
    ): ValidatorInterface {
176
        $dependency = [$implementor, [':validatorDefinition' => $validatorDefinition]];
177
        /** @var ValidatorInterface $validator */
178
        $validator = $this->resolve($container, $dependency, ValidatorInterface::class);
179
        return $validator;
180
    }
181
}
182