Completed
Push — master ( feafc2...e07893 )
by Matt
12s
created

Validator   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 276
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 97.22%

Importance

Changes 0
Metric Value
dl 0
loc 276
ccs 70
cts 72
cp 0.9722
rs 10
c 0
b 0
f 0
wmc 29
lcom 1
cbo 6

16 Methods

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