Completed
Push — master ( 5927eb...1e13db )
by
unknown
04:04
created

Validator   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 302
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 95.18%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 302
ccs 79
cts 83
cp 0.9518
rs 9.6
c 1
b 0
f 0
wmc 32
lcom 1
cbo 9

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 4
A fails() 0 4 1
A passes() 0 4 1
A errors() 0 6 1
A setMaxDepth() 0 6 1
A registerFormatExtension() 0 4 1
A setDepth() 0 6 1
A getPointer() 0 4 1
A setPointer() 0 6 1
A makeSubSchemaValidator() 0 7 1
A validate() 0 15 3
A checkDepth() 0 6 2
A validateRule() 0 14 3
B invokeConstraint() 0 14 5
A isCustomFormatExtension() 0 4 2
A validateCustomFormat() 0 7 1
A mergeErrors() 0 13 3
1
<?php
2
3
namespace League\JsonGuard;
4
5
use League\JsonGuard\Constraints\Constraint;
6
use League\JsonGuard\Constraints\ContainerInstanceConstraint;
7
use League\JsonGuard\Constraints\ParentSchemaAwareContainerInstanceConstraint;
8
use League\JsonGuard\Constraints\ParentSchemaAwarePropertyConstraint;
9
use League\JsonGuard\Constraints\PropertyConstraint;
10
use League\JsonGuard\Exceptions\MaximumDepthExceededException;
11
use League\JsonGuard\RuleSets\DraftFour;
12
13
class Validator implements SubSchemaValidatorFactory
14
{
15
    /**
16
     * @var array
17
     */
18
    private $errors = [];
19
20
    /**
21
     * @var mixed
22
     */
23
    private $data;
24
25
    /**
26
     * @var object
27
     */
28
    private $schema;
29
30
    /**
31
     * @var string
32
     */
33
    private $pointer = '';
34
35
    /**
36
     * The maximum depth the validator should recurse into $data
37
     * before throwing an exception.
38
     *
39
     * @var int
40
     */
41
    private $maxDepth = 10;
42
43
    /**
44
     * The depth the validator has reached in the data.
45
     *
46
     * @var int
47
     */
48
    private $depth = 0;
49
50
    /**
51
     * @var \League\JsonGuard\FormatExtension[]
52
     */
53
    private $formatExtensions = [];
54
55
    /**
56
     * @var \League\JsonGuard\RuleSet
57
     */
58
    private $ruleSet;
59
60
    /**
61
     * @var bool
62
     */
63
    private $hasValidated;
64
65
    /**
66
     * @param mixed  $data
67
     * @param object $schema
68
     * @param RuleSet|null   $ruleSet
69
     */
70 72
    public function __construct($data, $schema, $ruleSet = null)
71
    {
72 72
        if (!is_object($schema)) {
73
            throw new \InvalidArgumentException(
74
                sprintf('The schema should be an object from a json_decode call, got "%s"', gettype($schema))
75
            );
76
        }
77
78 72
        if ($schema instanceof Reference) {
79 14
            $schema = $schema->resolve();
80 14
        }
81
82 72
        $this->data    = $data;
83 72
        $this->schema  = $schema;
84 72
        $this->ruleSet = $ruleSet ?: new DraftFour();
85 72
    }
86
87
    /**
88
     * @return boolean
89
     */
90 72
    public function fails()
91
    {
92 72
        return !$this->passes();
93 28
    }
94
95
    /**
96
     * @return boolean
97
     */
98 72
    public function passes()
99
    {
100 72
        return empty($this->errors());
101
    }
102
103
    /**
104
     * Get a collection of errors.
105
     *
106
     * @return ValidationError[]
107
     */
108 72
    public function errors()
109
    {
110 72
        $this->validate();
111
112 70
        return $this->errors;
113
    }
114
115
    /**
116
     * Set the maximum allowed depth data will be validated until.
117
     * If the data exceeds the stack depth an exception is thrown.
118
     *
119
     * @param int $maxDepth
120
     * @return $this
121
     */
122 38
    public function setMaxDepth($maxDepth)
123
    {
124 38
        $this->maxDepth = $maxDepth;
125
126 38
        return $this;
127
    }
128
129
    /**
130
     * Register a custom format validation extension.
131
     *
132
     * @param string          $format
133
     * @param FormatExtension $extension
134
     */
135 2
    public function registerFormatExtension($format, FormatExtension $extension)
136
    {
137 2
        $this->formatExtensions[$format] = $extension;
138 2
    }
139
140
    /**
141
     * @internal
142
     * @param int $depth
143
     * @return $this
144
     */
145 38
    public function setDepth($depth)
146
    {
147 38
        $this->depth = $depth;
148
149 38
        return $this;
150
    }
151
152
    /**
153
     * @internal
154
     * @return string
155
     */
156 72
    public function getPointer()
157
    {
158 72
        return $this->pointer;
159
    }
160
161
    /**
162
     * @internal
163
     * @param string $pointer
164
     * @return $this
165
     */
166 38
    public function setPointer($pointer)
167
    {
168 38
        $this->pointer = $pointer;
169
170 38
        return $this;
171
    }
172
173
    /**
174
     * Create a new sub-validator.
175
     *
176
     * @param mixed  $data
177
     * @param object $schema
178
     * @param string $pointer
179
     * @return Validator
180
     */
181 38
    public function makeSubSchemaValidator($data, $schema, $pointer)
182
    {
183 38
        return (new Validator($data, $schema))
184 38
            ->setPointer($pointer)
185 38
            ->setMaxDepth($this->maxDepth)
186 38
            ->setDepth($this->depth + 1);
187
    }
188
189
    /**
190
     * Validate the data and collect the errors.
191
     */
192 72
    private function validate()
193
    {
194 72
        if ($this->hasValidated) {
195 24
            return;
196
        }
197
198 72
        $this->checkDepth();
199
200 72
        foreach ($this->schema as $rule => $parameter) {
201 72
            $errors = $this->validateRule($rule, $parameter);
202 70
            $this->mergeErrors($errors);
203 70
        }
204
205 70
        $this->hasValidated = true;
206 70
    }
207
208
    /**
209
     * Keep track of how many levels deep we have validated.
210
     * This is to prevent a really deeply nested JSON
211
     * structure from causing the validator to continue
212
     * validating for an incredibly long time.
213
     *
214
     * @throws \League\JsonGuard\Exceptions\MaximumDepthExceededException
215
     */
216 72
    private function checkDepth()
217
    {
218 72
        if ($this->depth > $this->maxDepth) {
219 4
            throw new MaximumDepthExceededException();
220
        }
221 72
    }
222
223
    /**
224
     * Validate the data using the given rule and parameter.
225
     *
226
     * @param string $rule
227
     * @param mixed $parameter
228
     * @return null|ValidationError|ValidationError[]
229
     */
230 72
    private function validateRule($rule, $parameter)
231
    {
232 72
        if (!$this->ruleSet->has($rule)) {
233 14
            return null;
234
        }
235
236 72
        if ($this->isCustomFormatExtension($rule, $parameter)) {
237 2
            return $this->validateCustomFormat($parameter);
238
        }
239
240 70
        $constraint = $this->ruleSet->getConstraint($rule);
241
242 70
        return $this->invokeConstraint($constraint, $parameter);
0 ignored issues
show
Bug introduced by
It seems like $constraint defined by $this->ruleSet->getConstraint($rule) on line 240 can be null; however, League\JsonGuard\Validator::invokeConstraint() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
243
    }
244
245
    /**
246
     * Invoke the given constraint and return the validation errors.
247
     *
248
     * @param \League\JsonGuard\Constraints\Constraint $constraint
249
     * @param mixed                                    $parameter
250
     *
251
     * @return \League\JsonGuard\ValidationError|\League\JsonGuard\ValidationError[]|null
252
     */
253 70
    private function invokeConstraint(Constraint $constraint, $parameter)
254
    {
255 70
        if ($constraint instanceof PropertyConstraint) {
256 58
            return $constraint::validate($this->data, $parameter, $this->getPointer());
257 44
        } elseif ($constraint instanceof ParentSchemaAwarePropertyConstraint) {
258 18
            return $constraint::validate($this->data, $this->schema, $parameter, $this->getPointer());
259 38
        } elseif ($constraint instanceof ContainerInstanceConstraint) {
260 38
            return $constraint::validate($this->data, $parameter, $this, $this->getPointer());
261 16
        } elseif ($constraint instanceof ParentSchemaAwareContainerInstanceConstraint) {
262 16
            return $constraint::validate($this->data, $this->schema, $parameter, $this, $this->getPointer());
263
        }
264
265
        throw new \InvalidArgumentException('Invalid constraint.');
266
    }
267
268
    /**
269
     * Determine if a rule has a custom format extension registered.
270
     *
271
     * @param string $rule
272
     * @param mixed $parameter
273
     *
274
     * @return bool
275
     */
276 72
    private function isCustomFormatExtension($rule, $parameter)
277
    {
278 72
        return $rule === 'format' && isset($this->formatExtensions[$parameter]);
279
    }
280
281
    /**
282
     * Call a custom format extension to validate the data.
283
     *
284
     * @param string $format
285
     *
286
     * @return ValidationError|null
287
     */
288 2
    private function validateCustomFormat($format)
289
    {
290
        /** @var FormatExtension $extension */
291 2
        $extension = $this->formatExtensions[$format];
292
293 2
        return $extension->validate($this->data, $this->getPointer());
294
    }
295
296
    /**
297
     * Merge the errors with our error collection.
298
     *
299
     * @param ValidationError[]|ValidationError|null $errors
300
     */
301 70
    private function mergeErrors($errors)
302
    {
303 70
        if (is_null($errors)) {
304 68
            return;
305
        }
306
307 70
        if (is_array($errors)) {
308 32
            $this->errors = array_merge($this->errors, $errors);
309 32
            return;
310
        }
311
312 66
        $this->errors[] = $errors;
313 66
    }
314
}
315