Completed
Push — master ( f03bf9...cd8df8 )
by Bernhard
03:21
created

JsonValidator::assertSchemaValid()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 30
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5.073

Importance

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