JsonValidator::loadSchema()   B
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 32
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 4

Importance

Changes 8
Bugs 2 Features 3
Metric Value
c 8
b 2
f 3
dl 0
loc 32
ccs 16
cts 16
cp 1
rs 8.5806
cc 4
eloc 20
nc 6
nop 1
crap 4
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\Exception\InvalidArgumentException;
15
use JsonSchema\Exception\ResourceNotFoundException;
16
use JsonSchema\RefResolver;
17
use JsonSchema\Uri\UriResolver;
18
use JsonSchema\Uri\UriRetriever;
19
use JsonSchema\UriResolverInterface;
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
     * Reference resolver.
52
     *
53
     * @var RefResolver
54
     */
55
    private $resolver;
56
57
    /**
58
     * JsonValidator constructor.
59
     *
60
     * @param Validator|null            $validator    JsonSchema\Validator
61
     *                                                instance to use
62
     * @param UriRetriever|null         $uriRetriever The retriever for fetching
63
     *                                                JSON schemas
64
     * @param UriResolverInterface|null $uriResolver  The resolver for URIs
65
     */
66 115
    public function __construct(Validator $validator = null, UriRetriever $uriRetriever = null, UriResolverInterface $uriResolver = null)
67
    {
68 115
        $this->validator = $validator ?: new Validator();
69 115
        $this->resolver = new RefResolver($uriRetriever ?: new UriRetriever(), $uriResolver ?: new UriResolver());
70 115
    }
71
72
    /**
73
     * Validates JSON data against a schema.
74
     *
75
     * The schema may be passed as file path or as object returned from
76
     * `json_decode($schemaFile)`.
77
     *
78
     * @param mixed              $data   The decoded JSON data
79
     * @param string|object|null $schema The schema file or object. If `null`,
80
     *                                   the validator will look for a `$schema`
81
     *                                   property
82
     *
83
     * @return string[] The errors found during validation. Returns an empty
84
     *                  array if no errors were found
85
     *
86
     * @throws InvalidSchemaException If the schema is invalid
87
     */
88 31
    public function validate($data, $schema = null)
89
    {
90 31
        if (null === $schema && isset($data->{'$schema'})) {
91 1
            $schema = $data->{'$schema'};
92
        }
93
94 31
        if (is_string($schema)) {
95 18
            $schema = $this->loadSchema($schema);
96 29
        } elseif (is_object($schema)) {
97 26
            $this->assertSchemaValid($schema);
98
        } else {
99 3
            throw new InvalidSchemaException(sprintf(
100
                'The schema must be given as string, object or in the "$schema" '.
101 3
                'property of the JSON data. Got: %s',
102 3
                is_object($schema) ? get_class($schema) : gettype($schema)
103
            ));
104
        }
105
106 26
        $this->validator->reset();
107
108
        try {
109 26
            $this->validator->check($data, $schema);
110 1
        } catch (InvalidArgumentException $e) {
111 1
            throw new InvalidSchemaException(sprintf(
112 1
                'The schema is invalid: %s',
113 1
                $e->getMessage()
114 1
            ), 0, $e);
115
        }
116
117 26
        $errors = array();
118
119 26
        if (!$this->validator->isValid()) {
120 15
            $errors = (array) $this->validator->getErrors();
121
122 15
            foreach ($errors as $key => $error) {
123 15
                $prefix = $error['property'] ? $error['property'].': ' : '';
124 15
                $errors[$key] = $prefix.$error['message'];
125
            }
126
        }
127
128 26
        return $errors;
129
    }
130
131 26
    private function assertSchemaValid($schema)
132
    {
133 26
        if (null === $this->metaSchema) {
134
            // The meta schema is obviously not validated. If we
135
            // validate it against itself, we have an endless recursion
136 26
            $this->metaSchema = json_decode(file_get_contents(__DIR__.'/../res/meta-schema.json'));
137
        }
138
139 26
        if ($schema === $this->metaSchema) {
140 26
            return;
141
        }
142
143 26
        $errors = $this->validate($schema, $this->metaSchema);
144
145 26
        if (count($errors) > 0) {
146 3
            throw new InvalidSchemaException(sprintf(
147 3
                "The schema is invalid:\n%s",
148 3
                implode("\n", $errors)
149
            ));
150
        }
151 23
    }
152
153 18
    private function loadSchema($file)
154
    {
155
        // Retrieve schema and cache in UriRetriever
156 18
        $file = Path::canonicalize($file);
157
158
        // Add file:// scheme if necessary
159 18
        if (false === strpos($file, '://')) {
160 16
            $file = 'file://'.$file;
161
        }
162
163
        // Resolve references to other schemas
164
        try {
165 18
            $schema = $this->resolver->resolve($file);
166 2
        } catch (ResourceNotFoundException $e) {
167 2
            throw new InvalidSchemaException(sprintf(
168 2
                'The schema %s does not exist.',
169
                $file
170 2
            ), 0, $e);
171
        }
172
173
        try {
174 16
            $this->assertSchemaValid($schema);
175 2
        } catch (InvalidSchemaException $e) {
176 2
            throw new InvalidSchemaException(sprintf(
177 2
                'An error occurred while loading the schema %s: %s',
178
                $file,
179 2
                $e->getMessage()
180 2
            ), 0, $e);
181
        }
182
183 14
        return $schema;
184
    }
185
}
186