Failed Conditions
Pull Request — master (#27)
by Chris
20:29
created

src/JsonValidator.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 115
     *
67
     * @var int
68 115
     */
69 115
    private $jsonSchemaVersion;
70 115
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
    public function __construct(Validator $validator = null, UriRetriever $uriRetriever = null)
80
    {
81
        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 31
            if ($uriRetriever) {
89
                $this->validator->setUriRetriever($uriRetriever);
0 ignored issues
show
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 31
            }
91 1
            if (2 === $this->jsonSchemaVersion) {
92
                $this->resolver = new RefResolver($uriRetriever ?: new UriRetriever(), new UriResolver());
0 ignored issues
show
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 31
        } else {
95 18
            $this->jsonSchemaVersion = 4;
96 29
            $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 26
            $this->validator = $validator ?: new Validator($this->internalFactory);
98
            $this->internalValidator = new Validator($this->internalFactory);
99 3
        }
100
    }
101 3
102 3
    /**
103
     * Validates JSON data against a schema.
104
     *
105
     * The schema may be passed as file path or as object returned from
106 26
     * `json_decode($schemaFile)`.
107
     *
108
     * @param mixed              $data   The decoded JSON data
109 26
     * @param string|object|null $schema The schema file or object. If `null`,
110 1
     *                                   the validator will look for a `$schema`
111 1
     *                                   property
112 1
     *
113 1
     * @return string[] The errors found during validation. Returns an empty
114 1
     *                  array if no errors were found
115
     *
116
     * @throws InvalidSchemaException If the schema is invalid
117 26
     */
118
    public function validate($data, $schema = null)
119 26
    {
120 15
        if (null === $schema && isset($data->{'$schema'})) {
121
            $schema = $data->{'$schema'};
122 15
        }
123 15
124 15
        if (is_string($schema)) {
125
            $schema = $this->loadSchema($schema);
126
        } elseif (is_object($schema)) {
127
            $this->assertSchemaValid($schema);
128 26
        } else {
129
            throw new InvalidSchemaException(sprintf(
130
                'The schema must be given as string, object or in the "$schema" '.
131 26
                'property of the JSON data. Got: %s',
132
                is_object($schema) ? get_class($schema) : gettype($schema)
133 26
            ));
134
        }
135
136 26
        $this->validator->reset();
137
138
        try {
139 26
            $this->validator->check($data, $schema);
140 26
        } catch (ResourceNotFoundException $e) {
141
            throw new InvalidSchemaException($e->getMessage(), $e->getCode(), $e);
142
        } catch (InvalidArgumentException $e) {
143 26
            throw new InvalidSchemaException(sprintf(
144
                'The schema is invalid: %s',
145 26
                $e->getMessage()
146 3
            ), 0, $e);
147 3
        }
148 3
149
        $errors = array();
150
151 23
        if (!$this->validator->isValid()) {
152
            $errors = (array) $this->validator->getErrors();
153 18
154
            foreach ($errors as $key => $error) {
155
                $prefix = $error['property'] ? $error['property'].': ' : '';
156 18
                $errors[$key] = $prefix.$error['message'];
157
            }
158
        }
159 18
160 16
        return $errors;
161
    }
162
163
    private function assertSchemaValid($schema)
164
    {
165 18
        if (null === $this->metaSchema) {
166 2
            // The meta schema is obviously not validated. If we
167 2
            // validate it against itself, we have an endless recursion
168 2
            $this->metaSchema = json_decode(file_get_contents(__DIR__.'/../res/meta-schema.json'));
169
170 2
            switch ($this->jsonSchemaVersion) {
171
                case 3:
172
                    $this->validator->getSchemaStorage()->addSchema('http://json-schema.org/draft-04/schema', $this->metaSchema);
0 ignored issues
show
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 16
                case 4:
175 2
                    $this->internalFactory->getSchemaStorage()->addSchema('http://json-schema.org/draft-04/schema', $this->metaSchema);
0 ignored issues
show
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 2
                    break;
177 2
            }
178
        }
179 2
180 2
        if ($schema === $this->metaSchema) {
181
            return;
182
        }
183 14
184
        $errors = $this->validate($schema, $this->metaSchema);
185
186
        if (count($errors) > 0) {
187
            throw new InvalidSchemaException(sprintf(
188
                "The schema is invalid:\n%s",
189
                implode("\n", $errors)
190
            ));
191
        }
192
193
        if (3 === $this->jsonSchemaVersion && !isset($schema->{'$ref'})) {
194
            $this->validator->getSchemaStorage()->addSchema($schema->id, $schema);
0 ignored issues
show
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
    }
197
198
    private function loadSchema($file)
199
    {
200
        // Retrieve schema and cache in UriRetriever
201
        $file = Path::canonicalize($file);
202
203
        // Add file:// scheme if necessary
204
        if (false === strpos($file, '://')) {
205
            $file = 'file://'.$file;
206
        }
207
208
        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
            $schema = (object) array('$ref' => $file);
220
        }
221
222
        try {
223
            $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
        return $schema;
233
    }
234
}
235