Completed
Branch develop (1b1ec3)
by Mariano
03:37
created

Properties::validateTuple()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 1
Bugs 1 Features 1
Metric Value
c 1
b 1
f 1
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 9.2
cc 4
eloc 5
nc 3
nop 1
crap 4
1
<?php
2
/**
3
 * This file is part of php-simple-request.
4
 *
5
 * php-simple-request is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU Lesser General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * php-simple-request is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with php-simple-request.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18
namespace Mcustiel\SimpleRequest\Validator;
19
20
use Mcustiel\SimpleRequest\Interfaces\ValidatorInterface;
21
use Mcustiel\SimpleRequest\Annotation\ValidatorAnnotation;
22
23
/**
24
 * Checks that each element of an object or array validates against its corresponding
25
 * validator in a collection, using the name of the property or key.
26
 * <a href="http://spacetelescope.github.io/understanding-json-schema/UnderstandingJSONSchema.pdf">Here</a>
27
 * you can see examples of use for this validator.
28
 *
29
 * @author mcustiel
30
 */
31
class Properties extends AbstractIterableValidator
32
{
33
    const ITEMS_INDEX = 'properties';
34
    const ADDITIONAL_ITEMS_INDEX = 'additionalProperties';
35
36
    /**
37
     * @var bool|\Mcustiel\SimpleRequest\Interfaces\ValidatorInterface
38
     */
39
    private $additionalItems = true;
40
41
    /**
42
     * {@inheritdoc}
43
     *
44
     * @see \Mcustiel\SimpleRequest\Validator\AbstractIterableValidator::setSpecification()
45
     */
46 79
    public function setSpecification($specification = null)
47
    {
48 79
        $this->checkSpecificationIsArray($specification);
49
50 79
        if (isset($specification[self::ITEMS_INDEX])) {
51 79
            $this->setItems($specification[self::ITEMS_INDEX]);
52 79
        }
53 79
        if (isset($specification[self::ADDITIONAL_ITEMS_INDEX])) {
54 79
            $this->setAdditionalItems($specification[self::ADDITIONAL_ITEMS_INDEX]);
55 79
        }
56 79
    }
57
58
    /**
59
     * {@inheritdoc}
60
     *
61
     * @see \Mcustiel\SimpleRequest\Validator\AbstractAnnotationSpecifiedValidator::validate()
62
     */
63 80
    public function validate($value)
64
    {
65 80
        if (!(is_array($value) || $value instanceof \stdClass)) {
66
            return false;
67
        }
68
69
        // From json-schema definition: if "items" is not present, or its value is an object,
70
        // validation of the instance always succeeds, regardless of the value of "additionalItems";
71 80
        if (empty($this->items)) {
72
            return true;
73
        }
74
75 80
        return $this->executePropertiesValidation($this->convertToArray($value));
76
    }
77
78 80
    private function executePropertiesValidation($value)
79
    {
80 80
        if ($this->items instanceof ValidatorInterface) {
81
            return $this->validateWithoutAdditionalItemsConcern($value);
82
        }
83
84
        // From json-schema definition: if the value of "additionalItems" is boolean value false and
85
        // the value of "items" is an array, the instance is valid if its size is less than, or
86
        // equal to, the size of "items".
87 80
        if ($this->additionalItems === false) {
88 80
            return (count($value) <= count($this->items))
89 80
            && $this->validateTuple($value);
90
        }
91
92
        // From json-schema definition: if the value of "additionalItems" is
93
        // boolean value true or an object, validation of the instance always succeeds;
94
        return $this->validateList($value);
95
    }
96
97 80
    private function convertToArray($value)
98
    {
99 80
        if (!is_array($value)) {
100
            return json_decode(json_encode($value), true);
101
        }
102 80
        return $value;
103
    }
104
105
106
    /**
107
     * Checks all properties against a validator.
108
     *
109
     * @param array $array
110
     *
111
     * @return bool
112
     */
113
    private function validateWithoutAdditionalItemsConcern(array $array)
114
    {
115
        foreach ($array as $value) {
116
            if (!$this->items->validate($value)) {
0 ignored issues
show
Bug introduced by
The method validate cannot be called on $this->items (of type array<integer,object<Mcu...es\ValidatorInterface>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
117
                return false;
118
            }
119
        }
120
121
        return true;
122
    }
123
124
    /**
125
     * Validates each element against its validator and if additionalItems is a
126
     * validator, validates the rest of the elements against it.
127
     *
128
     * @param array $list
129
     *
130
     * @return bool
131
     */
132
    private function validateList(array $list)
133
    {
134
        if (!$this->validateTuple($list)) {
135
            return false;
136
        }
137
        if ($this->additionalItems === true) {
138
            return true;
139
        }
140
141
        $keys = array_keys($this->items);
142
        $count = count($this->items);
143
        return $this->validateListItems(array_slice($keys, $count, count($list) - $count));
144
    }
145
146
    private function validateListItems($array)
147
    {
148
        foreach ($array as $item) {
149
            if (!$this->additionalItems->validate($item)) {
150
                return false;
151
            }
152
        }
153
        return true;
154
    }
155
156
    /**
157
     * Validate each element of the array against its corresponding validator.
158
     *
159
     * @param array $tuple
160
     *
161
     * @return bool
162
     */
163 79
    private function validateTuple(array $tuple)
164
    {
165 79
        foreach ($this->items as $property => $validator) {
166 79
            if (!$validator->validate(isset($tuple[$property]) ? $tuple[$property] : null)) {
167 1
                return false;
168
            }
169 79
        }
170
171 78
        return true;
172
    }
173
174
    /**
175
     * Checks and sets items specification.
176
     *
177
     * @param array|\Mcustiel\SimpleRequest\Interfaces\ValidatorInterface $specification
178
     */
179 79
    private function setItems($specification)
180
    {
181 79
        if ($specification instanceof ValidatorAnnotation) {
182
            $this->items = $this->createValidatorInstanceFromAnnotation(
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->createValidatorIn...otation($specification) of type object<Mcustiel\SimpleRe...ces\ValidatorInterface> is incompatible with the declared type array<integer,object<Mcu...es\ValidatorInterface>> of property $items.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
183
                $specification
184
            );
185 79
        } elseif (is_array($specification)) {
186 79
            foreach ($specification as $key => $item) {
187 79
                $this->items[$key] = $this->checkIfAnnotationAndReturnObject($item);
188 79
            }
189 79
        }
190 79
    }
191
192
    /**
193
     * Sets the specified additionalItems.
194
     *
195
     * @param bool|\Mcustiel\SimpleRequest\Interfaces\ValidatorInterface $specification
196
     */
197 79
    private function setAdditionalItems($specification)
198
    {
199 79
        if (is_bool($specification)) {
200 79
            $this->additionalItems = $specification;
201 79
        } elseif ($specification instanceof ValidatorAnnotation) {
202
            $this->additionalItems = $this->createValidatorInstanceFromAnnotation(
203
                $specification
204
            );
205
        }
206 79
    }
207
}
208