hydrate($data); $this->required = (bool) $this->required; $this->stockLocation($data); $this->stockItemSchema($data); $this->stockProperties($data); } private function stockLocation(array $data) { $this->location = isset($data['location']) ? $data['location'] : self::DEFAULT_LOCATION; if (!AbstractParams::isSupportedLocation($this->location)) { throw new \RuntimeException(sprintf('%s is not a permitted location', $this->location)); } } private function stockItemSchema(array $data) { if (isset($data['items'])) { $this->itemSchema = new Parameter($data['items']); } } private function stockProperties(array $data) { if (isset($data['properties'])) { if ($this->name && false !== stripos($this->name, 'metadata')) { $this->properties = new Parameter($data['properties']); } else { foreach ($data['properties'] as $name => $property) { $this->properties[$name] = new Parameter($property + ['name' => $name]); } } } } /** * Retrieve the name that will be used over the wire. */ public function getName(): string { return $this->sentAs ?: $this->name; } /** * Indicates whether the user must provide a value for this parameter. */ public function isRequired(): bool { return true === $this->required; } /** * Validates a given user value and checks whether it passes basic sanity checking, such as types. * * @param $userValues The value provided by the user * * @return bool TRUE if the validation passes * * @throws \Exception If validation fails */ public function validate($userValues): bool { $this->validateEnums($userValues); $this->validateType($userValues); if ($this->isArray()) { $this->validateArray($userValues); } elseif ($this->isObject()) { $this->validateObject($userValues); } return true; } private function validateEnums($userValues) { if (!empty($this->enum) && 'string' == $this->type && !in_array($userValues, $this->enum)) { throw new \Exception(sprintf('The only permitted values are %s. You provided %s', implode(', ', $this->enum), print_r($userValues, true))); } } private function validateType($userValues) { if (!$this->hasCorrectType($userValues)) { throw new \Exception(sprintf('The key provided "%s" has the wrong value type. You provided %s (%s) but was expecting %s', $this->name, print_r($userValues, true), gettype($userValues), $this->type)); } } private function validateArray($userValues) { foreach ($userValues as $userValue) { $this->itemSchema->validate($userValue); } } private function validateObject($userValues) { foreach ($userValues as $key => $userValue) { $property = $this->getNestedProperty($key); $property->validate($userValue); } } /** * Internal method which retrieves a nested property for object parameters. * * @param string $key The name of the child parameter * * @returns Parameter * * @throws \Exception */ private function getNestedProperty($key): Parameter { if ($this->name && false !== stripos($this->name, 'metadata') && $this->properties instanceof Parameter) { return $this->properties; } elseif (isset($this->properties[$key])) { return $this->properties[$key]; } else { throw new \Exception(sprintf('The key provided "%s" is not defined', $key)); } } /** * Internal method which indicates whether the user value is of the same type as the one expected * by this parameter. * * @param $userValue The value being checked */ private function hasCorrectType($userValue): bool { // Helper fn to see whether an array is associative (i.e. a JSON object) $isAssociative = function ($value) { return is_array($value) && array_keys($value) !== range(0, count($value) - 1); }; // For params defined as objects, we'll let the user get away with // passing in an associative array - since it's effectively a hash if ('object' == $this->type && $isAssociative($userValue)) { return true; } if (class_exists($this->type) || interface_exists($this->type)) { return is_a($userValue, $this->type); } if (!$this->type) { return true; } // allow string nulls if ('string' == $this->type && null === $userValue) { return true; } return gettype($userValue) == $this->type; } /** * Indicates whether this parameter represents an array type. */ public function isArray(): bool { return 'array' == $this->type && $this->itemSchema instanceof Parameter; } /** * Indicates whether this parameter represents an object type. */ public function isObject(): bool { return 'object' == $this->type && !empty($this->properties); } public function getLocation(): string { return $this->location; } /** * Verifies whether the given location matches the parameter's location. * * @param $value */ public function hasLocation($value): bool { return $this->location == $value; } /** * Retrieves the parameter's path. * * @return string|null */ public function getPath(): string { return $this->path; } /** * Retrieves the common schema that an array parameter applies to all its child elements. * * @return Parameter|null */ public function getItemSchema() { return $this->itemSchema; } /** * Sets the name of the parameter to a new value. */ public function setName(string $name) { $this->name = $name; } /** * Retrieves the child parameter for an object parameter. * * @param string $name The name of the child property * * @return Parameter|null */ public function getProperty(string $name) { if ($this->properties instanceof Parameter) { $this->properties->setName($name); return $this->properties; } return isset($this->properties[$name]) ? $this->properties[$name] : null; } /** * Retrieves the prefix for a parameter, if any. * * @return string|null */ public function getPrefix(): string { return $this->prefix; } public function getPrefixedName(): string { return $this->prefix.$this->getName(); } }