Completed
Pull Request — master (#14)
by Benoît
06:02
created

JsonValidator::assertSchemaValid()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 30
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5.4558

Importance

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