GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Parameter::validateArray()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 1
crap 2
1
<?php declare(strict_types=1);
2
3
namespace OpenStack\Common\Api;
4
5
use OpenStack\Common\HydratorStrategyTrait;
6
7
/**
8
 * Represents an individual request parameter in a RESTful operation. A parameter can take on many forms:
9
 * in a URL path, in a URL query, in a JSON body, and in a HTTP header. It is worth documenting brifly each
10
 * variety of parameter:
11
 *
12
 * * Header parameters are those which populate a HTTP header in a request. Header parameters can have
13
 *   aliases; for example, a user-facing name of "Foo" can be sent over the wire as "X-Foo_Bar", as defined
14
 *   by ``sentAs``. Prefixes can also be used.
15
 *
16
 * * Query parameters are those which populate a URL query parameter. The value is therefore usually
17
 *   confined to a string.
18
 *
19
 * * JSON parameters are those which populate a JSON request body. These are the most complex variety
20
 *   of Parameter, since there are so many different ways a JSON document can be constructed. The SDK
21
 *   supports deep-nesting according to a XPath syntax; for more information, see {@see \OpenStack\Common\JsonPath}.
22
 *   Nested object and array properties are also supported since JSON is a recursive data type. What
23
 *   this means is that a Parameter can have an assortment of child Parameters, one for each object
24
 *   property or array element.
25
 *
26
 * * Raw parameters are those which populate a non-JSON request body. This is typically used for
27
 *   uploading payloads (such as Swift object data) to a remote API.
28
 *
29
 * * Path parameters are those which populate a URL path. They are serialized according to URL
30
 *   placeholders.
31
 *
32
 * @package OpenStack\Common\Api
33
 */
34
class Parameter
35
{
36
    use HydratorStrategyTrait;
37
38
    const DEFAULT_LOCATION = 'json';
39
40
    /**
41
     * The human-friendly name of the parameter. This is what the user will input.
42
     *
43
     * @var string
44
     */
45
    private $name = '';
46
47
    /**
48
     * The alias for this parameter. Although the user will always interact with the human-friendly $name property,
49
     * the $sentAs is what's used over the wire.
50
     *
51
     * @var string
52
     */
53
    private $sentAs = '';
54
55
    /**
56
     * For array parameters (for example, an array of security group names when creating a server), each array element
57
     * will need to adhere to a common schema. For the aforementioned example, each element will need to be a string.
58
     * For more complicated parameters, you might be validated an array of complicated objects.
59
     *
60
     * @var Parameter
61
     */
62
    private $itemSchema;
63
64
    /**
65
     * For object parameters, each property will need to adhere to a specific schema. For every property in the
66
     * object, it has its own schema - meaning that this property is a hash of name/schema pairs.
67
     *
68
     * The *only* exception to this rule is for metadata parameters, which are arbitrary key/value pairs. Since it does
69
     * not make sense to have a schema for each metadata key, a common schema is use for every one. So instead of this
70
     * property being a hash of schemas, it is a single Parameter object instead. This single Parameter schema will
71
     * then be applied to each metadata key provided.
72
     *
73
     * @var []Parameter|Parameter
74
     */
75
    private $properties;
76
77
    /**
78
     * The value's PHP type which this parameter represents; either "string", "bool", "object", "array", "NULL".
79
     *
80
     * @var string
81
     */
82
    private $type = '';
83
84
    /**
85
     * Indicates whether this parameter requires a value from the user.
86
     *
87
     * @var bool
88
     */
89
    private $required;
90
91
    /**
92
     * The location in the HTTP request where this parameter will populate; either "header", "url", "query", "raw" or
93
     * "json".
94
     *
95
     * @var string
96
     */
97
    private $location = '';
98
99
    /**
100
     * Relevant to "json" location parameters only. This property allows for deep nesting through the use of
101
     * {@see OpenStack\Common\JsonPath}.
102
     *
103
     * @var string
104
     */
105
    private $path = '';
106
107
    /**
108
     * Allows for the prefixing of parameter names.
109
     *
110
     * @var string
111
     */
112
    private $prefix = '';
113
114
    /**
115
     * The enum values for which this param is restricted.
116
     *
117
     * @var array
118
     */
119
    private $enum;
120
121
    /**
122
     * @param array $data
123
     */
124 220
    public function __construct(array $data)
125
    {
126 220
        $this->hydrate($data);
127
128 220
        $this->required = (bool)$this->required;
129 102
130 102
        $this->stockLocation($data);
131
        $this->stockItemSchema($data);
132 220
        $this->stockProperties($data);
133
    }
134 220
135 220
    private function stockLocation(array $data)
136 220
    {
137 220
        $this->location = isset($data['location']) ? $data['location'] : self::DEFAULT_LOCATION;
138
139 220
        if (!AbstractParams::isSupportedLocation($this->location)) {
140
            throw new \RuntimeException(sprintf("%s is not a permitted location", $this->location));
141 220
        }
142
    }
143 220
144 1
    private function stockItemSchema(array $data)
145
    {
146 220
        if (isset($data['items'])) {
147
            $this->itemSchema = new Parameter($data['items']);
148 220
        }
149
    }
150 220
151 29
    private function stockProperties(array $data)
152 29
    {
153 220
        if (isset($data['properties'])) {
154
            if ($this->name && stripos($this->name, 'metadata') !== false) {
155 220
                $this->properties = new Parameter($data['properties']);
156
            } else {
157 220
                foreach ($data['properties'] as $name => $property) {
158 42
                    $this->properties[$name] = new Parameter($property + ['name' => $name]);
159 22
                }
160 22
            }
161 30
        }
162 30
    }
163 30
164
    /**
165 42
     * Retrieve the name that will be used over the wire.
166 220
     *
167
     * @return string
168
     */
169
    public function getName(): string
170
    {
171
        return $this->sentAs ?: $this->name;
172
    }
173 97
174
    /**
175 97
     * Indicates whether the user must provide a value for this parameter.
176
     *
177
     * @return bool
178
     */
179
    public function isRequired(): bool
180
    {
181
        return $this->required === true;
182
    }
183 60
184
    /**
185 60
     * Validates a given user value and checks whether it passes basic sanity checking, such as types.
186
     *
187
     * @param $userValues The value provided by the user
188
     *
189
     * @return bool       TRUE if the validation passes
190
     * @throws \Exception If validation fails
191
     */
192
    public function validate($userValues): bool
193
    {
194
        $this->validateEnums($userValues);
195
        $this->validateType($userValues);
196 195
197
        if ($this->isArray()) {
198 195
            $this->validateArray($userValues);
199 194
        } elseif ($this->isObject()) {
200
            $this->validateObject($userValues);
201 193
        }
202 13
203 192
        return true;
204 23
    }
205 22
206
    private function validateEnums($userValues)
207 191
    {
208
        if (!empty($this->enum) && $this->type == 'string' && !in_array($userValues, $this->enum)) {
209
            throw new \Exception(sprintf(
210 195
                'The only permitted values are %s. You provided %s', implode(', ', $this->enum), print_r($userValues, true)
211
            ));
212 195
        }
213 1
    }
214 1
215 1
    private function validateType($userValues)
216
    {
217 194
        if (!$this->hasCorrectType($userValues)) {
218
            throw new \Exception(sprintf(
219 194
                'The key provided "%s" has the wrong value type. You provided %s (%s) but was expecting %s',
220
                $this->name, print_r($userValues, true), gettype($userValues), $this->type
221 194
            ));
222 3
        }
223 3
    }
224 3
225 3
    private function validateArray($userValues)
226
    {
227 193
        foreach ($userValues as $userValue) {
228
            $this->itemSchema->validate($userValue);
229 13
        }
230
    }
231 13
232 13
    private function validateObject($userValues)
233 12
    {
234 12
        foreach ($userValues as $key => $userValue) {
235
            $property = $this->getNestedProperty($key);
236 23
            $property->validate($userValue);
237
        }
238 23
    }
239 23
240 22
    /**
241 22
     * Internal method which retrieves a nested property for object parameters.
242 22
     *
243
     * @param string $key The name of the child parameter
244
     *
245
     * @returns Parameter
246
     * @throws \Exception
247
     */
248
    private function getNestedProperty($key): Parameter
249
    {
250
        if ($this->name && stripos($this->name, 'metadata') !== false && $this->properties instanceof Parameter) {
251
            return $this->properties;
252 23
        } elseif (isset($this->properties[$key])) {
253
            return $this->properties[$key];
254 23
        } else {
255 12
            throw new \Exception(sprintf('The key provided "%s" is not defined', $key));
256 12
        }
257 11
    }
258
259 1
    /**
260
     * Internal method which indicates whether the user value is of the same type as the one expected
261
     * by this parameter.
262
     *
263
     * @param $userValue The value being checked
264
     *
265
     * @return bool
266
     */
267
    private function hasCorrectType($userValue): bool
268
    {
269
        // Helper fn to see whether an array is associative (i.e. a JSON object)
270
        $isAssociative = function ($value) {
271
            return is_array($value) && array_keys($value) !== range(0, count($value) - 1);
272
        };
273
274 194
        // For params defined as objects, we'll let the user get away with
275 24
        // passing in an associative array - since it's effectively a hash
276 194
        if ($this->type == 'object' && $isAssociative($userValue)) {
277
            return true;
278
        }
279
280 194
        if (class_exists($this->type) || interface_exists($this->type)) {
281 23
            return is_a($userValue, $this->type);
282
        }
283
284 193
        if (!$this->type) {
285 2
            return true;
286
        }
287
288 193
        // allow string nulls
289
        if ($this->type == 'string' && $userValue === null) {
290
            return true;
291
        }
292
293
        return gettype($userValue) == $this->type;
294
    }
295
296 193
    /**
297
     * Indicates whether this parameter represents an array type
298 193
     *
299
     * @return bool
300
     */
301
    public function isArray(): bool
302
    {
303
        return $this->type == 'array' && $this->itemSchema instanceof Parameter;
304
    }
305
306 192
    /**
307
     * Indicates whether this parameter represents an object type
308 192
     *
309
     * @return bool
310
     */
311 185
    public function isObject(): bool
312
    {
313 185
        return $this->type == 'object' && !empty($this->properties);
314
    }
315
316
    public function getLocation(): string
317
    {
318
        return $this->location;
319
    }
320
321
    /**
322
     * Verifies whether the given location matches the parameter's location.
323 1
     *
324
     * @param $value
325 1
     *
326
     * @return bool
327
     */
328
    public function hasLocation($value): bool
329
    {
330
        return $this->location == $value;
331
    }
332
333 68
    /**
334
     * Retrieves the parameter's path.
335 68
     *
336
     * @return string|null
337
     */
338
    public function getPath(): string
339
    {
340
        return $this->path;
341
    }
342
343 20
    /**
344
     * Retrieves the common schema that an array parameter applies to all its child elements.
345 20
     *
346
     * @return Parameter|null
347
     */
348
    public function getItemSchema()
349
    {
350
        return $this->itemSchema;
351
    }
352
353 13
    /**
354
     * Sets the name of the parameter to a new value
355 13
     *
356 13
     * @param string $name
357
     */
358
    public function setName(string $name)
359
    {
360
        $this->name = $name;
361
    }
362
363
    /**
364
     * Retrieves the child parameter for an object parameter.
365 22
     *
366
     * @param string $name The name of the child property
367 22
     *
368 12
     * @return null|Parameter
369 12
     */
370
    public function getProperty(string $name)
371
    {
372 11
        if ($this->properties instanceof Parameter) {
373
            $this->properties->setName($name);
374
            return $this->properties;
375
        }
376
377
        return isset($this->properties[$name]) ? $this->properties[$name] : null;
378
    }
379
380 9
    /**
381
     * Retrieves the prefix for a parameter, if any.
382 9
     *
383
     * @return string|null
384
     */
385 16
    public function getPrefix(): string
386
    {
387 16
        return $this->prefix;
388
    }
389
390
    public function getPrefixedName(): string
391
    {
392
        return $this->prefix . $this->getName();
393
    }
394
}
395