Failed Conditions
Pull Request — master (#27)
by Chris
10:24 queued 08:12
created

JsonValidator::__construct()   C

Complexity

Conditions 8
Paths 18

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 17.7072

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 22
ccs 7
cts 15
cp 0.4667
rs 6.6037
cc 8
eloc 16
nc 18
nop 2
crap 17.7072
1
<?php
2
3
/*
4
 * This file is part of the Webmozart JSON package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Webmozart\Json;
13
14
use JsonSchema\Constraints\Factory;
15
use JsonSchema\Exception\InvalidArgumentException;
16
use JsonSchema\Exception\ResourceNotFoundException;
17
use JsonSchema\RefResolver;
18
use JsonSchema\Uri\UriResolver;
19
use JsonSchema\Uri\UriRetriever;
20
use JsonSchema\Validator;
21
use Webmozart\PathUtil\Path;
22
23
/**
24
 * Validates decoded JSON values against a JSON schema.
25
 *
26
 * This class is a wrapper for {@link Validator} that adds exceptions and
27
 * validation of schema files. A few edge cases that are not handled by
28
 * {@link Validator} are handled by this class.
29
 *
30
 * @since  1.0
31
 *
32
 * @author Bernhard Schussek <[email protected]>
33
 */
34
class JsonValidator
35
{
36
    /**
37
     * The schema used for validating schemas.
38
     *
39
     * @var object|null
40
     */
41
    private $metaSchema;
42
43
    /**
44
     * Validator instance used for validation.
45
     *
46
     * @var Validator
47
     */
48
    private $validator;
49
50
    /**
51
     * Validator instance used for internal validation.
52
     *
53
     * @var Validator|null
54
     */
55
    private $internalValidator;
56
57
    /**
58
     * Validator instance used for internal validation.
59
     *
60
     * @var Validator|null
61
     */
62
    private $internalFactory;
63
64
    /**
65
     * Version of the justinrainbow/json-schema library.
66
     *
67
     * @var int
68
     */
69
    private $jsonSchemaVersion;
70
71
    /**
72
     * JsonValidator constructor.
73
     *
74
     * @param Validator|null    $validator    JsonSchema\Validator instance
75
     *                                        to use
76
     * @param UriRetriever|null $uriRetriever The retriever for fetching
77
     *                                        JSON schemas
78
     */
79 113
    public function __construct(Validator $validator = null, UriRetriever $uriRetriever = null)
80
    {
81 113
        if (method_exists('JsonSchema\Validator', 'getUriRetriever')) {
82
            if (!method_exists('JsonSchema\Validator', 'getSchemaStorage')) {
83
                $this->jsonSchemaVersion = 2;
84
            } else {
85
                $this->jsonSchemaVersion = 3;
86
            }
87
            $this->validator = $validator ?: new Validator();
88
            if ($uriRetriever) {
89
                $this->validator->setUriRetriever($uriRetriever);
0 ignored issues
show
Bug introduced by
The method setUriRetriever() does not seem to exist on object<JsonSchema\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
90
            }
91
            if (2 === $this->jsonSchemaVersion) {
92
                $this->resolver = new RefResolver($uriRetriever ?: new UriRetriever(), new UriResolver());
0 ignored issues
show
Bug introduced by
The property resolver does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
93
            }
94
        } else {
95 113
            $this->jsonSchemaVersion = 4;
96 113
            $this->internalFactory = new Factory(null, $uriRetriever);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \JsonSchema\Constrai...ry(null, $uriRetriever) of type object<JsonSchema\Constraints\Factory> is incompatible with the declared type object<JsonSchema\Validator>|null of property $internalFactory.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
97 113
            $this->validator = $validator ?: new Validator($this->internalFactory);
98 113
            $this->internalValidator = new Validator($this->internalFactory);
99
        }
100 113
    }
101
102
    /**
103
     * Validates JSON data against a schema.
104
     *
105
     * The schema may be passed as file path or as object returned from
106
     * `json_decode($schemaFile)`.
107
     *
108
     * @param mixed              $data   The decoded JSON data
109
     * @param string|object|null $schema The schema file or object. If `null`,
110
     *                                   the validator will look for a `$schema`
111
     *                                   property
112
     *
113
     * @return string[] The errors found during validation. Returns an empty
114
     *                  array if no errors were found
115
     *
116
     * @throws InvalidSchemaException If the schema is invalid
117
     */
118 29
    public function validate($data, $schema = null)
119
    {
120 29
        if (null === $schema && isset($data->{'$schema'})) {
121 1
            $schema = $data->{'$schema'};
122
        }
123
124 29
        if (is_string($schema)) {
125 16
            $schema = $this->loadSchema($schema);
126 29
        } elseif (is_object($schema)) {
127 26
            $this->assertSchemaValid($schema);
128
        } else {
129 3
            throw new InvalidSchemaException(sprintf(
130
                'The schema must be given as string, object or in the "$schema" '.
131 3
                'property of the JSON data. Got: %s',
132 3
                is_object($schema) ? get_class($schema) : gettype($schema)
133
            ));
134
        }
135
136 26
        $this->validator->reset();
137
138
        try {
139 26
            $this->validator->check($data, $schema);
140 2
        } catch (ResourceNotFoundException $e) {
141 2
            throw new InvalidSchemaException($e->getMessage(), $e->getCode(), $e);
142
        } catch (InvalidArgumentException $e) {
143
            throw new InvalidSchemaException(sprintf(
144
                'The schema is invalid: %s',
145
                $e->getMessage()
146
            ), 0, $e);
147
        }
148
149 26
        $errors = array();
150
151 26
        if (!$this->validator->isValid()) {
152 14
            $errors = (array) $this->validator->getErrors();
153
154 14
            foreach ($errors as $key => $error) {
155 14
                $prefix = $error['property'] ? $error['property'].': ' : '';
156 14
                $errors[$key] = $prefix.$error['message'];
157
            }
158
        }
159
160 26
        return $errors;
161
    }
162
163 26
    private function assertSchemaValid($schema)
164
    {
165 26
        if (null === $this->metaSchema) {
166
            // The meta schema is obviously not validated. If we
167
            // validate it against itself, we have an endless recursion
168 26
            $this->metaSchema = json_decode(file_get_contents(__DIR__.'/../res/meta-schema.json'));
169
170 26
            switch ($this->jsonSchemaVersion) {
171 26
                case 3:
172
                    $this->validator->getSchemaStorage()->addSchema('http://json-schema.org/draft-04/schema', $this->metaSchema);
0 ignored issues
show
Bug introduced by
The method getSchemaStorage() does not seem to exist on object<JsonSchema\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
173
                    break;
174 26
                case 4:
175 26
                    $this->internalFactory->getSchemaStorage()->addSchema('http://json-schema.org/draft-04/schema', $this->metaSchema);
0 ignored issues
show
Bug introduced by
The method getSchemaStorage() does not seem to exist on object<JsonSchema\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
176 26
                    break;
177
            }
178
        }
179
180 26
        if ($schema === $this->metaSchema) {
181 26
            return;
182
        }
183
184 26
        $errors = $this->validate($schema, $this->metaSchema);
185
186 26
        if (count($errors) > 0) {
187 2
            throw new InvalidSchemaException(sprintf(
188 2
                "The schema is invalid:\n%s",
189 2
                implode("\n", $errors)
190
            ));
191
        }
192
193 24
        if (3 === $this->jsonSchemaVersion && !isset($schema->{'$ref'})) {
194
            $this->validator->getSchemaStorage()->addSchema($schema->id, $schema);
0 ignored issues
show
Bug introduced by
The method getSchemaStorage() does not seem to exist on object<JsonSchema\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
195
        }
196 24
    }
197
198 16
    private function loadSchema($file)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
199
    {
200
        // Retrieve schema and cache in UriRetriever
201 16
        $file = Path::canonicalize($file);
202
203
        // Add file:// scheme if necessary
204 16
        if (false === strpos($file, '://')) {
205 14
            $file = 'file://'.$file;
206
        }
207
208 16
        if (2 === $this->jsonSchemaVersion) {
209
            // Resolve references to other schemas
210
            try {
211
                $schema = $this->resolver->resolve($file);
212
            } catch (ResourceNotFoundException $e) {
213
                throw new InvalidSchemaException(sprintf(
214
                    'The schema %s does not exist.',
215
                    $file
216
                ), 0, $e);
217
            }
218
        } else {
219 16
            $schema = (object) array('$ref' => $file);
220
        }
221
222
        try {
223 16
            $this->assertSchemaValid($schema);
224
        } catch (InvalidSchemaException $e) {
225
            throw new InvalidSchemaException(sprintf(
226
                'An error occurred while loading the schema %s: %s',
227
                $file,
228
                $e->getMessage()
229
            ), 0, $e);
230
        }
231
232 16
        return $schema;
233
    }
234
}
235