Walker::doResolveReferences()   C
last analyzed

Complexity

Conditions 13
Paths 13

Size

Total Lines 42
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 13

Importance

Changes 0
Metric Value
dl 0
loc 42
c 0
b 0
f 0
ccs 32
cts 32
cp 1
rs 5.1234
cc 13
eloc 25
nc 13
nop 3
crap 13

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
use SplObjectStorage;
14
15
/**
16
 * Implements the three steps needed to perform a JSON Schema validation,
17
 * i.e. distinct methods to recursively:.
18
 *
19
 * 1) resolve JSON pointer references within schema
20
 * 2) normalize and validate the syntax of the schema
21
 * 3) validate a given instance against it
22
 */
23
class Walker
24
{
25
    /**
26
     * @var Registry
27
     */
28
    private $registry;
29
30
    /**
31
     * @var Resolver
32
     */
33
    private $resolver;
34
35
    /**
36
     * @var SplObjectStorage
37
     */
38
    private $parsedSchemas;
39
40
    /**
41
     * @var SplObjectStorage
42
     */
43
    private $resolvedSchemas;
44
45
    /**
46
     * @var Constraint[][]
47
     */
48
    private $constraintsCache = [];
49
50
    /**
51
     * Constructor.
52
     *
53
     * @param Registry $registry
54
     * @param Resolver $resolver
55
     */
56 494
    public function __construct(Registry $registry, Resolver $resolver)
57
    {
58 494
        $this->registry = $registry;
59 494
        $this->resolver = $resolver;
60 494
        $this->resolvedSchemas = new SplObjectStorage();
61 494
        $this->parsedSchemas = new SplObjectStorage();
62 494
    }
63
64
    /**
65
     * Recursively resolves JSON pointer references within a given schema.
66
     *
67
     * @param stdClass $schema The schema to resolve
68
     * @param Uri      $uri    The URI of the schema
69
     *
70
     * @return stdClass
71
     */
72 308
    public function resolveReferences(stdClass $schema, Uri $uri)
73
    {
74 308
        $this->resolver->initialize($schema, $uri);
75
76 308
        return $this->doResolveReferences($schema, $uri);
77
    }
78
79
    /**
80
     * @param stdClass $schema
81
     * @param Uri      $uri
82
     * @param bool     $inProperties
83
     *
84
     * @return stdClass
85
     */
86 308
    private function doResolveReferences(stdClass $schema, Uri $uri, $inProperties = false)
87
    {
88 308
        if ($this->isProcessed($schema, $this->resolvedSchemas)) {
89 16
            return $schema;
90
        }
91
92 308
        $inScope = false;
93
94 308
        if (property_exists($schema, 'id') && is_string($schema->id)) {
95 7
            $this->resolver->enter(new Uri($schema->id));
96 7
            $inScope = true;
97 7
        }
98
99 308
        if (property_exists($schema, '$ref')) {
100 25
            $resolved = $this->resolver->resolve($schema);
101 25
            $this->resolver->enter($resolved[0], $resolved[1]);
102 25
            $schema = $this->doResolveReferences($resolved[1], $resolved[0]);
103 25
            $this->resolver->leave();
104 25
        } else {
105 308
            $version = $this->getVersion($schema);
106
107 308
            foreach ($schema as $property => $value) {
108 308
                if ($inProperties || $this->registry->hasKeyword($version, $property)) {
109 306
                    if (is_object($value)) {
110 121
                        $schema->{$property} = $this->doResolveReferences($value, $uri, $property === 'properties');
111 306
                    } elseif (is_array($value)) {
112 69
                        foreach ($value as $index => $element) {
113 69
                            if (is_object($element)) {
114 51
                                $schema->{$property}[$index] = $this->doResolveReferences($element, $uri);
115 51
                            }
116 69
                        }
117 69
                    }
118 306
                }
119 308
            }
120
        }
121
122 308
        if ($inScope) {
123 7
            $this->resolver->leave();
124 7
        }
125
126 308
        return $schema;
127
    }
128
129
    /**
130
     * Recursively normalizes a given schema and validates its syntax.
131
     *
132
     * @param stdClass $schema
133
     * @param Context  $context
134
     *
135
     * @return stdClass
136
     */
137 366
    public function parseSchema(stdClass $schema, Context $context)
138
    {
139 366
        if ($this->isProcessed($schema, $this->parsedSchemas)) {
140 15
            return $schema;
141
        }
142
143 366
        $version = $this->getVersion($schema);
144 366
        $constraints = $this->registry->getConstraints($version);
145 366
        $constraints = $this->filterConstraintsForSchema($constraints, $schema);
146
147 366
        foreach ($constraints as $constraint) {
148 340
            $constraint->normalize($schema, $context, $this);
149 366
        }
150
151 366
        return $schema;
152
    }
153
154
    /**
155
     * Validates an instance against a given schema, populating a context
156
     * with encountered violations.
157
     *
158
     * @param mixed    $instance
159
     * @param stdClass $schema
160
     * @param Context  $context
161
     */
162 357
    public function applyConstraints($instance, stdClass $schema, Context $context)
163
    {
164 357
        $cacheKey = gettype($instance).spl_object_hash($schema);
165 357
        $constraints = & $this->constraintsCache[$cacheKey];
166
167 357
        if ($constraints === null) {
168 357
            $version = $this->getVersion($schema);
169 357
            $instanceType = Types::getPrimitiveTypeOf($instance);
170 357
            $constraints = $this->registry->getConstraintsForType($version, $instanceType);
171 357
            $constraints = $this->filterConstraintsForSchema($constraints, $schema);
172 357
        }
173
174 357
        foreach ($constraints as $constraint) {
175 314
            $constraint->apply($instance, $schema, $context, $this);
176 357
        }
177 357
    }
178
179
    /**
180
     * Returns whether a schema has already been processed and stored in
181
     * a given collection. This acts as an infinite recursion check.
182
     *
183
     * @param stdClass          $schema
184
     * @param SplObjectStorage  $storage
185
     *
186
     * @return bool
187
     */
188 372
    private function isProcessed(stdClass $schema, SplObjectStorage $storage)
189
    {
190 372
        if ($storage->contains($schema)) {
191 16
            return true;
192
        }
193
194 372
        $storage->attach($schema);
195
196 372
        return false;
197
    }
198
199
    /**
200
     * Returns the version of a schema.
201
     *
202
     * @param stdClass $schema
203
     *
204
     * @return string
205
     */
206 372
    private function getVersion(stdClass $schema)
207
    {
208 372
        return property_exists($schema, '$schema') && is_string($schema->{'$schema'}) ?
209 372
            $schema->{'$schema'} :
210 372
            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 366
    private function filterConstraintsForSchema(array $constraints, stdClass $schema)
222
    {
223 366
        $filtered = [];
224
225 366
        foreach ($constraints as $constraint) {
226 366
            foreach ($constraint->keywords() as $keyword) {
227 366
                if (property_exists($schema, $keyword)) {
228 340
                    $filtered[] = $constraint;
229 340
                    break;
230
                }
231 366
            }
232 366
        }
233
234 366
        return $filtered;
235
    }
236
}
237