Completed
Pull Request — master (#39)
by Claudio
04:25
created

JsonValidator::loadSchema()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.8846

Importance

Changes 0
Metric Value
dl 0
loc 24
c 0
b 0
f 0
ccs 7
cts 13
cp 0.5385
rs 8.9713
cc 3
eloc 14
nc 4
nop 1
crap 3.8846
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
     * JsonValidator constructor.
52
     *
53
     * @param Validator|null            $validator    JsonSchema\Validator
54
     *                                                instance to use
55
     * @param UriRetriever|null         $uriRetriever The retriever for fetching
56
     *                                                JSON schemas
57
     * @param UriResolverInterface|null $uriResolver  The resolver for URIs
58
     */
59 113
    public function __construct(Validator $validator = null, UriRetriever $uriRetriever = null, UriResolverInterface $uriResolver = null)
0 ignored issues
show
Unused Code introduced by
The parameter $uriRetriever is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $uriResolver is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
60
    {
61 113
        $this->validator = $validator ?: new Validator();
62 113
    }
63
64
    /**
65
     * Validates JSON data against a schema.
66
     *
67
     * The schema may be passed as file path or as object returned from
68
     * `json_decode($schemaFile)`.
69
     *
70
     * @param mixed              $data   The decoded JSON data
71
     * @param string|object|null $schema The schema file or object. If `null`,
72
     *                                   the validator will look for a `$schema`
73
     *                                   property
74
     *
75
     * @return string[] The errors found during validation. Returns an empty
76
     *                  array if no errors were found
77
     *
78
     * @throws InvalidSchemaException If the schema is invalid
79
     */
80 27
    public function validate($data, $schema = null)
81
    {
82 27
        if (null === $schema && isset($data->{'$schema'})) {
83
            $schema = $data->{'$schema'};
84
        }
85
86 27
        if (is_string($schema)) {
87 14
            $schema = $this->loadSchema($schema);
88 27
        } elseif (is_object($schema)) {
89 24
            $this->assertSchemaValid($schema);
90
        } else {
91 3
            throw new InvalidSchemaException(sprintf(
92
                'The schema must be given as string, object or in the "$schema" '.
93 3
                'property of the JSON data. Got: %s',
94 3
                is_object($schema) ? get_class($schema) : gettype($schema)
95
            ));
96
        }
97
98 24
        $this->validator->reset();
99
100
        try {
101 24
            $this->validator->check($data, $schema);
102 2
        } catch (InvalidArgumentException | ResourceNotFoundException $e) {
103 2
            throw new InvalidSchemaException(sprintf(
104 2
                'The schema is invalid: %s',
105 2
                $e->getMessage()
106 2
            ), 0, $e);
107
        }
108
109 24
        $errors = array();
110
111 24
        if (!$this->validator->isValid()) {
112 13
            $errors = (array) $this->validator->getErrors();
113
114 13
            foreach ($errors as $key => $error) {
115 13
                $prefix = $error['property'] ? $error['property'].': ' : '';
116 13
                $errors[$key] = $prefix.$error['message'];
117
            }
118
        }
119
120 24
        return $errors;
121
    }
122
123 24
    private function assertSchemaValid($schema)
124
    {
125 24
        if (null === $this->metaSchema) {
126
            // The meta schema is obviously not validated. If we
127
            // validate it against itself, we have an endless recursion
128 24
            $this->metaSchema = json_decode(file_get_contents(__DIR__.'/../res/meta-schema.json'));
129
        }
130
131 24
        if ($schema === $this->metaSchema) {
132 24
            return;
133
        }
134
135 24
        $errors = $this->validate($schema, $this->metaSchema);
136
137 24
        if (count($errors) > 0) {
138 2
            throw new InvalidSchemaException(sprintf(
139 2
                "The schema is invalid:\n%s",
140 2
                implode("\n", $errors)
141
            ));
142
        }
143 22
    }
144
145 14
    private function loadSchema($file)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
146
    {
147
        // Retrieve schema and cache in UriRetriever
148 14
        $file = Path::canonicalize($file);
149
150
        // Add file:// scheme if necessary
151 14
        if (false === strpos($file, '://')) {
152 13
            $file = 'file://'.$file;
153
        }
154
155 14
        $schema = (object) ['$ref' => $file];
156
157
        try {
158 14
            $this->assertSchemaValid($schema);
159
        } catch (InvalidSchemaException $e) {
160
            throw new InvalidSchemaException(sprintf(
161
                'An error occurred while loading the schema %s: %s',
162
                $file,
163
                $e->getMessage()
164
            ), 0, $e);
165
        }
166
167 14
        return $schema;
168
    }
169
}
170