Completed
Pull Request — master (#10)
by Jan
03:16 queued 53s
created

Walker::getConstraints()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 9.4285
cc 2
eloc 4
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 stdClass[]
36
     */
37
    private $parsedSchemas = [];
38
39
    /**
40
     * @var stdClass[]
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
        $constraints = $this->registry->getConstraints($context->getVersion());
139
140 357 View Code Duplication
        foreach ($constraints as $constraint) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
141 357
            foreach ($constraint->keywords() as $keyword) {
142 357
                if (property_exists($schema, $keyword)) {
143 333
                    $constraint->normalize($schema, $context, $this);
144 333
                    break;
145
                }
146 357
            }
147 357
        }
148
149 357
        return $schema;
150
    }
151
152
    /**
153
     * Validates an instance against a given schema, populating a context
154
     * with encountered violations.
155
     *
156
     * @param mixed    $instance
157
     * @param stdClass $schema
158
     * @param Context  $context
159
     */
160 348
    public function applyConstraints($instance, stdClass $schema, Context $context)
161
    {
162 348
        if (isset($schema->{'$schema'})) {
163 4
            $context->setVersion($schema->{'$schema'});
164 4
        }
165
166 348
        $cacheKey = gettype($instance).spl_object_hash($schema);
167 348
        $constraints = & $this->constraintsCache[$cacheKey];
168
169 348
        if ($constraints === null) {
170 348
            $version = $context->getVersion();
171 348
            $instanceType = Types::getPrimitiveTypeOf($instance);
172 348
            $constraintsForType = $this->registry->getConstraintsForType($version, $instanceType);
173
174 348
            $constraints = [];
175 348 View Code Duplication
            foreach ($constraintsForType as $constraint) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
176 348
                foreach ($constraint->keywords() as $keyword) {
177 348
                    if (property_exists($schema, $keyword)) {
178 307
                        $constraints[] = $constraint;
179 307
                        break;
180
                    }
181 348
                }
182 348
            }
183 348
        }
184
185 348
        foreach ($constraints as $constraint) {
186 307
            $constraint->apply($instance, $schema, $context, $this);
187 348
        }
188 348
    }
189
190
    /**
191
     * Checks if given schema has been already visited.
192
     *
193
     * @param stdClass $schema
194
     * @param array    $stack
195
     *
196
     * @return bool
197
     */
198 363
    private function isLooping(stdClass $schema, array &$stack)
199
    {
200 363
        $schemaHash = spl_object_hash($schema);
201 363
        if (isset($stack[$schemaHash])) {
202 20
            return true;
203
        }
204
205 363
        $stack[$schemaHash] = true;
206 363
        return false;
207
    }
208
}
209