Passed
Push — master ( ff0e1a...d70f62 )
by Mr
02:29
created

ServerRequestValidator::error()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 3
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 2
rs 10
1
<?php declare(strict_types=1);
2
3
namespace Daikon\Boot\Validator;
4
5
use Daikon\Boot\Middleware\ActionHandler;
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;
0 ignored issues
show
Bug introduced by
The type Daikon\Validize\Validation\ValidationReport was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
use Psr\Http\Message\ServerRequestInterface;
21
22
final class ServerRequestValidator implements ValidatorInterface, StatusCodeInterface
23
{
24
    use ResolvesDependency;
25
26
    private ContainerInterface $container;
27
28
    private ValidationReport $validationReport;
29
30
    private array $validatorDefinitions = [];
31
32
    public function __construct(ContainerInterface $container)
33
    {
34
        $this->container = $container;
35
        $this->validationReport = new ValidationReport;
36
    }
37
38
    /** @return array */
39
    public function __invoke(ValidatorDefinition $validatorDefinition)
40
    {
41
        $request = $validatorDefinition->getArgument();
42
        Assertion::isInstanceOf($request, ServerRequestInterface::class);
43
        if (!empty($payload = $request->getAttribute(ActionHandler::ATTR_PAYLOAD, []))) {
44
            throw new RuntimeException('Action payload already exists.');
45
        }
46
47
        $queryParams = [];
48
        parse_str($request->getUri()->getQuery(), $queryParams);
49
        $source = array_merge($queryParams, $request->getParsedBody(), $request->getAttributes());
50
51
        /**
52
         * @var string $implementor
53
         * @var ValidatorDefinition $validatorDefinition
54
         */
55
        foreach ($this->validatorDefinitions as list($implementor, $validatorDefinition)) {
56
            $name = $validatorDefinition->getName();
57
            $severity = $validatorDefinition->getSeverity();
58
            $settings = $validatorDefinition->getSettings();
59
            try {
60
                // Check dependents are executed
61
                if (array_key_exists('depends', $settings)) {
62
                    foreach ((array)$settings['depends'] as $depends) {
63
                        if (!$this->validationReport->isProvided($depends)) {
64
                            throw new DomainException("Dependent validator '$depends' not provided.");
65
                        }
66
                    }
67
                }
68
                // Check imports set
69
                if (array_key_exists('import', $settings)) {
70
                    foreach ((array)$settings['import'] as $import) {
71
                        Assertion::keyExists($payload, $import, "Missing required import '$import'.");
72
                        $validatorDefinition = $validatorDefinition->withImport($import, $payload[$import]);
73
                    }
74
                }
75
                // Check argument set
76
                if (!array_key_exists($name, $source)) {
77
                    if ($settings['required'] ?? true) {
78
                        throw new InvalidArgumentException('Missing required input.');
79
                    } else {
80
                        $result = $settings['default'] ?? null; // Default value infers success
81
                        $incident = new ValidationIncident($validatorDefinition, Severity::success());
82
                    }
83
                } else {
84
                    // Run validation
85
                    $validator = $this->resolveValidator($this->container, $implementor, $validatorDefinition);
86
                    $result = $validator($validatorDefinition->withArgument($source[$name]));
87
                    $incident = new ValidationIncident($validatorDefinition, Severity::success());
88
                }
89
                if ($settings['export'] ?? true) {
90
                    $payload = array_merge_recursive($payload, [($settings['export'] ?? $name) => $result]);
91
                }
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->isGreaterThanOrEqual(Severity::notice())) {
98
                    $incident = new ValidationIncident($validatorDefinition, $severity);
99
                    switch (true) {
100
                        case $error instanceof LazyAssertionException:
101
                            /** @var LazyAssertionException $error */
102
                            foreach ($error->getErrorExceptions() as $exception) {
103
                                $incident = $incident->addMessage($exception->getMessage());
104
                            }
105
                            break;
106
                        default:
107
                            $incident = $incident->addMessage($error->getMessage());
108
                            break;
109
                    }
110
                    $this->validationReport = $this->validationReport->push($incident);
111
                }
112
                if ($severity->equals(Severity::critical())) {
113
                    break;
114
                }
115
            }
116
        }
117
118
        if (!$this->validationReport->getErrors()->isEmpty()) {
119
            throw new InvalidArgumentException('Validation service reports errors.');
120
        }
121
122
        return $payload;
123
    }
124
125
    public function getValidationReport(): ValidationReport
126
    {
127
        return $this->validationReport;
128
    }
129
130
    public function critical(string $name, string $validator, array $settings = []): self
131
    {
132
        return $this->register(Severity::critical(), $name, $validator, $settings);
133
    }
134
135
    public function error(string $name, string $validator, array $settings = []): self
136
    {
137
        return $this->register(Severity::error(), $name, $validator, $settings);
138
    }
139
140
    public function notice(string $name, string $validator, array $settings = []): self
141
    {
142
        return $this->register(Severity::notice(), $name, $validator, $settings);
143
    }
144
145
    public function silent(string $name, string $validator, array $settings = []): self
146
    {
147
        return $this->register(Severity::silent(), $name, $validator, $settings);
148
    }
149
150
    private function register(Severity $severity, string $name, string $implementor, array $settings): self
151
    {
152
        $validatorDefinition = new ValidatorDefinition($name, $severity, $settings);
153
        $this->validatorDefinitions[] = [$implementor, $validatorDefinition];
154
        return $this;
155
    }
156
157
    private function resolveValidator(
158
        ContainerInterface $container,
159
        string $implementor,
160
        ValidatorDefinition $validatorDefinition
161
    ): ValidatorInterface {
162
        $dependency = [$implementor, [':validatorDefinition' => $validatorDefinition]];
163
        /** @var ValidatorInterface $validator */
164
        $validator = $this->resolve($container, $dependency, ValidatorInterface::class);
165
        return $validator;
166
    }
167
}
168