All Downloads are FREE. Search and download functionalities are using the official Maven repository.

php-slim4-server.openapi_data_mocker.mustache Maven / Gradle / Ivy

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;
                $enum = $options['enum'] ?? null;
                return $this->mockString($dataFormat, $minLength, $maxLength, $enum);
            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
    ) {
        $dataFormat = is_string($dataFormat) ? strtolower($dataFormat) : $dataFormat;
        switch ($dataFormat) {
            case IMocker::DATA_FORMAT_INT32:
                // -2147483647..2147483647
                $minimum = is_numeric($minimum) ? max($minimum, -2147483647) : -2147483647;
                $maximum = is_numeric($maximum) ? min($maximum, 2147483647) : 2147483647;
                break;
            case IMocker::DATA_FORMAT_INT64:
                // -9223372036854775807..9223372036854775807
                $minimum = is_numeric($minimum) ? max($minimum, -9223372036854775807) : -9223372036854775807;
                $maximum = is_numeric($maximum) ? min($maximum, 9223372036854775807) : 9223372036854775807;
                break;
            default:
                // do nothing, unsupported format
        }

        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
    ) {
        $str = '';
        $getLoremIpsum = function ($length) {
            return str_pad(
                '',
                $length,
                'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ',
                \STR_PAD_RIGHT
            );
        };
        $truncateOrPad = function ($text, $min = null, $max = null, $glue = '') {
            if ($max !== null && mb_strlen($text) > $max) {
                // truncate
                $text = substr($text, 0, $max);
            } elseif ($min !== null && mb_strlen($text) < $min) {
                // pad
                $text = str_pad('', $min, $text . $glue, \STR_PAD_RIGHT);
            }
            return $text;
        };

        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"');
        }

        switch ($dataFormat) {
            case IMocker::DATA_FORMAT_BYTE:
            case IMocker::DATA_FORMAT_BINARY:
                // base64 encoded string
                $inputLength = 1;
                $str = base64_encode($getLoremIpsum($inputLength));
                while (mb_strlen($str) < $minLength) {
                    $inputLength++;
                    $str = base64_encode($getLoremIpsum($inputLength));
                }

                // base64 encoding produces strings devided by 4, so resulted string can exceed maxLength parameter
                // I think truncated(invalid) base64 string is better than oversized, cause this data is fake anyway
                $str = $truncateOrPad($str, null, $maxLength, '. ');
                break;
            case IMocker::DATA_FORMAT_DATE:
            case IMocker::DATA_FORMAT_DATE_TIME:
                // min unix timestamp is 0 and max is 2147483647 for 32bit systems which equals 2038-01-19 03:14:07
                $date = DateTime::createFromFormat('U', mt_rand(0, 2147483647));
                $str = ($dataFormat === IMocker::DATA_FORMAT_DATE) ? $date->format('Y-m-d') : $date->format('Y-m-d\TH:i:sP');

                // truncate or pad datestring to fit minLength and maxLength
                $str = $truncateOrPad($str, $minLength, $maxLength, ' ');
                break;
            case IMocker::DATA_FORMAT_PASSWORD:
                // use list of most popular passwords
                $obviousPassList = [
                    'qwerty',
                    'qwerty12345',
                    'hello',
                    '12345',
                    '0000',
                    'qwerty12345!',
                    'qwertyuiop[]',
                ];
                $str = $obviousPassList[mt_rand(0, count($obviousPassList) - 1)];

                // truncate or pad password to fit minLength and maxLength
                $str = $truncateOrPad($str, $minLength, $maxLength);
                break;
            case IMocker::DATA_FORMAT_UUID:
                // use php built-in uniqid function
                $str = uniqid();

                // truncate or pad password to fit minLength and maxLength
                $str = $truncateOrPad($str, $minLength, $maxLength);
                break;
            case IMocker::DATA_FORMAT_EMAIL:
                // just for visionary purpose, not related to real persons
                $fakeEmailList = [
                    'johndoe',
                    'lhoswald',
                    'ojsimpson',
                    'mlking',
                    'jfkennedy',
                ];
                $str = $fakeEmailList[mt_rand(0, count($fakeEmailList) - 1)] . '@example.com';

                // truncate or pad email to fit minLength and maxLength
                $str = $truncateOrPad($str, $minLength, $maxLength);
                break;
            default:
                $str = $getLoremIpsum(mt_rand($minLength, $maxLength));
        }

        return $str;
    }

    /**
     * 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;
        $ref = $options['$ref'] ?? null;

        // always generate smallest possible array to avoid huge JSON responses
        $arrSize = ($maxSize < 1) ? $maxSize : max($minSize, 1);
        while (count($arr) < $arrSize) {
            $data = $this->mockFromRef($ref);
            $arr[] = ($data) ? $data : $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['format'] ?? null;
            $ref = $options['$ref'] ?? null;
            $data = $this->mockFromRef($ref);
            $obj->$propName = ($data) ? $data : $this->mock($dataType, $dataFormat, $options);
        }

        return $obj;
    }

    /**
     * Mocks OpenApi Data from schema.
     *
     * @param array|object $schema OpenAPI schema
     *
     * @throws \InvalidArgumentException when invalid arguments passed
     *
     * @return mixed
     */
    public function mockFromSchema($schema)
    {
        $props = $this->extractSchemaProperties($schema);
        if (array_key_exists('$ref', $props) && !empty($props['$ref'])) {
            return $this->mockFromRef($props['$ref']);
        } elseif ($props['type'] === null) {
            throw new InvalidArgumentException('"schema" must be object or assoc array with "type" property');
        }
        return $this->mock($props['type'], $props['format'], $props);
    }

    /**
     * Mock data by referenced schema.
     * TODO: this method will return model instance, not an StdClass
     *
     * @param string|null $ref Ref to model, eg. #/components/schemas/User
     *
     * @return mixed
     */
    public function mockFromRef($ref)
    {
        $data = null;
        if (is_string($ref) && !empty($ref)) {
            $refName = static::getSimpleRef($ref);
            $modelName = static::toModelName($refName);
            $modelClass = '{{modelPackage}}\\' . $modelName;
            if (!class_exists($modelClass) || !method_exists($modelClass, 'getOpenApiSchema')) {
                throw new InvalidArgumentException(sprintf(
                    'Model %s not found or method %s doesn\'t exist',
                    $modelClass,
                    $modelClass . '::getOpenApiSchema'
                ));
            }
            $data = $this->mockFromSchema($modelClass::getOpenApiSchema(true));
        }

        return $data;
    }

    /**
     * @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',
                '$ref',
            ] 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((int) $min, (int) $max);
    }
}
{{/apiInfo}}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy