mockInteger($dataFormat, $minimum, $maximum, $exclusiveMinimum, $exclusiveMaximum); } return $this->mockNumber($dataFormat, $minimum, $maximum, $exclusiveMinimum, $exclusiveMaximum); case IMocker::DATA_TYPE_STRING: $minLength = $options['minLength'] ?? 0; $maxLength = $options['maxLength'] ?? null; return $this->mockString($dataFormat, $minLength, $maxLength); case IMocker::DATA_TYPE_BOOLEAN: return $this->mockBoolean(); case IMocker::DATA_TYPE_ARRAY: $items = $options['items'] ?? null; $minItems = $options['minItems'] ?? 0; $maxItems = $options['maxItems'] ?? null; $uniqueItems = $options['uniqueItems'] ?? false; return $this->mockArray($items, $minItems, $maxItems, $uniqueItems); case IMocker::DATA_TYPE_OBJECT: $properties = $options['properties'] ?? null; $minProperties = $options['minProperties'] ?? 0; $maxProperties = $options['maxProperties'] ?? null; $additionalProperties = $options['additionalProperties'] ?? null; $required = $options['required'] ?? null; return $this->mockObject($properties, $minProperties, $maxProperties, $additionalProperties, $required); default: throw new InvalidArgumentException('"dataType" must be one of ' . implode(', ', [ IMocker::DATA_TYPE_INTEGER, IMocker::DATA_TYPE_NUMBER, IMocker::DATA_TYPE_STRING, IMocker::DATA_TYPE_BOOLEAN, IMocker::DATA_TYPE_ARRAY, IMocker::DATA_TYPE_OBJECT, ])); } } /** * Shortcut to mock integer type * Equivalent to mockData(DATA_TYPE_INTEGER); * * @param string|null $dataFormat (optional) int32 or int64 * @param number|null $minimum (optional) Default is 0 * @param number|null $maximum (optional) Default is mt_getrandmax() * @param bool|null $exclusiveMinimum (optional) Default is false * @param bool|null $exclusiveMaximum (optional) Default is false * * @throws \InvalidArgumentException when $maximum less than $minimum or invalid arguments provided * * @return int */ public function mockInteger( $dataFormat = null, $minimum = null, $maximum = null, $exclusiveMinimum = false, $exclusiveMaximum = false ) { return $this->getRandomNumber($minimum, $maximum, $exclusiveMinimum, $exclusiveMaximum, 0); } /** * Shortcut to mock number type * Equivalent to mockData(DATA_TYPE_NUMBER); * * @param string|null $dataFormat (optional) float or double * @param number|null $minimum (optional) Default is 0 * @param number|null $maximum (optional) Default is mt_getrandmax() * @param bool|null $exclusiveMinimum (optional) Default is false * @param bool|null $exclusiveMaximum (optional) Default is false * * @throws \InvalidArgumentException when $maximum less than $minimum or invalid arguments provided * * @return float */ public function mockNumber( $dataFormat = null, $minimum = null, $maximum = null, $exclusiveMinimum = false, $exclusiveMaximum = false ) { return $this->getRandomNumber($minimum, $maximum, $exclusiveMinimum, $exclusiveMaximum, 4); } /** * Shortcut to mock string type * Equivalent to mockData(DATA_TYPE_STRING); * * @param string|null $dataFormat (optional) one of byte, binary, date, date-time, password * @param int|null $minLength (optional) Default is 0 * @param int|null $maxLength (optional) Default is 100 chars * @param array $enum (optional) This array should have at least one element. * Elements in the array should be unique. * @param string|null $pattern (optional) This string should be a valid regular expression, according to the ECMA 262 regular expression dialect. * Recall: regular expressions are not implicitly anchored. * * @throws \InvalidArgumentException when invalid arguments passed * * @return string */ public function mockString( $dataFormat = null, $minLength = 0, $maxLength = null, $enum = null, $pattern = null ) { if ($enum !== null) { if ( is_array($enum) === false || empty($enum) || count($enum) > count(array_unique($enum)) ) { throw new InvalidArgumentException('"enum" must be an array. This array should have at least one element. Elements in the array should be unique.'); } // return random variant return $enum[mt_rand(0, count($enum) - 1)]; } if ($minLength !== 0 && $minLength !== null) { if (is_int($minLength) === false) { throw new InvalidArgumentException('"minLength" must be an integer'); } elseif ($minLength < 0) { throw new InvalidArgumentException('"minLength" must be greater than, or equal to, 0'); } } else { $minLength = 0; } if ($maxLength !== null) { if (is_int($maxLength) === false) { throw new InvalidArgumentException('"maxLength" must be an integer'); } elseif ($maxLength < 0) { throw new InvalidArgumentException('"maxLength" must be greater than, or equal to, 0'); } } else { // since we don't need huge texts by default, lets cut them down to 100 chars $maxLength = 100; } if ($maxLength < $minLength) { throw new InvalidArgumentException('"maxLength" value cannot be less than "minLength"'); } return str_pad('', mt_rand($minLength, $maxLength), 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ', \STR_PAD_RIGHT); } /** * Shortcut to mock boolean type * Equivalent to mockData(DATA_TYPE_BOOLEAN); * * @return bool */ public function mockBoolean() { return (bool) mt_rand(0, 1); } /** * Shortcut to mock array type * Equivalent to mockData(DATA_TYPE_ARRAY); * * @param object|array $items Object or assoc array of described items * @param int|null $minItems (optional) An array instance is valid against "minItems" if its size is greater than, or equal to, the value of this keyword. * @param int|null $maxItems (optional) An array instance is valid against "maxItems" if its size is less than, or equal to, the value of this keyword * @param bool|null $uniqueItems (optional) If it has boolean value true, the instance validates successfully if all of its elements are unique * * @throws \InvalidArgumentException when invalid arguments passed * * @return array */ public function mockArray( $items, $minItems = 0, $maxItems = null, $uniqueItems = false ) { $arr = []; $minSize = 0; $maxSize = \PHP_INT_MAX; if ( (is_array($items) === false && is_object($items) === false) || (is_array($items) && array_key_exists('type', $items) === false) || (is_object($items) && isset($items->type) === false) ) { new InvalidArgumentException('"items" must be object or assoc array with "type" key'); } if ($minItems !== null) { if (is_integer($minItems) === false || $minItems < 0) { throw new InvalidArgumentException('"mitItems" must be an integer. This integer must be greater than, or equal to, 0'); } $minSize = $minItems; } if ($maxItems !== null) { if (is_integer($maxItems) === false || $maxItems < 0) { throw new InvalidArgumentException('"maxItems" must be an integer. This integer must be greater than, or equal to, 0.'); } if ($maxItems < $minItems) { throw new InvalidArgumentException('"maxItems" value cannot be less than "minItems"'); } $maxSize = $maxItems; } $options = $this->extractSchemaProperties($items); $dataType = $options['type']; $dataFormat = $options['format'] ?? null; // always genarate smallest possible array to avoid huge JSON responses $arrSize = ($maxSize < 1) ? $maxSize : max($minSize, 1); while (count($arr) < $arrSize) { $arr[] = $this->mock($dataType, $dataFormat, $options); } return $arr; } /** * Shortcut to mock object type. * Equivalent to mockData(DATA_TYPE_OBJECT); * * @param object|array $properties Object or array of described properties * @param int|null $minProperties (optional) An object instance is valid against "minProperties" if its number of properties is greater than, or equal to, the value of this keyword. * @param int|null $maxProperties (optional) An object instance is valid against "maxProperties" if its number of properties is less than, or equal to, the value of this keyword. * @param bool|object|array|null $additionalProperties (optional) If "additionalProperties" is true, validation always succeeds. * If "additionalProperties" is false, validation succeeds only if the instance is an object and all properties on the instance were covered by "properties" and/or "patternProperties". * If "additionalProperties" is an object, validate the value as a schema to all of the properties that weren't validated by "properties" nor "patternProperties". * @param array|null $required (optional) This array MUST have at least one element. Elements of this array must be strings, and MUST be unique. * An object instance is valid if its property set contains all elements in this array value. * * @throws \InvalidArgumentException when invalid arguments passed * * @return object */ public function mockObject( $properties, $minProperties = 0, $maxProperties = null, $additionalProperties = null, $required = null ) { $obj = new StdClass(); if (is_object($properties) === false && is_array($properties) === false) { throw new InvalidArgumentException('The value of "properties" must be an array or object'); } foreach ($properties as $propName => $propValue) { if (is_object($propValue) === false && is_array($propValue) === false) { throw new InvalidArgumentException('Each value of "properties" must be an array or object'); } } if ($minProperties !== null) { if (is_integer($minProperties) === false || $minProperties < 0) { throw new InvalidArgumentException('"minProperties" must be an integer. This integer must be greater than, or equal to, 0'); } } if ($maxProperties !== null) { if (is_integer($maxProperties) === false || $maxProperties < 0) { throw new InvalidArgumentException('"maxProperties" must be an integer. This integer must be greater than, or equal to, 0.'); } if ($maxProperties < $minProperties) { throw new InvalidArgumentException('"maxProperties" value cannot be less than "minProperties"'); } } if ($additionalProperties !== null) { if (is_bool($additionalProperties) === false && is_object($additionalProperties) === false && is_array($additionalProperties) === false) { throw new InvalidArgumentException('The value of "additionalProperties" must be a boolean or object or array.'); } } if ($required !== null) { if ( is_array($required) === false || count($required) > count(array_unique($required)) ) { throw new InvalidArgumentException('The value of "required" must be an array. Elements of this array must be unique.'); } foreach ($required as $requiredPropName) { if (is_string($requiredPropName) === false) { throw new InvalidArgumentException('Elements of "required" array must be strings'); } } } foreach ($properties as $propName => $propValue) { $options = $this->extractSchemaProperties($propValue); $dataType = $options['type']; $dataFormat = $options['dataFormat'] ?? null; $obj->$propName = $this->mock($dataType, $dataFormat, $options); } return $obj; } /** * @internal Extract OAS properties from array or object. * @codeCoverageIgnore * * @param array|object $val Processed array or object * * @return array */ private function extractSchemaProperties($val) { $props = [ 'type' => null, 'format' => null, ]; foreach ( [ 'type', 'format', 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'minLength', 'maxLength', 'pattern', 'enum', 'items', 'minItems', 'maxItems', 'uniqueItems', 'properties', 'minProperties', 'maxProperties', 'additionalProperties', 'required', 'example', ] as $propName ) { if (is_array($val) && array_key_exists($propName, $val)) { $props[$propName] = $val[$propName]; } elseif (is_object($val) && isset($val->$propName)) { $props[$propName] = $val->$propName; } } return $props; } /** * @internal * @codeCoverageIgnore * * @return float|int */ protected function getRandomNumber( $minimum = null, $maximum = null, $exclusiveMinimum = false, $exclusiveMaximum = false, $maxDecimals = 4 ) { $min = 0; $max = mt_getrandmax(); if ($minimum !== null) { if (is_numeric($minimum) === false) { throw new InvalidArgumentException('"minimum" must be a number'); } $min = $minimum; } if ($maximum !== null) { if (is_numeric($maximum) === false) { throw new InvalidArgumentException('"maximum" must be a number'); } $max = $maximum; } if ($exclusiveMinimum !== false) { if (is_bool($exclusiveMinimum) === false) { throw new InvalidArgumentException('"exclusiveMinimum" must be a boolean'); } elseif ($minimum === null) { throw new InvalidArgumentException('If "exclusiveMinimum" is present, "minimum" must also be present'); } $min += 1; } if ($exclusiveMaximum !== false) { if (is_bool($exclusiveMaximum) === false) { throw new InvalidArgumentException('"exclusiveMaximum" must be a boolean'); } elseif ($maximum === null) { throw new InvalidArgumentException('If "exclusiveMaximum" is present, "maximum" must also be present'); } $max -= 1; } if ($max < $min) { throw new InvalidArgumentException('"maximum" value cannot be less than "minimum"'); } if ($maxDecimals > 0) { return round($min + mt_rand() / mt_getrandmax() * ($max - $min), $maxDecimals); } return mt_rand($min, $max); } }