Failed Conditions
Push — master ( 2488d5...d2f10c )
by Bernhard
07:01
created

JsonValidator::assertSchemaValid()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

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