Completed
Pull Request — master (#108)
by Matt
12:20
created

Validator::getCurrentKeyword()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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