Completed
Pull Request — master (#108)
by Matt
02:11
created

Validator::validateRule()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 8
ccs 4
cts 4
cp 1
crap 2
rs 9.4285
c 1
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 function League\JsonReference\pointer_push;
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
     * @param mixed                   $data
75
     * @param object                  $schema
76
     * @param ContainerInterface|null $ruleSet
77
     */
78 178
    public function __construct($data, $schema, ContainerInterface $ruleSet = null)
79
    {
80 178
        if (!is_object($schema)) {
81 2
            throw new \InvalidArgumentException(
82 2
                sprintf('The schema should be an object from a json_decode call, got "%s"', gettype($schema))
83 2
            );
84
        }
85
86 176
        while ($schema instanceof Reference) {
87 16
            $schema = $schema->resolve();
88 16
        }
89
90 176
        $this->data    = $data;
91 176
        $this->schema  = $schema;
92 176
        $this->ruleSet = $ruleSet ?: new DraftFour();
93 176
    }
94
95
    /**
96
     * @return boolean
97
     */
98 82
    public function fails()
99
    {
100 82
        return !$this->passes();
101
    }
102
103
    /**
104
     * @return boolean
105
     */
106 82
    public function passes()
107
    {
108 82
        return empty($this->errors());
109
    }
110
111
    /**
112
     * Get a collection of errors.
113
     *
114
     * @return ValidationError[]
115
     */
116 150
    public function errors()
117
    {
118 150
        $this->validate();
119
120 80
        return $this->errors;
121
    }
122
123
    /**
124
     * Set the maximum allowed depth data will be validated until.
125
     * If the data exceeds the stack depth an exception is thrown.
126
     *
127
     * @param int $maxDepth
128
     *
129
     * @return $this
130
     */
131 4
    public function setMaxDepth($maxDepth)
132
    {
133 4
        $this->maxDepth = $maxDepth;
134
135 4
        return $this;
136
    }
137
138
    /**
139
     * @return \Psr\Container\ContainerInterface
140
     */
141 4
    public function getRuleSet()
142
    {
143 4
        return $this->ruleSet;
144
    }
145
146
    /**
147
     * @return string
148
     */
149 98
    public function getDataPath()
150
    {
151 98
        return $this->dataPath;
152
    }
153
154
    /**
155
     * @return mixed
156
     */
157 92
    public function getData()
158
    {
159 92
        return $this->data;
160
    }
161
162
    /**
163
     * @return object
164
     */
165 94
    public function getSchema()
166
    {
167 94
        return $this->schema;
168
    }
169
170
    /**
171
     * @return string
172
     */
173 176
    public function getSchemaPath()
174
    {
175 176
        return pointer_push($this->baseSchemaPath, $this->currentKeyword);
176
    }
177
178
    /**
179
     * @return string
180
     */
181 92
    public function getCurrentKeyword()
182
    {
183 92
        return $this->currentKeyword;
184
    }
185
186
    /**
187
     * @return mixed
188
     */
189 92
    public function getCurrentParameter()
190
    {
191 92
        return $this->currentParameter;
192
    }
193
194
    /**
195
     * Create a new sub-validator.
196
     *
197
     * @param mixed       $data
198
     * @param object      $schema
199
     * @param string|null $dataPath
200
     * @param string|null $schemaPath
201
     *
202
     * @return Validator
203
     */
204 48
    public function makeSubSchemaValidator($data, $schema, $dataPath = null, $schemaPath = null)
205
    {
206 48
        $validator = new Validator($data, $schema, $this->ruleSet);
207
208 48
        $validator->dataPath         = $dataPath ?: $this->dataPath;
209 48
        $validator->baseSchemaPath   = $schemaPath ?: $this->getSchemaPath();
210 48
        $validator->maxDepth         = $this->maxDepth;
211 48
        $validator->depth            = $this->depth + 1;
212
213 48
        return $validator;
214
    }
215
216
    /**
217
     * Validate the data and collect the errors.
218
     */
219 150
    private function validate()
220
    {
221 150
        if ($this->hasValidated) {
222 30
            return;
223
        }
224
225 150
        $this->checkDepth();
226
227 150
        foreach ($this->schema as $rule => $parameter) {
228 150
            $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...
229 150
            $this->currentParameter = $parameter;
230 150
            $this->mergeErrors($this->validateRule($rule, $parameter));
231 80
            $this->currentKeyword = $this->currentParameter = null;
232 80
        }
233
234 80
        $this->hasValidated = true;
235 80
    }
236
237
    /**
238
     * Keep track of how many levels deep we have validated.
239
     * This is to prevent a really deeply nested JSON
240
     * structure from causing the validator to continue
241
     * validating for an incredibly long time.
242
     *
243
     * @throws \League\JsonGuard\Exception\MaximumDepthExceededException
244
     */
245 150
    private function checkDepth()
246
    {
247 150
        if ($this->depth > $this->maxDepth) {
248 2
            throw new MaximumDepthExceededException();
249
        }
250 150
    }
251
252
    /**
253
     * Validate the data using the given rule and parameter.
254
     *
255
     * @param string $keyword
256
     * @param mixed  $parameter
257
     *
258
     * @return null|ValidationError|ValidationError[]
259
     */
260 150
    private function validateRule($keyword, $parameter)
261
    {
262 150
        if (!$this->ruleSet->has($keyword)) {
263 14
            return null;
264
        }
265
266 150
        return $this->ruleSet->get($keyword)->validate($this->data, $parameter, $this);
267
    }
268
269
    /**
270
     * Merge the errors with our error collection.
271
     *
272
     * @param ValidationError[]|ValidationError|null $errors
273
     */
274 80
    private function mergeErrors($errors)
275
    {
276 80
        if (is_null($errors)) {
277 76
            return;
278
        }
279
280 80
        $errors       = is_array($errors) ? $errors : [$errors];
281 80
        $this->errors = array_merge($this->errors, $errors);
282 80
    }
283
}
284