Completed
Pull Request — master (#10)
by Jan
02:39
created

Walker   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 194
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 100%

Importance

Changes 4
Bugs 0 Features 2
Metric Value
wmc 31
c 4
b 0
f 2
lcom 1
cbo 6
dl 0
loc 194
ccs 89
cts 89
cp 1
rs 9.8

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
C resolveReferences() 0 42 12
B parseSchema() 0 23 6
D applyConstraints() 0 39 10
A isLooping() 0 10 2
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 stdClass[]
36
     */
37
    private $parsedSchemas = [];
38
39
    /**
40
     * @var stdClass[]
41
     */
42
    private $resolvedSchemas = [];
43
44
    /**
45
     * @var Constraint[][]
46
     */
47
    private $constraintsCacheA = [];
48
49
    /**
50
     * @var Constraint[][]
51
     */
52
    private $constraintsCacheB = [];
53
54
    /**
55
     * Constructor.
56
     *
57
     * @param Registry $registry
58
     * @param Resolver $resolver
59
     */
60 485
    public function __construct(Registry $registry, Resolver $resolver)
61
    {
62 485
        $this->registry = $registry;
63 485
        $this->resolver = $resolver;
64 485
    }
65
66
    /**
67
     * Recursively resolve JSON pointer references within a given schema.
68
     *
69
     * @param stdClass $schema The schema to resolve
70
     * @param Uri      $uri    The URI of the schema
71
     *
72
     * @return stdClass
73
     */
74 299
    public function resolveReferences(stdClass $schema, Uri $uri)
75
    {
76 299
        if ($this->isLooping($schema, $this->resolvedSchemas)) {
77 20
            return $schema;
78
        }
79
80 299
        if (!$this->resolver->hasBaseSchema()) {
81 299
            $this->resolver->setBaseSchema($schema, $uri);
82 299
        }
83
84 299
        $inScope = false;
85
86 299
        if (property_exists($schema, 'id') && is_string($schema->id)) {
87 7
            $this->resolver->enter(new Uri($schema->id));
88 7
            $inScope = true;
89 7
        }
90
91 299
        if (property_exists($schema, '$ref')) {
92 22
            $resolved = $this->resolver->resolve($schema);
93 22
            $this->resolver->enter($resolved[0], $resolved[1]);
94 22
            $schema = $this->resolveReferences($resolved[1], $resolved[0]);
95 22
            $this->resolver->leave();
96 22
        } else {
97 299
            foreach ($schema as $property => $value) {
98 299
                if (is_object($value)) {
99 114
                    $schema->{$property} = $this->resolveReferences($value, $uri);
100 299
                } elseif (is_array($value)) {
101 75
                    foreach ($value as $index => $element) {
102 73
                        if (is_object($element)) {
103 44
                            $schema->{$property}[$index] = $this->resolveReferences($element, $uri);
104 44
                        }
105 75
                    }
106 75
                }
107 299
            }
108
        }
109
110 299
        if ($inScope) {
111 7
            $this->resolver->leave();
112 7
        }
113
114 299
        return $schema;
115
    }
116
117
    /**
118
     * Recursively normalizes a given schema and validates its syntax.
119
     *
120
     * @param stdClass $schema
121
     * @param Context  $context
122
     *
123
     * @return stdClass
124
     */
125 357
    public function parseSchema(stdClass $schema, Context $context)
126
    {
127 357
        if ($this->isLooping($schema, $this->parsedSchemas)) {
128 12
            return $schema;
129
        }
130
131 357
        if (isset($schema->{'$schema'})) {
132 4
            $context->setVersion($schema->{'$schema'});
133 4
        }
134
135 357
        $constraints = $this->registry->getConstraints($context->getVersion());
136
137 357
        foreach ($constraints as $constraint) {
138 357
            foreach ($constraint->keywords() as $keyword) {
139 357
                if (property_exists($schema, $keyword)) {
140 333
                    $constraint->normalize($schema, $context, $this);
141 333
                    break;
142
                }
143 357
            }
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
        $instanceType = gettype($instance);
164 348
        $schemaHash = spl_object_hash($schema);
165 348
        $constraintsA = & $this->constraintsCacheA[$schemaHash.$instanceType];
166
167 348
        if ($constraintsA === null) {
168 348
            $version = $context->getVersion();
169 348
            $constraintsB = & $this->constraintsCacheB[$version.$instanceType];
170
171 348
            if ($constraintsB === null) {
172 348
                $instanceType = Types::getPrimitiveTypeOf($instance);
173 348
                $constraintsB = [];
174 348
                foreach ($this->registry->getConstraints($version) as $constraint) {
175 348
                    if ($constraint->supports($instanceType)) {
176 348
                        $constraintsB[] = $constraint;
177 348
                    }
178 348
                }
179 348
            }
180
181 348
            $constraintsA = [];
182 348
            foreach ($constraintsB as $constraint) {
183 348
                foreach ($constraint->keywords() as $keyword) {
184 348
                    if (property_exists($schema, $keyword)) {
185 307
                        $constraintsA[] = $constraint;
186 307
                        break;
187
                    }
188 348
                }
189 348
            }
190 348
        }
191
192 348
        foreach ($constraintsA as $constraint) {
193 307
            $constraint->apply($instance, $schema, $context, $this);
194 348
        }
195 348
    }
196
197
    /**
198
     * Checks if given schema has been already visited.
199
     *
200
     * @param stdClass $schema
201
     * @param array    $stack
202
     *
203
     * @return bool
204
     */
205 363
    private function isLooping(stdClass $schema, array &$stack)
206
    {
207 363
        $schemaHash = spl_object_hash($schema);
208 363
        if (isset($stack[$schemaHash])) {
209 20
            return true;
210
        }
211
212 363
        $stack[$schemaHash] = true;
213 363
        return false;
214
    }
215
}
216