Completed
Pull Request — master (#130)
by
unknown
04:56
created

Validator::setIgnoreUnknownConstraints()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace League\JsonGuard;
4
5
use League\JsonGuard\Exception\MaximumDepthExceededException;
6
use League\JsonGuard\RuleSet\DraftFour;
7
use League\JsonReference\Reference;
8
use Psr\Container\ContainerInterface;
9
use \League\JsonGuard\Exception\InvalidSchemaException;
10
11
final class Validator
12
{
13
    /**
14
     * @var array
15
     */
16
    private $errors = [];
17
18
    /**
19
     * @var mixed
20
     */
21
    private $data;
22
23
    /**
24
     * @var object
25
     */
26
    private $schema;
27
28
    /**
29
     * @var string
30
     */
31
    private $dataPath = '/';
32
33
    /**
34
     * @var string
35
     */
36
    private $baseSchemaPath = '/';
37
38
    /**
39
     * The maximum depth the validator should recurse into $data
40
     * before throwing an exception.
41
     *
42
     * @var int
43
     */
44
    private $maxDepth = 50;
45
46
    /**
47
     * The depth the validator has reached in the data.
48
     *
49
     * @var int
50
     */
51
    private $depth = 0;
52
53
    /**
54
     * @var \Psr\Container\ContainerInterface
55
     */
56
    private $ruleSet;
57
58
    /**
59
     * @var bool
60
     */
61
    private $hasValidated;
62
63
    /**
64
     * @var string
65
     */
66
    private $currentKeyword;
67
68
    /**
69
     * @var mixed
70
     */
71
    private $currentParameter;
72
73
    /**
74
     * @var bool
75
     */
76
    private $ignoreUnknownConstraints = true;
77
78
    /**
79
     * @param mixed                   $data
80
     * @param object                  $schema
81
     * @param ContainerInterface|null $ruleSet
82
     * @param bool                    $ignoreUnknownConstraints
83
     */
84 248
    public function __construct($data, $schema, ContainerInterface $ruleSet = null, $ignoreUnknownConstraints = true)
85
    {
86 248
        if (!is_object($schema)) {
87 2
            throw new \InvalidArgumentException(
88 2
                sprintf('The schema should be an object from a json_decode call, got "%s"', gettype($schema))
89 1
            );
90
        }
91
92 246
        while ($schema instanceof Reference) {
93 16
            $schema = $schema->resolve();
94 8
        }
95
96 246
        $this->data    = $data;
97 246
        $this->schema  = $schema;
98 246
        $this->ruleSet = $ruleSet ?: new DraftFour();
99 246
        $this->ignoreUnknownConstraints = $ignoreUnknownConstraints;
100 246
    }
101
102
    /**
103
     * Define if unknown constraints shall be ignored
104
     *
105
     * @param boolean
106
     */
107 2
    public function setIgnoreUnknownConstraints($ignoreUnknownConstraints)
108
    {
109 2
        $this->ignoreUnknownConstraints = $ignoreUnknownConstraints;
110 2
    }
111
112
    /**
113
     * @return boolean
114
     *
115
     * @throws \League\JsonGuard\Exception\InvalidSchemaException
116
     * @throws \League\JsonGuard\Exception\MaximumDepthExceededException
117
     */
118 90
    public function fails()
119
    {
120 90
        return !$this->passes();
121
    }
122
123
    /**
124
     * @return boolean
125
     *
126
     * @throws \League\JsonGuard\Exception\InvalidSchemaException
127
     * @throws \League\JsonGuard\Exception\MaximumDepthExceededException
128
     */
129 90
    public function passes()
130
    {
131 90
        return empty($this->errors());
132
    }
133
134
    /**
135
     * Get a collection of errors.
136
     *
137
     * @return ValidationError[]
138
     *
139
     * @throws \League\JsonGuard\Exception\InvalidSchemaException
140
     * @throws \League\JsonGuard\Exception\MaximumDepthExceededException
141
     */
142 158
    public function errors()
143
    {
144 158
        $this->validate();
145
146 88
        return $this->errors;
147
    }
148
149
    /**
150
     * Set the maximum allowed depth data will be validated until.
151
     * If the data exceeds the stack depth an exception is thrown.
152
     *
153
     * @param int $maxDepth
154
     *
155
     * @return $this
156
     */
157 4
    public function setMaxDepth($maxDepth)
158
    {
159 4
        $this->maxDepth = $maxDepth;
160
161 4
        return $this;
162
    }
163
164
    /**
165
     * @return \Psr\Container\ContainerInterface
166
     */
167 4
    public function getRuleSet()
168
    {
169 4
        return $this->ruleSet;
170
    }
171
172
    /**
173
     * @return string
174
     */
175 108
    public function getDataPath()
176
    {
177 108
        return $this->dataPath;
178
    }
179
180
    /**
181
     * @return mixed
182
     */
183 94
    public function getData()
184
    {
185 94
        return $this->data;
186
    }
187
188
    /**
189
     * @return object
190
     */
191 104
    public function getSchema()
192
    {
193 104
        return $this->schema;
194
    }
195
196
    /**
197
     * @return string
198
     */
199 246
    public function getSchemaPath()
200
    {
201 246
        return pointer_push($this->baseSchemaPath, $this->currentKeyword);
202
    }
203
204
    /**
205
     * @return string
206
     */
207 94
    public function getCurrentKeyword()
208
    {
209 94
        return $this->currentKeyword;
210
    }
211
212
    /**
213
     * @return mixed
214
     */
215 94
    public function getCurrentParameter()
216
    {
217 94
        return $this->currentParameter;
218
    }
219
220
    /**
221
     * Create a new sub-validator.
222
     *
223
     * @param mixed       $data
224
     * @param object      $schema
225
     * @param string|null $dataPath
226
     * @param string|null $schemaPath
227
     *
228
     * @return Validator
229
     */
230 56
    public function makeSubSchemaValidator($data, $schema, $dataPath = null, $schemaPath = null)
231
    {
232 56
        $validator = new Validator($data, $schema, $this->ruleSet);
233
234 56
        $validator->dataPath         = $dataPath ?: $this->dataPath;
235 56
        $validator->baseSchemaPath   = $schemaPath ?: $this->getSchemaPath();
236 56
        $validator->maxDepth         = $this->maxDepth;
237 56
        $validator->depth            = $this->depth + 1;
238
239 56
        return $validator;
240
    }
241
242
    /**
243
     * Validate the data and collect the errors.
244
     */
245 158
    private function validate()
246
    {
247 158
        if ($this->hasValidated) {
248 30
            return;
249
        }
250
251 158
        $this->checkDepth();
252
253 158
        foreach ($this->schema as $rule => $parameter) {
254 158
            $this->currentKeyword   = $rule;
0 ignored issues
show
Documentation Bug introduced by
It seems like $rule can also be of type integer. However, the property $currentKeyword is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
255 158
            $this->currentParameter = $parameter;
256 158
            $this->mergeErrors($this->validateRule($rule, $parameter));
257 88
            $this->currentKeyword = $this->currentParameter = null;
258 44
        }
259
260 88
        $this->hasValidated = true;
261 88
    }
262
263
    /**
264
     * Keep track of how many levels deep we have validated.
265
     * This is to prevent a really deeply nested JSON
266
     * structure from causing the validator to continue
267
     * validating for an incredibly long time.
268
     *
269
     * @throws \League\JsonGuard\Exception\MaximumDepthExceededException
270
     */
271 158
    private function checkDepth()
272
    {
273 158
        if ($this->depth > $this->maxDepth) {
274 2
            throw new MaximumDepthExceededException();
275
        }
276 158
    }
277
278
    /**
279
     * Validate the data using the given rule and parameter.
280
     *
281
     * @param string $keyword
282
     * @param mixed  $parameter
283
     *
284
     * @return null|ValidationError|ValidationError[]
285
     */
286 158
    private function validateRule($keyword, $parameter)
287
    {
288 158
        if ($this->ignoreUnknownConstraints && !$this->ruleSet->has($keyword)) {
289 8
            return null;
290
        }
291
292 158
        return $this->ruleSet->get($keyword)->validate($this->data, $parameter, $this);
293
    }
294
295
    /**
296
     * Merge the errors with our error collection.
297
     *
298
     * @param ValidationError[]|ValidationError|null $errors
299
     */
300 88
    private function mergeErrors($errors)
301
    {
302 88
        if (is_null($errors)) {
303 84
            return;
304
        }
305
306 88
        $errors       = is_array($errors) ? $errors : [$errors];
307 88
        $this->errors = array_merge($this->errors, $errors);
308 88
    }
309
}
310