Completed
Push — master ( fdc4c7...a2204f )
by Stéphane
7s
created

Walker::parseSchema()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 20
ccs 13
cts 13
cp 1
rs 9.2
cc 4
eloc 11
nc 5
nop 2
crap 4
1
<?php
2
3
/*
4
 * This file is part of the JVal package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace JVal;
11
12
use stdClass;
13
14
/**
15
 * Implements the three steps needed to perform a JSON Schema validation,
16
 * i.e. distinct methods to recursively:.
17
 *
18
 * 1) resolve JSON pointer references within schema
19
 * 2) normalize and validate the syntax of the schema
20
 * 3) validate a given instance against it
21
 */
22
class Walker
23
{
24
    /**
25
     * @var Registry
26
     */
27
    private $registry;
28
29
    /**
30
     * @var Resolver
31
     */
32
    private $resolver;
33
34
    /**
35
     * @var array
36
     */
37
    private $parsedSchemas = [];
38
39
    /**
40
     * @var array
41
     */
42
    private $resolvedSchemas = [];
43
44
    /**
45
     * @var Constraint[][]
46
     */
47
    private $constraintsCache = [];
48
49
    /**
50
     * Constructor.
51
     *
52
     * @param Registry $registry
53
     * @param Resolver $resolver
54
     */
55 485
    public function __construct(Registry $registry, Resolver $resolver)
56
    {
57 485
        $this->registry = $registry;
58 485
        $this->resolver = $resolver;
59 485
    }
60
61
    /**
62
     * Recursively resolve JSON pointer references within a given schema.
63
     *
64
     * @param stdClass $schema The schema to resolve
65
     * @param Uri      $uri    The URI of the schema
66
     *
67
     * @return stdClass
68
     */
69 299
    public function resolveReferences(stdClass $schema, Uri $uri)
70
    {
71 299
        $this->resolver->initialize($schema, $uri);
72
73 299
        return $this->doResolveReferences($schema, $uri);
74
    }
75
76
    /**
77
     * @param stdClass $schema
78
     * @param Uri      $uri
79
     *
80
     * @return stdClass
81
     */
82 299
    private function doResolveReferences(stdClass $schema, Uri $uri)
83
    {
84 299
        if ($this->isLooping($schema, $this->resolvedSchemas)) {
85 20
            return $schema;
86
        }
87
88 299
        $inScope = false;
89 299
        if (property_exists($schema, 'id') && is_string($schema->id)) {
90 7
            $this->resolver->enter(new Uri($schema->id));
91 7
            $inScope = true;
92 7
        }
93
94 299
        if (property_exists($schema, '$ref')) {
95 22
            $resolved = $this->resolver->resolve($schema);
96 22
            $this->resolver->enter($resolved[0], $resolved[1]);
97 22
            $schema = $this->doResolveReferences($resolved[1], $resolved[0]);
98 22
            $this->resolver->leave();
99 22
        } else {
100 299
            foreach ($schema as $property => $value) {
101 299
                if (is_object($value)) {
102 114
                    $schema->{$property} = $this->doResolveReferences($value, $uri);
103 299
                } elseif (is_array($value)) {
104 75
                    foreach ($value as $index => $element) {
105 73
                        if (is_object($element)) {
106 44
                            $schema->{$property}[$index] = $this->doResolveReferences($element, $uri);
107 44
                        }
108 75
                    }
109 75
                }
110 299
            }
111
        }
112
113 299
        if ($inScope) {
114 7
            $this->resolver->leave();
115 7
        }
116
117 299
        return $schema;
118
    }
119
120
    /**
121
     * Recursively normalizes a given schema and validates its syntax.
122
     *
123
     * @param stdClass $schema
124
     * @param Context  $context
125
     *
126
     * @return stdClass
127
     */
128 357
    public function parseSchema(stdClass $schema, Context $context)
129
    {
130 357
        if ($this->isLooping($schema, $this->parsedSchemas)) {
131 12
            return $schema;
132
        }
133
134 357
        if (isset($schema->{'$schema'})) {
135 4
            $context->setVersion($schema->{'$schema'});
136 4
        }
137
138 357
        $version = $context->getVersion();
139 357
        $constraints = $this->registry->getConstraints($version);
140 357
        $constraints = $this->filterConstraintsForSchema($constraints, $schema);
141
142 357
        foreach ($constraints as $constraint) {
143 333
            $constraint->normalize($schema, $context, $this);
144 357
        }
145
146 357
        return $schema;
147
    }
148
149
    /**
150
     * Validates an instance against a given schema, populating a context
151
     * with encountered violations.
152
     *
153
     * @param mixed    $instance
154
     * @param stdClass $schema
155
     * @param Context  $context
156
     */
157 348
    public function applyConstraints($instance, stdClass $schema, Context $context)
158
    {
159 348
        if (isset($schema->{'$schema'})) {
160 4
            $context->setVersion($schema->{'$schema'});
161 4
        }
162
163 348
        $cacheKey = gettype($instance).spl_object_hash($schema);
164 348
        $constraints = & $this->constraintsCache[$cacheKey];
165
166 348
        if ($constraints === null) {
167 348
            $version = $context->getVersion();
168 348
            $instanceType = Types::getPrimitiveTypeOf($instance);
169 348
            $constraints = $this->registry->getConstraintsForType($version, $instanceType);
170 348
            $constraints = $this->filterConstraintsForSchema($constraints, $schema);
171 348
        }
172
173 348
        foreach ($constraints as $constraint) {
174 307
            $constraint->apply($instance, $schema, $context, $this);
175 348
        }
176 348
    }
177
178
    /**
179
     * Checks if given schema has been already visited.
180
     *
181
     * @param stdClass $schema
182
     * @param array    $stack
183
     *
184
     * @return bool
185
     */
186 363
    private function isLooping(stdClass $schema, array &$stack)
187
    {
188 363
        $schemaHash = spl_object_hash($schema);
189 363
        if (isset($stack[$schemaHash])) {
190 20
            return true;
191
        }
192
193 363
        $stack[$schemaHash] = true;
194 363
        return false;
195
    }
196
197
    /**
198
     * Filters constraints which should be triggered for given schema.
199
     *
200
     * @param Constraint[] $constraints
201
     * @param stdClass     $schema
202
     *
203
     * @return Constraint[]
204
     */
205 357
    private function filterConstraintsForSchema(array $constraints, stdClass $schema)
206
    {
207 357
        $filtered = [];
208
209 357
        foreach ($constraints as $constraint) {
210 357
            foreach ($constraint->keywords() as $keyword) {
211 357
                if (property_exists($schema, $keyword)) {
212 333
                    $filtered[] = $constraint;
213 333
                    break;
214
                }
215 357
            }
216 357
        }
217
218 357
        return $filtered;
219
    }
220
}
221