Completed
Push — master ( d0b5dd...691c19 )
by Stéphane
7s
created

Walker::isProcessed()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
ccs 6
cts 6
cp 1
rs 9.4285
cc 2
eloc 6
nc 2
nop 2
crap 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 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 491
    public function __construct(Registry $registry, Resolver $resolver)
56
    {
57 491
        $this->registry = $registry;
58 491
        $this->resolver = $resolver;
59 491
    }
60
61
    /**
62
     * Recursively resolves 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 305
    public function resolveReferences(stdClass $schema, Uri $uri)
70
    {
71 305
        $this->resolver->initialize($schema, $uri);
72
73 305
        return $this->doResolveReferences($schema, $uri);
74
    }
75
76
    /**
77
     * @param stdClass $schema
78
     * @param Uri      $uri
79
     * @param bool     $inProperties
80
     *
81
     * @return stdClass
82
     */
83 305
    private function doResolveReferences(stdClass $schema, Uri $uri, $inProperties = false)
84
    {
85 305
        if ($this->isProcessed($schema, $this->resolvedSchemas)) {
86 13
            return $schema;
87
        }
88
89 305
        $inScope = false;
90
91 305
        if (property_exists($schema, 'id') && is_string($schema->id)) {
92 7
            $this->resolver->enter(new Uri($schema->id));
93 7
            $inScope = true;
94 7
        }
95
96 305
        if (property_exists($schema, '$ref')) {
97 22
            $resolved = $this->resolver->resolve($schema);
98 22
            $this->resolver->enter($resolved[0], $resolved[1]);
99 22
            $schema = $this->doResolveReferences($resolved[1], $resolved[0]);
100 22
            $this->resolver->leave();
101 22
        } else {
102 305
            $version = $this->getVersion($schema);
103
104 305
            foreach ($schema as $property => $value) {
105 305
                if ($inProperties || $this->registry->hasKeyword($version, $property)) {
106 303
                    if (is_object($value)) {
107 118
                        $schema->{$property} = $this->doResolveReferences($value, $uri, $property === 'properties');
108 303
                    } elseif (is_array($value)) {
109 66
                        foreach ($value as $index => $element) {
110 66
                            if (is_object($element)) {
111 48
                                $schema->{$property}[$index] = $this->doResolveReferences($element, $uri);
112 48
                            }
113 66
                        }
114 66
                    }
115 303
                }
116 305
            }
117
        }
118
119 305
        if ($inScope) {
120 7
            $this->resolver->leave();
121 7
        }
122
123 305
        return $schema;
124
    }
125
126
    /**
127
     * Recursively normalizes a given schema and validates its syntax.
128
     *
129
     * @param stdClass $schema
130
     * @param Context  $context
131
     *
132
     * @return stdClass
133
     */
134 363
    public function parseSchema(stdClass $schema, Context $context)
135
    {
136 363
        if ($this->isProcessed($schema, $this->parsedSchemas)) {
137 12
            return $schema;
138
        }
139
140 363
        $version = $this->getVersion($schema);
141 363
        $constraints = $this->registry->getConstraints($version);
142 363
        $constraints = $this->filterConstraintsForSchema($constraints, $schema);
143
144 363
        foreach ($constraints as $constraint) {
145 337
            $constraint->normalize($schema, $context, $this);
146 363
        }
147
148 363
        return $schema;
149
    }
150
151
    /**
152
     * Validates an instance against a given schema, populating a context
153
     * with encountered violations.
154
     *
155
     * @param mixed    $instance
156
     * @param stdClass $schema
157
     * @param Context  $context
158
     */
159 354
    public function applyConstraints($instance, stdClass $schema, Context $context)
160
    {
161 354
        $cacheKey = gettype($instance).spl_object_hash($schema);
162 354
        $constraints = & $this->constraintsCache[$cacheKey];
163
164 354
        if ($constraints === null) {
165 354
            $version = $this->getVersion($schema);
166 354
            $instanceType = Types::getPrimitiveTypeOf($instance);
167 354
            $constraints = $this->registry->getConstraintsForType($version, $instanceType);
168 354
            $constraints = $this->filterConstraintsForSchema($constraints, $schema);
169 354
        }
170
171 354
        foreach ($constraints as $constraint) {
172 311
            $constraint->apply($instance, $schema, $context, $this);
173 354
        }
174 354
    }
175
176
    /**
177
     * Returns whether a schema has already been processed and stored in
178
     * a given collection. This is helpful both as a cache lookup and as
179
     * an infinite recursion check.
180
     *
181
     * @param stdClass $schema
182
     * @param array    $processed
183
     *
184
     * @return bool
185
     */
186 369
    private function isProcessed(stdClass $schema, array &$processed)
187
    {
188 369
        $schemaHash = spl_object_hash($schema);
189
190 369
        if (isset($processed[$schemaHash])) {
191 13
            return true;
192
        }
193
194 369
        $processed[$schemaHash] = true;
195
196 369
        return false;
197
    }
198
199
    /**
200
     * Returns the version of a schema.
201
     *
202
     * @param stdClass $schema
203
     *
204
     * @return string
205
     */
206 369
    private function getVersion(stdClass $schema)
207
    {
208 369
        return property_exists($schema, '$schema') && is_string($schema->{'$schema'}) ?
209 369
            $schema->{'$schema'} :
210 369
            Registry::VERSION_CURRENT;
211
    }
212
213
    /**
214
     * Filters constraints which should be triggered for given schema.
215
     *
216
     * @param Constraint[] $constraints
217
     * @param stdClass     $schema
218
     *
219
     * @return Constraint[]
220
     */
221 363
    private function filterConstraintsForSchema(array $constraints, stdClass $schema)
222
    {
223 363
        $filtered = [];
224
225 363
        foreach ($constraints as $constraint) {
226 363
            foreach ($constraint->keywords() as $keyword) {
227 363
                if (property_exists($schema, $keyword)) {
228 337
                    $filtered[] = $constraint;
229 337
                    break;
230
                }
231 363
            }
232 363
        }
233
234 363
        return $filtered;
235
    }
236
}
237