Completed
Branch develop (2b04dd)
by Mariano
03:37
created

Properties::validateList()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 2
Bugs 1 Features 1
Metric Value
c 2
b 1
f 1
dl 0
loc 13
ccs 0
cts 8
cp 0
rs 9.4285
cc 3
eloc 8
nc 3
nop 1
crap 12
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
     * (non-PHPdoc)
43
     * @see \Mcustiel\SimpleRequest\Validator\AbstractIterableValidator::setSpecification()
44
     */
45 74
    public function setSpecification($specification = null)
46
    {
47 74
        $this->checkSpecificationIsArray($specification);
48
49 74
        if (isset($specification[self::ITEMS_INDEX])) {
50 74
            $this->setItems($specification[self::ITEMS_INDEX]);
51 74
        }
52 74
        if (isset($specification[self::ADDITIONAL_ITEMS_INDEX])) {
53 74
            $this->setAdditionalItems($specification[self::ADDITIONAL_ITEMS_INDEX]);
54 74
        }
55 74
    }
56
57
    /**
58
     * (non-PHPdoc)
59
     * @see \Mcustiel\SimpleRequest\Validator\AbstractAnnotationSpecifiedValidator::validate()
60
     */
61 75
    public function validate($value)
62
    {
63 75
        if (!(is_array($value) || $value instanceof \stdClass)) {
64
            return false;
65
        }
66
67
        // From json-schema definition: if "items" is not present, or its value is an object,
68
        // validation of the instance always succeeds, regardless of the value of "additionalItems";
69 75
        if (empty($this->items)) {
70
            return true;
71
        }
72
73 75
        return $this->executePropertiesValidation($this->convertToArray($value));
74
    }
75
76 75
    private function executePropertiesValidation($value)
77
    {
78 75
        if ($this->items instanceof ValidatorInterface) {
79
            return $this->validateWithoutAdditionalItemsConcern($value);
80
        }
81
82
        // From json-schema definition: if the value of "additionalItems" is boolean value false and
83
        // the value of "items" is an array, the instance is valid if its size is less than, or
84
        // equal to, the size of "items".
85 75
        if ($this->additionalItems === false) {
86 75
            return (count($value) <= count($this->items))
87 75
            && $this->validateTuple($value);
88
        }
89
90
        // From json-schema definition: if the value of "additionalItems" is
91
        // boolean value true or an object, validation of the instance always succeeds;
92
        return $this->validateList($value);
93
    }
94
95 75
    private function convertToArray($value)
96
    {
97 75
        if (!is_array($value)) {
98
            return json_decode(json_encode($value), true);
99
        }
100 75
        return $value;
101
    }
102
103
104
    /**
105
     * Checks all properties against a validator.
106
     *
107
     * @param array $array
108
     *
109
     * @return bool
110
     */
111
    private function validateWithoutAdditionalItemsConcern(array $array)
112
    {
113
        foreach ($array as $value) {
114
            if (!$this->items->validate($value)) {
115
                return false;
116
            }
117
        }
118
119
        return true;
120
    }
121
122
    /**
123
     * Validates each element against its validator and if additionalItems is a
124
     * validator, validates the rest of the elements against it.
125
     *
126
     * @param array $list
127
     *
128
     * @return bool
129
     */
130
    private function validateList(array $list)
131
    {
132
        if (!$this->validateTuple($list)) {
133
            return false;
134
        }
135
        if ($this->additionalItems === true) {
136
            return true;
137
        }
138
139
        $keys = array_keys($this->items);
140
        $count = count($this->items);
141
        return $this->validateListItems(array_slice($keys, $count, count($list) - $count));
142
    }
143
144
    private function validateListItems($array)
145
    {
146
        foreach ($array as $item) {
147
            if (!$this->additionalItems->validate($item)) {
148
                return false;
149
            }
150
        }
151
        return true;
152
    }
153
154
    /**
155
     * Validate each element of the array against its corresponding validator.
156
     *
157
     * @param array $tuple
158
     *
159
     * @return bool
160
     */
161 74
    private function validateTuple(array $tuple)
162
    {
163 74
        foreach ($this->items as $property => $validator) {
0 ignored issues
show
Bug introduced by
The expression $this->items of type object<Mcustiel\SimpleRe...es\ValidatorInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
164 74
            if (!$validator->validate(isset($tuple[$property]) ? $tuple[$property] : null)) {
165 1
                return false;
166
            }
167 74
        }
168
169 73
        return true;
170
    }
171
172
    /**
173
     * Checks and sets items specification.
174
     *
175
     * @param array|\Mcustiel\SimpleRequest\Interfaces\ValidatorInterface $specification
176
     */
177 74
    private function setItems($specification)
178
    {
179 74
        if ($specification instanceof ValidatorAnnotation) {
180
            $this->items = $this->createValidatorInstanceFromAnnotation(
181
                $specification
182
            );
183 74
        } elseif (is_array($specification)) {
184 74
            foreach ($specification as $key => $item) {
185 74
                $this->items[$key] = $this->checkIfAnnotationAndReturnObject($item);
186 74
            }
187 74
        }
188 74
    }
189
190
    /**
191
     * Sets the specified additionalItems.
192
     *
193
     * @param bool|\Mcustiel\SimpleRequest\Interfaces\ValidatorInterface $specification
194
     */
195 74
    private function setAdditionalItems($specification)
196
    {
197 74
        if (is_bool($specification)) {
198 74
            $this->additionalItems = $specification;
199 74
        } elseif ($specification instanceof ValidatorAnnotation) {
200
            $this->additionalItems = $this->createValidatorInstanceFromAnnotation(
201
                $specification
202
            );
203
        }
204 74
    }
205
}
206