├── test ├── fixture │ ├── null.json │ ├── boolean.json │ ├── combining.json │ ├── format.json │ ├── array.json │ ├── integer.json │ ├── number.json │ ├── string.json │ └── object.json ├── TestCase.php ├── FakerTest.php └── HelperTest.php ├── .gitignore ├── circle.yml ├── README.md ├── composer.json └── src ├── helper.php └── Faker.php /test/fixture/null.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "null" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | clover.xml 4 | -------------------------------------------------------------------------------- /test/fixture/boolean.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "boolean" 3 | } -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | php: 3 | version: 7.0.4 4 | 5 | dependencies: 6 | cache_directories: 7 | - ~/.composer/cache 8 | override: 9 | - ./composer.phar update 10 | post: 11 | - sed -i 's/^;//' ~/.phpenv/versions/$(phpenv global)/etc/conf.d/xdebug.ini 12 | 13 | test: 14 | override: 15 | - ./composer.phar run lint 16 | - ./composer.phar run test 17 | post: 18 | - bash <(curl -s https://codecov.io/bash) 19 | -------------------------------------------------------------------------------- /test/TestCase.php: -------------------------------------------------------------------------------- 1 | setAccessible(true); 16 | 17 | return $ref->invokeArgs($instance, $args); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/fixture/combining.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "allOf", 5 | "anyOf", 6 | "oneOf" 7 | ], 8 | "properties": { 9 | "allOf": { 10 | "allOf": [ 11 | { "type": "object" }, 12 | { "minProperties": 3 } 13 | ] 14 | }, 15 | "anyOf": { 16 | "anyOf": [ 17 | { "type": "object", "maxProperties": 3 }, 18 | { "type": "string", "maxLength": 103 }, 19 | { "type": "integer", "maximum": 137 } 20 | ] 21 | }, 22 | "oneOf": { 23 | "oneOf": [ 24 | { "type": "array", "items": { "type": "null" } }, 25 | { "type": "string", "minLength": 103 }, 26 | { "type": "boolean" } 27 | ] 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /test/fixture/format.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "date-time", 5 | "email", 6 | "hostname", 7 | "ipv4", 8 | "ipv6", 9 | "uri" 10 | ], 11 | "properties": { 12 | "date-time": { 13 | "type": "string", 14 | "format": "date-time" 15 | }, 16 | "email": { 17 | "type": "string", 18 | "format": "email" 19 | }, 20 | "hostname": { 21 | "type": "string", 22 | "format": "hostname" 23 | }, 24 | "ipv4": { 25 | "type": "string", 26 | "format": "ipv4" 27 | }, 28 | "ipv6": { 29 | "type": "string", 30 | "format": "ipv6" 31 | }, 32 | "uri": { 33 | "type": "string", 34 | "format": "uri" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/fixture/array.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "typeOnly", 5 | "typeAndMinItems", 6 | "typeAndMaxItems", 7 | "typeAndMinItems", 8 | "typeAndMultipleItems" 9 | ], 10 | "properties": { 11 | "typeOnly": { 12 | "type": "array" 13 | }, 14 | "typeAndMinItems": { 15 | "type": "array", 16 | "minItems": 10 17 | }, 18 | "typeAndMaxItems": { 19 | "type": "array", 20 | "maxItems": 10 21 | }, 22 | "typeAndMinItems": { 23 | "type": "array", 24 | "minItems": 10, 25 | "maxItems": 10 26 | }, 27 | "typeAndMultipleItems": { 28 | "type": "array", 29 | "items": [{ 30 | "type": "integer" 31 | }, { 32 | "type": "string" 33 | }] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notice 2 | 3 | This package is no longer maintained. 4 | Please use [koriym/php-json-schema-faker](https://github.com/koriym/php-json-schema-faker) instead. 5 | 6 | # PHP JSON Schema Faker 7 | 8 | [![CircleCI](https://circleci.com/gh/Leko/php-json-schema-faker.svg?style=svg)](https://circleci.com/gh/Leko/php-json-schema-faker) 9 | [![codecov](https://codecov.io/gh/Leko/php-json-schema-faker/branch/master/graph/badge.svg)](https://codecov.io/gh/Leko/php-json-schema-faker) 10 | 11 | Create dummy data with JSON schema. 12 | Inspire from [json-schema-faker](https://github.com/json-schema-faker/json-schema-faker) 13 | 14 | ## Getting started 15 | 16 | ```bash 17 | composer require leko/json-schema-faker 18 | ``` 19 | 20 | ```php 21 | getFixture($type); 16 | $validator = new Validator(); 17 | 18 | $actual = Faker::fake($schema); 19 | $validator->check($actual, $schema); 20 | 21 | $this->assertTrue($validator->isValid(), json_encode($validator->getErrors(), JSON_PRETTY_PRINT)); 22 | } 23 | 24 | public function getTypes() 25 | { 26 | return [ 27 | ['boolean'], 28 | ['null'], 29 | ['integer'], 30 | ['number'], 31 | ['string'], 32 | ['array'], 33 | ['object'], 34 | ['combining'] 35 | ]; 36 | } 37 | 38 | /** 39 | * @expectedException Exception 40 | */ 41 | public function testFakeMustThrowExceptionIfInvalidType() 42 | { 43 | Faker::fake((object)['type' => 'xxxxx']); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/fixture/integer.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "typeOnly", 5 | "typeAndEnum", 6 | "typeAndMinimum", 7 | "typeAndExcludeMinimum", 8 | "typeAndMaximum", 9 | "typeAndExcludeMinimum", 10 | "typeAndMinimumAndMaximum", 11 | "typeAndMultipleOf" 12 | ], 13 | "properties": { 14 | "typeOnly": { 15 | "type": "integer" 16 | }, 17 | "typeAndEnum": { 18 | "type": "integer", 19 | "enum": [-100, 391, 2, 0] 20 | }, 21 | "typeAndMinimum": { 22 | "type": "integer", 23 | "minimum": -1 24 | }, 25 | "typeAndExcludeMinimum": { 26 | "type": "integer", 27 | "minimum": -1, 28 | "exclusiveMinimum": true 29 | }, 30 | "typeAndMaximum": { 31 | "type": "integer", 32 | "maximum": 3 33 | }, 34 | "typeAndExcludeMinimum": { 35 | "type": "integer", 36 | "maximum": 3, 37 | "excludeMinimum": true 38 | }, 39 | "typeAndMinimumAndMaximum": { 40 | "type": "integer", 41 | "minimum": -3010210, 42 | "maximum": 312 43 | }, 44 | "typeAndMultipleOf": { 45 | "type": "integer", 46 | "multipleOf": 3 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/fixture/number.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "typeOnly", 5 | "typeAndEnum", 6 | "typeAndMinimum", 7 | "typeAndExcludeMinimum", 8 | "typeAndMaximum", 9 | "typeAndExcludeMinimum", 10 | "typeAndMinimumAndMaximum", 11 | "typeAndMultipleOf" 12 | ], 13 | "properties": { 14 | "typeOnly": { 15 | "type": "number" 16 | }, 17 | "typeAndEnum": { 18 | "type": "number", 19 | "enum": [-10.0, 3.91, 2.1, -0.1] 20 | }, 21 | "typeAndMinimum": { 22 | "type": "number", 23 | "minimum": -1 24 | }, 25 | "typeAndExcludeMinimum": { 26 | "type": "number", 27 | "minimum": -1, 28 | "exclusiveMinimum": true 29 | }, 30 | "typeAndMaximum": { 31 | "type": "number", 32 | "maximum": 3 33 | }, 34 | "typeAndExcludeMinimum": { 35 | "type": "number", 36 | "maximum": 3, 37 | "excludeMinimum": true 38 | }, 39 | "typeAndMinimumAndMaximum": { 40 | "type": "number", 41 | "minimum": -301.0210, 42 | "maximum": 312 43 | }, 44 | "typeAndMultipleOf": { 45 | "type": "number", 46 | "multipleOf": 3 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leko/json-schema-faker", 3 | "description": "Create dummy data with JSON schema", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Leko", 9 | "email": "leko.noor@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "fzaninotto/faker": "^1.7" 14 | }, 15 | "require-dev": { 16 | "justinrainbow/json-schema": "~5.2.7", 17 | "phpmd/phpmd": "^2.4", 18 | "squizlabs/php_codesniffer": "^2.6", 19 | "phpunit/phpunit": "^5.4" 20 | }, 21 | "autoload": { 22 | "files": [ 23 | "./src/helper.php" 24 | ], 25 | "psr-4": { 26 | "JSONSchemaFaker\\": "src/" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "JSONSchemaFaker\\Test\\": "test/" 32 | } 33 | }, 34 | "scripts": { 35 | "test": [ 36 | "phpunit test/ --coverage-clover=clover.xml --whitelist=src" 37 | ], 38 | "lint": [ 39 | "phpmd src,test text codesize,controversial,design,naming,unusedcode", 40 | "phpcs --standard=PSR2 src test" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/fixture/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "typeOnly", 5 | "typeAndEnum", 6 | "typeAndMaxLength", 7 | "typeAndMinLength", 8 | "typeAndMinLengthAndMaxLength", 9 | "typeAndPattern", 10 | "typeAndFormat(date-time)", 11 | "typeAndFormat(email)", 12 | "typeAndFormat(hostname)", 13 | "typeAndFormat(ipv4)", 14 | "typeAndFormat(ipv6)", 15 | "typeAndFormat(uri)" 16 | ], 17 | "properties": { 18 | "typeOnly": { 19 | "type": "string" 20 | }, 21 | "typeAndEnum": { 22 | "type": "string", 23 | "enum": ["aaa", "xabc1290あ_!#"] 24 | }, 25 | "typeAndMaxLength": { 26 | "type": "string", 27 | "maxLength": 132 28 | }, 29 | "typeAndMinLength": { 30 | "type": "string", 31 | "minLength": 132 32 | }, 33 | "typeAndMinLengthAndMaxLength": { 34 | "type": "string", 35 | "minLength": 132, 36 | "maxLength": 13218 37 | }, 38 | "typeAndPattern": { 39 | "type": "string", 40 | "pattern": "^[0-9]{3}-[0-9]{4}$" 41 | }, 42 | "typeAndFormat(date-time)": { 43 | "type": "string", 44 | "format": "date-time" 45 | }, 46 | "typeAndFormat(email)": { 47 | "type": "string", 48 | "format": "email" 49 | }, 50 | "typeAndFormat(hostname)": { 51 | "type": "string", 52 | "format": "hostname" 53 | }, 54 | "typeAndFormat(ipv4)": { 55 | "type": "string", 56 | "format": "ipv4" 57 | }, 58 | "typeAndFormat(ipv6)": { 59 | "type": "string", 60 | "format": "ipv6" 61 | }, 62 | "typeAndFormat(uri)": { 63 | "type": "string", 64 | "format": "uri" 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixture/object.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "typeOnly", 5 | "typeAndAdditionalPropertiesFalse", 6 | "typeAndAdditionalPropertiesTrue", 7 | "typeAndPatternPropertiesAndAdditionalPropertiesFalse", 8 | "typeAndPatternPropertiesAndAdditionalPropertiesTrue", 9 | "typeAndPropertiesAndPatternPropertiesAndAdditionalPropertiesObject", 10 | "typeAndPropertiesAndPatternPropertiesAndAdditionalPropertiesObjectAndMinProperties", 11 | "typeAndPropertiesAndPatternPropertiesAndAdditionalPropertiesObjectAndMinPropertiesAndRequied", 12 | "deepNested" 13 | ], 14 | "properties": { 15 | "typeOnly": { 16 | "type": "object" 17 | }, 18 | "typeAndMinProperties": { 19 | "type": "object", 20 | "minProperties": 5 21 | }, 22 | "typeAndMaxProperties": { 23 | "type": "object", 24 | "maxProperties": 5 25 | }, 26 | "typeAndMinPropertiesAndMaxProperties": { 27 | "type": "object", 28 | "minProperties": 2, 29 | "maxProperties": 10 30 | }, 31 | "typeAndAdditionalPropertiesFalse": { 32 | "type": "object", 33 | "additionalProperties": false 34 | }, 35 | "typeAndAdditionalPropertiesTrue": { 36 | "type": "object", 37 | "additionalProperties": true 38 | }, 39 | "typeAndPatternPropertiesAndAdditionalPropertiesFalse": { 40 | "type": "object", 41 | "patternProperties": { 42 | "^S_[a-z]{0,20}": { "type": "string" }, 43 | "^I_[A-Z]{3}[0-9]{4,8}": { "type": "integer" } 44 | }, 45 | "additionalProperties": false 46 | }, 47 | "typeAndPatternPropertiesAndAdditionalPropertiesTrue": { 48 | "type": "object", 49 | "patternProperties": { 50 | "^S_[a-z]{0,20}": { "type": "string" }, 51 | "^I_[A-Z]{3}[0-9]{4,8}": { "type": "integer" } 52 | }, 53 | "additionalProperties": true 54 | }, 55 | "typeAndPropertiesAndPatternPropertiesAndAdditionalPropertiesObject": { 56 | "type": "object", 57 | "properties": { 58 | "builtin": { "type": "number" } 59 | }, 60 | "patternProperties": { 61 | "^S_[a-z]{0,20}": { "type": "string" }, 62 | "^I_[A-Z]{3}[0-9]{4,8}": { "type": "integer" } 63 | }, 64 | "additionalProperties": { "type": "string" } 65 | }, 66 | "typeAndPropertiesAndPatternPropertiesAndAdditionalPropertiesObjectAndMinProperties": { 67 | "type": "object", 68 | "minProperties": 3, 69 | "properties": { 70 | "builtin": { "type": "number" } 71 | }, 72 | "patternProperties": { 73 | "^S_[a-z]{0,20}": { "type": "string" }, 74 | "^I_[A-Z]{3}[0-9]{4,8}": { "type": "integer" } 75 | }, 76 | "additionalProperties": { "type": "string" } 77 | }, 78 | "typeAndPropertiesAndPatternPropertiesAndAdditionalPropertiesObjectAndMinPropertiesAndRequied": { 79 | "type": "object", 80 | "minProperties": 3, 81 | "required": ["builtin"], 82 | "properties": { 83 | "builtin": { "type": "number" } 84 | }, 85 | "patternProperties": { 86 | "^S_[a-z]{0,20}": { "type": "string" }, 87 | "^I_[A-Z]{3}[0-9]{4,8}": { "type": "integer" } 88 | }, 89 | "additionalProperties": { "type": "string" } 90 | }, 91 | "deepNested": { 92 | "type": "object", 93 | "required": ["first"], 94 | "properties": { 95 | "first": { 96 | "type": "object", 97 | "required": ["second"], 98 | "properties": { 99 | "second": { 100 | "type": "object", 101 | "required": ["third"], 102 | "properties": { 103 | "third": { 104 | "type": "object" 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /test/HelperTest.php: -------------------------------------------------------------------------------- 1 | $expected]; 16 | $key = 'xxx'; 17 | 18 | $actual = \JSONSchemaFaker\get($obj, $key); 19 | 20 | $this->assertSame($actual, $expected); 21 | } 22 | 23 | public function testGetMustReturnDefaultValueIfNotExists() 24 | { 25 | $expected = 123; 26 | $obj = (object)['xxx' => $expected]; 27 | $key = 'aaa'; 28 | 29 | $actual = \JSONSchemaFaker\get($obj, $key, $expected); 30 | 31 | $this->assertSame($actual, $expected); 32 | } 33 | 34 | public function testGetMaximumMustReturnMaximumMinusOneIfExclusiveMaximumTrue() 35 | { 36 | $maximum = 300; 37 | $schema = (object)['exclusiveMaximum' => true, 'maximum' => $maximum]; 38 | 39 | $actual = \JSONSchemaFaker\getMaximum($schema); 40 | 41 | // -1 mean exclusive 42 | $this->assertSame($actual, $maximum - 1); 43 | } 44 | 45 | public function testGetMaximumMustReturnMaximumIfExclusiveMaximumFalse() 46 | { 47 | $maximum = 300; 48 | $schema = (object)['exclusiveMaximum' => false, 'maximum' => $maximum]; 49 | 50 | $actual = \JSONSchemaFaker\getMaximum($schema); 51 | 52 | $this->assertSame($actual, $maximum); 53 | } 54 | 55 | public function testGetMaximumMustReturnMaximumIfExclusiveMaximumAbsent() 56 | { 57 | $maximum = 300; 58 | $schema = (object)['maximum' => $maximum]; 59 | 60 | $actual = \JSONSchemaFaker\getMaximum($schema); 61 | 62 | $this->assertSame($actual, $maximum); 63 | } 64 | 65 | public function testGetMinimumMustReturnMinimumMinusOneIfExclusiveMinimumTrue() 66 | { 67 | $minimum = 300; 68 | $schema = (object)['exclusiveMinimum' => true, 'minimum' => $minimum]; 69 | 70 | $actual = \JSONSchemaFaker\getMinimum($schema); 71 | 72 | // +1 mean exclusive 73 | $this->assertSame($actual, $minimum + 1); 74 | } 75 | 76 | public function testGetMinimumMustReturnMinimumIfExclusiveMinimumFalse() 77 | { 78 | $minimum = 300; 79 | $schema = (object)['exclusiveMinimum' => false, 'minimum' => $minimum]; 80 | 81 | $actual = \JSONSchemaFaker\getMinimum($schema); 82 | 83 | $this->assertSame($actual, $minimum); 84 | } 85 | 86 | public function testGetMinimumMustReturnMinimumIfExclusiveMinimumAbsent() 87 | { 88 | $minimum = 300; 89 | $schema = (object)['minimum' => $minimum]; 90 | 91 | $actual = \JSONSchemaFaker\getMinimum($schema); 92 | 93 | $this->assertSame($actual, $minimum); 94 | } 95 | 96 | public function testGetMultipleOfMustReturnValueIfPresent() 97 | { 98 | $expected = 7; 99 | $schema = (object)['multipleOf' => $expected]; 100 | 101 | $actual = \JSONSchemaFaker\getMultipleOf($schema); 102 | 103 | $this->assertSame($actual, $expected); 104 | } 105 | 106 | public function testGetMultipleOfMustReturnOneIfAbsent() 107 | { 108 | $expected = 1; 109 | $schema = (object)[]; 110 | 111 | $actual = \JSONSchemaFaker\getMultipleOf($schema); 112 | 113 | $this->assertSame($actual, $expected); 114 | } 115 | 116 | public function testGetInternetFakerInstanceMustReturnInstance() 117 | { 118 | $actual = \JSONSchemaFaker\getInternetFakerInstance(); 119 | 120 | $this->assertTrue($actual instanceof \Faker\Provider\Internet); 121 | } 122 | 123 | /** 124 | * @dataProvider getFormats 125 | */ 126 | public function testGetFormattedValueMustReturnValidValue($format) 127 | { 128 | $schema = (object)['type' => 'string', 'format' => $format]; 129 | $validator = new Validator(); 130 | 131 | $actual = \JSONSchemaFaker\getFormattedValue($schema); 132 | $validator->check($actual, $schema); 133 | 134 | $this->assertTrue($validator->isValid()); 135 | } 136 | 137 | /** 138 | * @expectedException Exception 139 | */ 140 | public function testGetFormattedValueMustThrowExceptionIfInvalidFormat() 141 | { 142 | \JSONSchemaFaker\getFormattedValue((object)['format' => 'xxxxx']); 143 | } 144 | 145 | public function testGetPropertiesMust() 146 | { 147 | } 148 | 149 | /** 150 | * @see testGetFormattedValueMustReturnValidValue 151 | * @SuppressWarnings(PHPMD.UnusedPrivateMethod) 152 | */ 153 | public function getFormats() 154 | { 155 | return [ 156 | ['date-time'], 157 | ['email'], 158 | ['hostname'], 159 | ['ipv4'], 160 | ['ipv6'], 161 | ['uri'], 162 | ]; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/helper.php: -------------------------------------------------------------------------------- 1 | {$prop} does not exist 22 | * @return mixed property value or default value 23 | */ 24 | function get($obj, $prop, $default = null) 25 | { 26 | return isset($obj->{$prop}) ? $obj->{$prop} : $default; 27 | } 28 | 29 | function mergeObject() 30 | { 31 | $merged = []; 32 | $objList = func_get_args(); 33 | 34 | foreach ($objList as $obj) { 35 | $merged = array_merge($merged, (array)$obj); 36 | } 37 | 38 | return (object)$merged; 39 | } 40 | 41 | function resolveOf(\stdClass $schema) 42 | { 43 | if (isset($schema->allOf)) { 44 | return call_user_func_array(__NAMESPACE__.'\mergeObject', $schema->allOf); 45 | } elseif (isset($schema->anyOf)) { 46 | return call_user_func_array(__NAMESPACE__.'\mergeObject', Base::randomElements($schema->anyOf)); 47 | } elseif (isset($schema->oneOf)) { 48 | return Base::randomElement($schema->oneOf); 49 | } else { 50 | return $schema; 51 | } 52 | } 53 | 54 | /** 55 | * Get maximum number 56 | * 57 | * @param \stdClass $schema Data structure 58 | * @return int maximum number 59 | */ 60 | function getMaximum($schema) 61 | { 62 | $offset = get($schema, 'exclusiveMaximum', false) ? 1 : 0; 63 | return (int)(get($schema, 'maximum', mt_getrandmax()) - $offset); 64 | } 65 | 66 | /** 67 | * 68 | * 69 | * @param \stdClass $schema Data structure 70 | * @return ... 71 | */ 72 | function getMinimum($schema) 73 | { 74 | $offset = get($schema, 'exclusiveMinimum', false) ? 1 : 0; 75 | return (int)(get($schema, 'minimum', -mt_getrandmax()) + $offset); 76 | } 77 | 78 | /** 79 | * 80 | * 81 | * @param \stdClass $schema Data structure 82 | * @return ... 83 | */ 84 | function getMultipleOf($schema) 85 | { 86 | return get($schema, 'multipleOf', 1); 87 | } 88 | 89 | function getInternetFakerInstance() 90 | { 91 | return new Internet(Factory::create()); 92 | } 93 | 94 | /** 95 | * 96 | * 97 | * @param \stdClass $schema Data structure 98 | * @return ... 99 | */ 100 | function getFormattedValue($schema) 101 | { 102 | switch ($schema->format) { 103 | // Date representation, as defined by RFC 3339, section 5.6. 104 | case 'date-time': 105 | return DateTime::dateTime()->format(DATE_RFC3339); 106 | // Internet email address, see RFC 5322, section 3.4.1. 107 | case 'email': 108 | return getInternetFakerInstance()->safeEmail(); 109 | // Internet host name, see RFC 1034, section 3.1. 110 | case 'hostname': 111 | return getInternetFakerInstance()->domainName(); 112 | // IPv4 address, according to dotted-quad ABNF syntax as defined in RFC 2673, section 3.2. 113 | case 'ipv4': 114 | return getInternetFakerInstance()->ipv4(); 115 | // IPv6 address, as defined in RFC 2373, section 2.2. 116 | case 'ipv6': 117 | return getInternetFakerInstance()->ipv6(); 118 | // A universal resource identifier (URI), according to RFC3986. 119 | case 'uri': 120 | return getInternetFakerInstance()->url(); 121 | default: 122 | throw new \Exception("Unsupported type: {$schema->format}"); 123 | } 124 | } 125 | 126 | function resolveDependencies(\stdClass $schema, array $keys) 127 | { 128 | $resolved = []; 129 | $dependencies = get($schema, 'dependencies', new \stdClass()); 130 | 131 | foreach ($keys as $key) { 132 | $resolved = array_merge($resolved, [$key], get($dependencies, $key, [])); 133 | } 134 | 135 | return $resolved; 136 | } 137 | 138 | /** 139 | * @return string[] Property names 140 | */ 141 | function getProperties(\stdClass $schema) 142 | { 143 | $requiredKeys = get($schema, 'required', []); 144 | $optionalKeys = array_keys((array)get($schema, 'properties', new \stdClass())); 145 | $maxProperties = get($schema, 'maxProperties', count($optionalKeys) - count($requiredKeys)); 146 | $pickSize = Base::numberBetween(0, min(count($optionalKeys), $maxProperties)); 147 | $additionalKeys = resolveDependencies($schema, Base::randomElements($optionalKeys, $pickSize)); 148 | $propertyNames = array_unique(array_merge($requiredKeys, $additionalKeys)); 149 | 150 | $additionalProperties = get($schema, 'additionalProperties', true); 151 | $patternProperties = get($schema, 'patternProperties', new \stdClass()); 152 | $patterns = array_keys((array)$patternProperties); 153 | while (count($propertyNames) < get($schema, 'minProperties', 0)) { 154 | $name = $additionalProperties ? Lorem::word() : Lorem::regexify(Base::randomElement($patterns)); 155 | if (!in_array($name, $propertyNames)) { 156 | $propertyNames[] = $name; 157 | } 158 | } 159 | 160 | return $propertyNames; 161 | } 162 | 163 | function getAdditionalPropertySchema(\stdClass $schema, $property) 164 | { 165 | $patternProperties = get($schema, 'patternProperties', new \stdClass()); 166 | $additionalProperties = get($schema, 'additionalProperties', true); 167 | 168 | foreach ($patternProperties as $pattern => $sub) { 169 | if (preg_match("/{$pattern}/", $property)) { 170 | return $sub; 171 | } 172 | } 173 | 174 | if (is_object($additionalProperties)) { 175 | return $additionalProperties; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/Faker.php: -------------------------------------------------------------------------------- 1 | generate($schema); 26 | } 27 | 28 | /** 29 | * Create dummy data with JSON schema 30 | * 31 | * @param \stdClass $schema Data structure writen in JSON Schema 32 | * @return mixed dummy data 33 | * @throws \Exception Throw when unsupported type specified 34 | */ 35 | public function generate(\stdClass $schema) 36 | { 37 | $schema = resolveOf($schema); 38 | $fakers = $this->getFakers(); 39 | 40 | $type = is_array($schema->type) ? Base::randomElement($schema->type) : $schema->type; 41 | 42 | if (isset($schema->enum)) { 43 | return Base::randomElement($schema->enum); 44 | } 45 | 46 | if (!isset($fakers[$type])) { 47 | throw new \Exception("Unsupported type: {$type}"); 48 | } 49 | 50 | return $fakers[$type]($schema); 51 | } 52 | 53 | private function getFakers() 54 | { 55 | return [ 56 | 'null' => [$this, 'fakeNull'], 57 | 'boolean' => [$this, 'fakeBoolean'], 58 | 'integer' => [$this, 'fakeInteger'], 59 | 'number' => [$this, 'fakeNumber'], 60 | 'string' => [$this, 'fakeString'], 61 | 'array' => [$this, 'fakeArray'], 62 | 'object' => [$this, 'fakeObject'] 63 | ]; 64 | } 65 | 66 | /** 67 | * Create null 68 | * 69 | * @return null 70 | * @SuppressWarnings(PHPMD.UnusedPrivateMethod) 71 | */ 72 | private function fakeNull() 73 | { 74 | return null; 75 | } 76 | 77 | /** 78 | * Create dummy boolean with JSON schema 79 | * 80 | * @return bool true or false 81 | * @SuppressWarnings(PHPMD.UnusedPrivateMethod) 82 | */ 83 | private function fakeBoolean() 84 | { 85 | return Base::randomElement([true, false]); 86 | } 87 | 88 | /** 89 | * Create dummy integer with JSON schema 90 | * 91 | * @param \stdClass $schema Data structure 92 | * @return int 93 | * @SuppressWarnings(PHPMD.UnusedPrivateMethod) 94 | */ 95 | private function fakeInteger(\stdClass $schema) 96 | { 97 | $minimum = getMinimum($schema); 98 | $maximum = getMaximum($schema); 99 | $multipleOf = getMultipleOf($schema); 100 | 101 | return (int)Base::numberBetween($minimum, $maximum) * $multipleOf; 102 | } 103 | 104 | /** 105 | * Create dummy floating number with JSON schema 106 | * 107 | * @param \stdClass $schema Data structure 108 | * @return float 109 | * @SuppressWarnings(PHPMD.UnusedPrivateMethod) 110 | */ 111 | private function fakeNumber(\stdClass $schema) 112 | { 113 | $minimum = getMinimum($schema); 114 | $maximum = getMaximum($schema); 115 | $multipleOf = getMultipleOf($schema); 116 | 117 | return Base::randomFloat(null, $minimum, $maximum) * $multipleOf; 118 | } 119 | 120 | /** 121 | * @param \stdClass $schema Data structure 122 | * @return string 123 | * @SuppressWarnings(PHPMD.UnusedPrivateMethod) 124 | */ 125 | private function fakeString(\stdClass $schema) 126 | { 127 | if (isset($schema->format)) { 128 | return getFormattedValue($schema); 129 | } elseif (isset($schema->pattern)) { 130 | return Lorem::regexify($schema->pattern); 131 | } else { 132 | $min = get($schema, 'minLength', 1); 133 | $max = get($schema, 'maxLength', max(5, $min + 1)); 134 | $lorem = Lorem::text($max); 135 | 136 | if (mb_strlen($lorem) < $min) { 137 | $lorem = str_repeat($lorem, $min); 138 | } 139 | 140 | return mb_substr($lorem, 0, $max); 141 | } 142 | } 143 | 144 | /** 145 | * @param \stdClass $schema Data structure 146 | * @return array 147 | * @SuppressWarnings(PHPMD.UnusedPrivateMethod) 148 | */ 149 | private function fakeArray(\stdClass $schema) 150 | { 151 | if (!isset($schema->items)) { 152 | $subschemas = [$this->getRandomSchema()]; 153 | // List 154 | } elseif (is_object($schema->items)) { 155 | $subschemas = [$schema->items]; 156 | // Tuple 157 | } elseif (is_array($schema->items)) { 158 | $subschemas = $schema->items; 159 | } else { 160 | throw new \Exception("Invalid items"); 161 | } 162 | 163 | $dummies = []; 164 | $itemSize = Base::numberBetween(get($schema, 'minItems', 0), get($schema, 'maxItems', count($subschemas))); 165 | $subschemas = array_slice($subschemas, 0, $itemSize); 166 | for ($i = 0; $i < $itemSize; $i++) { 167 | $dummies[] = $this->generate($subschemas[$i % count($subschemas)]); 168 | } 169 | 170 | return get($schema, 'uniqueItems', false) ? array_unique($dummies) : $dummies; 171 | } 172 | 173 | /** 174 | * @param \stdClass $schema Data structure 175 | * @return \stdClass 176 | * @SuppressWarnings(PHPMD.UnusedPrivateMethod) 177 | */ 178 | private function fakeObject(\stdClass $schema) 179 | { 180 | $properties = get($schema, 'properties', new \stdClass()); 181 | $propertyNames = getProperties($schema); 182 | 183 | $dummy = new \stdClass(); 184 | foreach ($propertyNames as $key) { 185 | if (isset($properties->{$key})) { 186 | $subschema = $properties->{$key}; 187 | } else { 188 | $subschema = getAdditionalPropertySchema($schema, $key) ?: $this->getRandomSchema(); 189 | } 190 | 191 | $dummy->{$key} = $this->generate($subschema); 192 | } 193 | 194 | return $dummy; 195 | } 196 | 197 | private function getRandomSchema() 198 | { 199 | $fakerNames = array_keys($this->getFakers()); 200 | 201 | return (object)[ 202 | 'type' => Base::randomElement($fakerNames) 203 | ]; 204 | } 205 | } 206 | --------------------------------------------------------------------------------