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

Walker::applyConstraints()   D

Complexity

Conditions 10
Paths 12

Size

Total Lines 39
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 10

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 39
ccs 32
cts 32
cp 1
rs 4.8196
cc 10
eloc 23
nc 12
nop 3
crap 10

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
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