Failed Conditions
Pull Request — master (#20)
by Chris
05:31
created

JsonValidator::__construct()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 5.1158

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 10
ccs 5
cts 6
cp 0.8333
rs 8.8571
cc 5
eloc 6
nc 4
nop 3
crap 5.1158
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\Validator;
20
use Webmozart\PathUtil\Path;
21
22
/**
23
 * Validates decoded JSON values against a JSON schema.
24
 *
25
 * This class is a wrapper for {@link Validator} that adds exceptions and
26
 * validation of schema files. A few edge cases that are not handled by
27
 * {@link Validator} are handled by this class.
28
 *
29
 * @since  1.0
30
 *
31
 * @author Bernhard Schussek <[email protected]>
32
 */
33
class JsonValidator
34
{
35
    /**
36
     * The schema used for validating schemas.
37
     *
38
     * @var object|null
39
     */
40
    private $metaSchema;
41
42
    /**
43
     * Validator instance used for validation.
44
     *
45
     * @var Validator
46
     */
47
    private $validator;
48
49
    /**
50
     * For fetching and resolving JSON schemas.
51
     *
52
     * @var RefResolver
53
     */
54
    private $refResolver;
55
56
    /**
57
     * JsonValidator constructor.
58
     *
59
     * @param Validator|null $validator JsonSchema\Validator instance to use.
60
     */
61 111
    public function __construct(Validator $validator = null, UriRetriever $uriRetriever = null, UriResolver $uriResolver = null)
62
    {
63 111
        $this->validator = $validator ?: new Validator();
64 111
        if(!interface_exists('JsonSchema\UriRetrieverInterface')) {
65
            // Remove when justinrainbow/json-schema dependency is ^2.0
66
            $this->refResolver = new RefResolver($uriRetriever);
0 ignored issues
show
Bug introduced by
The call to RefResolver::__construct() misses a required argument $uriResolver.

This check looks for function calls that miss required arguments.

Loading history...
Bug introduced by
It seems like $uriRetriever defined by parameter $uriRetriever on line 61 can be null; however, JsonSchema\RefResolver::__construct() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
67
        } else {
68 111
            $this->refResolver = new RefResolver($uriRetriever ?: new UriRetriever(), $uriResolver ?: new UriResolver());
69
        }
70 111
    }
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)
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...
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
        try {
164 18
            if(!interface_exists('JsonSchema\UriRetrieverInterface')) {
165
                // Remove when justinrainbow/json-schema dependency is ^2.0
166
                $schema = $this->refResolver->getUriRetriever()->retrieve($file);
0 ignored issues
show
Bug introduced by
The method getUriRetriever() does not seem to exist on object<JsonSchema\RefResolver>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
167
                $this->refResolver->resolve($schema, $file);
0 ignored issues
show
Unused Code introduced by
The call to RefResolver::resolve() has too many arguments starting with $file.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
168
            } else {
169 18
                $schema = $this->refResolver->resolve($file);
170
            }
171 2
        } catch (ResourceNotFoundException $e) {
172 2
            throw new InvalidSchemaException(sprintf(
173 2
                'The schema %s does not exist.',
174
                $file
175 2
            ), 0, $e);
176
        }
177
178
        try {
179 16
            $this->assertSchemaValid($schema);
180 2
        } catch (InvalidSchemaException $e) {
181 2
            throw new InvalidSchemaException(sprintf(
182 2
                'An error occurred while loading the schema %s: %s',
183
                $file,
184 2
                $e->getMessage()
185 2
            ), 0, $e);
186
        }
187
188 14
        return $schema;
189
    }
190
}
191