Completed
Push — master ( 4bfd5a...f9d52a )
by Bernhard
03:48
created

JsonValidator   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 136
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 96.08%

Importance

Changes 5
Bugs 1 Features 1
Metric Value
wmc 16
c 5
b 1
f 1
lcom 1
cbo 4
dl 0
loc 136
ccs 49
cts 51
cp 0.9608
rs 10

4 Methods

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