swaggest/json-schema-maker

View on GitHub
src/InstanceFaker.php

Summary

Maintainability
D
3 days
Test Coverage
<?php


namespace Swaggest\JsonSchemaMaker;

use Swaggest\JsonSchema\Constraint\Format;
use Swaggest\JsonSchema\Schema;
use Swaggest\JsonSchema\Wrapper;

class InstanceFaker
{
    /** @var Schema */
    private $schema;

    /** @var Options */
    private $options;

    /** @var integer */
    private $nesting = 0;

    /** @var string */
    private $path = '';

    /**
     * JsonSchemaToInstance constructor.
     * @param Schema|bool $schema
     */
    public function __construct($schema, Options $options = null)
    {
        $schema = Schema::unboolSchema($schema);

        if ($options === null) {
            $options = new Options();
        }
        $this->options = $options;
        $this->schema = $schema;
    }

    public function makeValue()
    {
        if (isset($this->schema->{'example'})) {
            return $this->schema->{'example'};
        }

        if (!empty($this->schema->{'examples'}) && is_array($this->schema->{'examples'})) {
            return $this->schema->{'examples'}[mt_rand(0, count($this->schema->{'examples'}) - 1)];
        }

        if (is_array($this->schema->enum)) {
            return $this->schema->enum[mt_rand(0, count($this->schema->enum) - 1)];
        }

        if ($this->schema->const !== null) {
            return $this->schema->const;
        }

        if (isset($this->schema->default)) {
            return $this->schema->default;
        }

        if (isset($this->schema->properties) || isset($this->schema->additionalProperties)) {
            return $this->makeObject();
        }

        if (isset($this->schema->oneOf)) {
            $i = mt_rand(0, count($this->schema->oneOf) - 1);
            if ($instanceFaker = $this->deeper($this->schema->oneOf[$i], Schema::names()->oneOf . $i)) {
                $val = $instanceFaker->makeValue();
                return $val;
            }
        }

        if (isset($this->schema->allOf)) {
            foreach ($this->schema->allOf as $i => $schema) {
                if ($schema instanceof Wrapper) {
                    $schema = $schema->exportSchema();
                }
                if ($instanceFaker = $this->deeper($schema, Schema::names()->allOf . $i)) {
                    $val = $instanceFaker->makeValue();
                    if ($val !== null) {
                        return $val;
                    }
                }
            }
        }

        if (isset($this->schema->anyOf)) {
            foreach ($this->schema->anyOf as $i => $schema) {
                if ($schema instanceof Wrapper) {
                    $schema = $schema->exportSchema();
                }
                if ($instanceFaker = $this->deeper($schema, Schema::names()->anyOf . $i)) {
                    $val = $instanceFaker->makeValue();
                    if ($val !== null) {
                        return $val;
                    }
                }
            }
        }

        if ($this->schema->type === null) {
            return null;
        }

        $types = $this->schema->type;
        if (!is_array($types)) {
            $types = [$types];
        }

        if (in_array(Schema::INTEGER, $types)) {
            return round($this->makeNumber());
        }

        if (in_array(Schema::NUMBER, $types)) {
            return $this->makeNumber();
        }

        if (in_array(Schema::BOOLEAN, $types)) {
            return true;
        }

        if (in_array(Schema::STRING, $types)) {
            return $this->makeString();
        }

        if (in_array(Schema::_ARRAY, $types)) {
            return $this->makeArray();
        }

        if (in_array(Schema::OBJECT, $types)) {
            return $this->makeObject();
        }

        if (in_array(Schema::NULL, $types)) {
            return null;
        }

        return $this->makeAny();
    }

    private function makeAny()
    {
        switch (mt_rand(0, 3)) {
            case 0:
                return $this->makeNumber();
            case 1:
                return round($this->makeNumber());
            case 2:
                return $this->makeString();
            default:
                return null;
        }
    }

    private function makeNumber()
    {
        $min = 1;
        if ($this->schema->minimum !== null) {
            $min = $this->schema->minimum;
        }

        $max = 10000;
        if ($this->schema->maximum !== null) {
            $max = $this->schema->maximum;
        }

        return round($min + mt_rand() / mt_getrandmax() * ($max - $min), 3);
    }

    private function makeString()
    {
        if ($this->schema->format === Format::DATE_TIME) {
            return "2006-01-02T15:04:05Z";
        }

        if ($this->schema->format === Format::DATE) {
            return "2006-01-02";
        }

        if ($this->schema->format === 'uuid') {
            return '123e4567-e89b-12d3-a456-426655440000';
        }

        $length = mt_rand(2, 6);
        if ($this->schema->minLength !== null && $length < $this->schema->minLength) {
            $length = $this->schema->minLength;
        }

        if ($this->schema->maxLength !== null && $length > $this->schema->maxLength) {
            $length = $this->schema->maxLength;
        }

        $result = '';
        for ($i = 0; $i < $length; $i++) {
            $result .= 'abcdef'[mt_rand(0, 5)];
        }

        return $result;
    }

    private function makeArray()
    {
        $numItems = 1;
        if ($this->schema->minItems !== null && $numItems < $this->schema->minItems) {
            $numItems = $this->schema->minItems;
        }

        if ($this->schema->maxItems !== null && $numItems > $this->schema->maxItems) {
            $numItems = $this->schema->maxItems;
        }

        $result = [];
        if ($this->schema->items instanceof Schema) {
            if ($instanceFaker = $this->deeper($this->schema->items, Schema::names()->items)) {
                for ($i = 0; $i < $numItems; $i++) {
                    $result [$i] = $instanceFaker->makeValue();
                }
            }
        } elseif (is_array($this->schema->items)) {
            foreach ($this->schema->items as $i => $itemSchema) {
                if ($instanceFaker = $this->deeper($itemSchema, Schema::names()->items . $i)) {
                    $result[] = $instanceFaker->makeValue();
                }
            }
        } else {
            $result[] = $this->makeString();
        }

        return $result;
    }

    private function makeObject()
    {
        $result = new \stdClass();
        if ($this->schema->properties !== null) {
            foreach ($this->schema->getProperties() as $propertyName => $property) {
                if ($instanceFaker = $this->deeper($property, $propertyName)) {
                    $val = $instanceFaker->makeValue();
                    if ($val !== null) {
                        $result->$propertyName = $val;
                    }
                }
            }
        }

        if (!empty($this->schema->additionalProperties) ||
            ($this->options->defaultAdditionalProperties && !isset($this->schema->additionalProperties))) {

            if ($this->schema->additionalProperties instanceof Schema) {
                if ($instanceFaker = $this->deeper($this->schema->additionalProperties, Schema::names()->additionalProperties)) {
                    $result->{$this->makeString()} = $instanceFaker->makeValue();
                }

            } else {
                $result->{$this->makeString()} = (new self(true))->makeString();
            }
        }

        return $result;
    }

    private function deeper($schema, $pathItem)
    {
        if ($this->nesting > $this->options->maxNesting) {
            return false;
        }
        $schema = Schema::unboolSchema($schema);

        $hash = spl_object_hash($schema);

        $newPath = $this->path . '/' . $hash . ':' . $pathItem;

        for ($i = 1; $i < strlen($newPath) / 2; $i++) {
            $tail = substr($newPath, -$i);
            $check = substr($newPath, -2 * $i, $i);

            if (($tail === $check) && strpos($tail, '/') !== false) {
                return false;
            }
        }


        $instanceFaker = new self($schema, $this->options);
        $instanceFaker->nesting = $this->nesting + 1;
        $instanceFaker->path = $newPath;

        return $instanceFaker;
    }

}