├── tests ├── JsonSchema │ ├── dist │ │ └── .gitkeep │ ├── fixture │ │ ├── def │ │ │ ├── null.json │ │ │ ├── boolean.json │ │ │ ├── typeOnly.json │ │ │ ├── ref_inline.json │ │ │ ├── typeAndMaxItems.json │ │ │ └── ref.json │ │ ├── null.json │ │ ├── boolean.json │ │ ├── ref_file_ref.json │ │ ├── ref_file_double.json │ │ ├── ref_file.json │ │ ├── ref_array.json │ │ ├── format.json │ │ ├── array.json │ │ ├── ref_inline.json │ │ ├── integer.json │ │ ├── combining.json │ │ ├── number.json │ │ ├── string.json │ │ └── object.json │ ├── TestCase.php │ ├── FakeJsonsTest.php │ ├── FakerTest.php │ └── HelperTest.php └── index.php ├── .gitignore ├── src ├── Faker │ ├── Factory.php │ └── Generator.php ├── JsonSchema │ ├── InvalidItemsException.php │ ├── UnsupportedTypeException.php │ ├── FakeJsons.php │ ├── Ref.php │ └── Faker.php ├── Provider │ ├── zh_TW │ │ ├── PhoneNumber.php │ │ ├── DateTime.php │ │ ├── Color.php │ │ ├── Person.php │ │ └── Company.php │ ├── zh_CN │ │ ├── Internet.php │ │ ├── Payment.php │ │ ├── PhoneNumber.php │ │ ├── DateTime.php │ │ ├── Color.php │ │ ├── Address.php │ │ └── Company.php │ ├── Company.php │ ├── PhoneNumber.php │ ├── en_US │ │ ├── Payment.php │ │ ├── PhoneNumber.php │ │ └── Address.php │ ├── Uuid.php │ ├── Biased.php │ ├── Barcode.php │ ├── Person.php │ ├── Address.php │ ├── Image.php │ ├── Color.php │ ├── Text.php │ ├── UserAgent.php │ ├── Lorem.php │ ├── HtmlLorem.php │ └── Payment.php ├── DefaultGenerator.php ├── autoload.php ├── Calculator │ ├── Inn.php │ ├── Ean.php │ ├── TCNo.php │ ├── Iban.php │ └── Luhn.php ├── UniqueGenerator.php ├── ValidGenerator.php ├── Factory.php ├── Documentor.php ├── Guesser │ └── Name.php └── Generator.php ├── composer.json └── LICENSE /tests/JsonSchema/dist/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/JsonSchema/fixture/def/null.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "null" 3 | } -------------------------------------------------------------------------------- /tests/JsonSchema/fixture/null.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "null" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tests/JsonSchema/dist/* 2 | !tests/JsonSchema/dist/.gitkeep -------------------------------------------------------------------------------- /tests/JsonSchema/fixture/boolean.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "boolean" 3 | } -------------------------------------------------------------------------------- /tests/JsonSchema/fixture/def/boolean.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "boolean" 3 | } -------------------------------------------------------------------------------- /src/Faker/Factory.php: -------------------------------------------------------------------------------- 1 | optional(). 8 | */ 9 | class DefaultGenerator 10 | { 11 | protected $default; 12 | 13 | public function __construct($default = null) 14 | { 15 | $this->default = $default; 16 | } 17 | 18 | /** 19 | * @param string $attribute 20 | * 21 | * @return mixed 22 | */ 23 | public function __get($attribute) 24 | { 25 | return $this->default; 26 | } 27 | 28 | /** 29 | * @param string $method 30 | * @param array $attributes 31 | * 32 | * @return mixed 33 | */ 34 | public function __call($method, $attributes) 35 | { 36 | return $this->default; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/JsonSchema/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 | -------------------------------------------------------------------------------- /tests/JsonSchema/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 | "typeAndMaxItems": { 15 | "type": "array", 16 | "maxItems": 10 17 | }, 18 | "typeAndMinItems": { 19 | "type": "array", 20 | "minItems": 10, 21 | "maxItems": 10 22 | }, 23 | "typeAndMultipleItems": { 24 | "type": "array", 25 | "items": [ 26 | { 27 | "type": "integer" 28 | }, 29 | { 30 | "type": "string" 31 | } 32 | ] 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "poppy/faker", 3 | "type": "library", 4 | "description": "Poppy Faker is a PHP library that generates fake data for zh user.", 5 | "keywords": [ 6 | "faker", 7 | "fixtures", 8 | "data" 9 | ], 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Mark Zhao" 14 | }, 15 | { 16 | "name": "François Zaninotto" 17 | } 18 | ], 19 | "require": { 20 | "php": "^8.2" 21 | }, 22 | "require-dev": { 23 | "ext-intl": "*" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Faker\\": "src/Faker/", 28 | "Poppy\\Faker\\": "src/" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Poppy\\Faker\\Test\\": "tests/" 34 | } 35 | }, 36 | "config": { 37 | "sort-packages": true 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/JsonSchema/TestCase.php: -------------------------------------------------------------------------------- 1 | invokeArgs($instance, $args); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/autoload.php: -------------------------------------------------------------------------------- 1 | fakeJsons = new FakeJsons(); 20 | } 21 | 22 | public function testInvoke(): void 23 | { 24 | ($this->fakeJsons)(__DIR__ . '/fixture', __DIR__ . '/dist', 'http://example.com/schema'); 25 | $validator = new Validator(); 26 | $data = json_decode((string) file_get_contents(__DIR__ . '/dist/ref_file_double.json')); 27 | $validator->validate($data, (object) ['$ref' => 'file://' . __DIR__ . '/fixture/ref_file_double.json']); 28 | foreach ($validator->getErrors() as $error) { 29 | fwrite(STDOUT, sprintf("[%s] %s\n", $error['property'], $error['message'])); 30 | } 31 | $this->assertTrue($validator->isValid()); 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/Provider/Company.php: -------------------------------------------------------------------------------- 1 | generator->parse($format); 27 | } 28 | 29 | /** 30 | * @return string 31 | * @example 'Ltd' 32 | * 33 | */ 34 | public static function companySuffix() 35 | { 36 | return static::randomElement(static::$companySuffix); 37 | } 38 | 39 | /** 40 | * @return string 41 | * @example 'Job' 42 | * 43 | */ 44 | public function jobTitle() 45 | { 46 | $format = static::randomElement(static::$jobTitleFormat); 47 | 48 | return $this->generator->parse($format); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Calculator/Inn.php: -------------------------------------------------------------------------------- 1 | 2, 2 => 4, 3 => 10, 4 => 3, 5 => 5, 6 => 9, 7 => 4, 8 => 6, 9 => 8]; 18 | $sum = 0; 19 | for ($i = 1; $i <= 9; $i++) { 20 | $sum += intval(substr($inn, $i - 1, 1)) * $multipliers[$i]; 21 | } 22 | return strval(($sum % 11) % 10); 23 | } 24 | 25 | /** 26 | * Checks whether an INN has a valid checksum 27 | * 28 | * @param string $inn 29 | * @return boolean 30 | */ 31 | public static function isValid($inn) 32 | { 33 | return self::checksum(substr($inn, 0, -1)) === substr($inn, -1, 1); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 duoli 2 | Copyright (c) 2011 François Zaninotto 3 | Portions Copyright (c) 2008 Caius Durling 4 | Portions Copyright (c) 2008 Adam Royle 5 | Portions Copyright (c) 2008 Fiona Burrows 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /src/Provider/PhoneNumber.php: -------------------------------------------------------------------------------- 1 | generator->parse(static::randomElement(static::$formats))); 17 | } 18 | 19 | /** 20 | * @return string 21 | * @example +27113456789 22 | */ 23 | public function e164PhoneNumber(): string 24 | { 25 | $formats = ['+%############']; 26 | return static::numerify($this->generator->parse(static::randomElement($formats))); 27 | } 28 | 29 | /** 30 | * International Mobile Equipment Identity (IMEI) 31 | * 32 | * @link http://en.wikipedia.org/wiki/International_Mobile_Station_Equipment_Identity 33 | * @link http://imei-number.com/imei-validation-check/ 34 | * @example '720084494799532' 35 | * @return string $imei 36 | */ 37 | public function imei(): string 38 | { 39 | $imei = static::numerify('##############'); 40 | $imei .= Luhn::computeCheckDigit($imei); 41 | return $imei; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/JsonSchema/fixture/ref_inline.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "typeOnly", 5 | "typeAndMinItems", 6 | "typeAndMaxItems", 7 | "typeAndMinItems", 8 | "typeAndMultipleItems" 9 | ], 10 | "properties": { 11 | "typeOnly": { 12 | "$ref": "#/definitions/typeOnly" 13 | }, 14 | "typeAndMaxItems": { 15 | "$ref": "#/definitions/typeAndMaxItems" 16 | }, 17 | "typeAndMinItems": { 18 | "$ref": "#/definitions/typeAndMinItems" 19 | }, 20 | "typeAndMultipleItems": { 21 | "$ref": "#/definitions/typeAndMultipleItems" 22 | } 23 | }, 24 | "definitions": { 25 | "typeOnly": { 26 | "type": "array" 27 | }, 28 | "typeAndMaxItems": { 29 | "type": "array", 30 | "maxItems": 10 31 | }, 32 | "typeAndMinItems": { 33 | "type": "array", 34 | "minItems": 10, 35 | "maxItems": 10 36 | }, 37 | "typeAndMultipleItems": { 38 | "type": "array", 39 | "items": [ 40 | { 41 | "type": "integer" 42 | }, 43 | { 44 | "type": "string" 45 | } 46 | ] 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/JsonSchema/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": [ 20 | -100, 21 | 391, 22 | 2, 23 | 0 24 | ] 25 | }, 26 | "typeAndMinimum": { 27 | "type": "integer", 28 | "minimum": -1 29 | }, 30 | "typeAndExcludeMinimum": { 31 | "type": "integer", 32 | "minimum": -1, 33 | "maximum": 3, 34 | "exclusiveMinimum": true 35 | }, 36 | "typeAndMaximum": { 37 | "type": "integer", 38 | "maximum": 3 39 | }, 40 | "typeAndMinimumAndMaximum": { 41 | "type": "integer", 42 | "minimum": -3010210, 43 | "maximum": 312 44 | }, 45 | "typeAndMultipleOf": { 46 | "type": "integer", 47 | "multipleOf": 3 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Provider/en_US/Payment.php: -------------------------------------------------------------------------------- 1 | format('a') === 'am' ? '上午' : '下午'; 10 | } 11 | 12 | public static function dayOfWeek($max = 'now') 13 | { 14 | $map = [ 15 | 'Sunday' => '星期日', 16 | 'Monday' => '星期一', 17 | 'Tuesday' => '星期二', 18 | 'Wednesday' => '星期三', 19 | 'Thursday' => '星期四', 20 | 'Friday' => '星期五', 21 | 'Saturday' => '星期六', 22 | ]; 23 | $week = static::dateTime($max)->format('l'); 24 | return $map[$week] ?? $week; 25 | } 26 | 27 | public static function monthName($max = 'now') 28 | { 29 | $map = [ 30 | 'January' => '一月', 31 | 'February' => '二月', 32 | 'March' => '三月', 33 | 'April' => '四月', 34 | 'May' => '五月', 35 | 'June' => '六月', 36 | 'July' => '七月', 37 | 'August' => '八月', 38 | 'September' => '九月', 39 | 'October' => '十月', 40 | 'November' => '十一月', 41 | 'December' => '十二月', 42 | ]; 43 | $month = static::dateTime($max)->format('F'); 44 | return $map[$month] ?? $month; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Provider/zh_TW/DateTime.php: -------------------------------------------------------------------------------- 1 | format('a') === 'am' ? '上午' : '下午'; 10 | } 11 | 12 | public static function dayOfWeek($max = 'now') 13 | { 14 | $map = [ 15 | 'Sunday' => '星期日', 16 | 'Monday' => '星期一', 17 | 'Tuesday' => '星期二', 18 | 'Wednesday' => '星期三', 19 | 'Thursday' => '星期四', 20 | 'Friday' => '星期五', 21 | 'Saturday' => '星期六', 22 | ]; 23 | $week = static::dateTime($max)->format('l'); 24 | return $map[$week] ?? $week; 25 | } 26 | 27 | public static function monthName($max = 'now') 28 | { 29 | $map = [ 30 | 'January' => '一月', 31 | 'February' => '二月', 32 | 'March' => '三月', 33 | 'April' => '四月', 34 | 'May' => '五月', 35 | 'June' => '六月', 36 | 'July' => '七月', 37 | 'August' => '八月', 38 | 'September' => '九月', 39 | 'October' => '十月', 40 | 'November' => '十一月', 41 | 'December' => '十二月', 42 | ]; 43 | $month = static::dateTime($max)->format('F'); 44 | return $map[$month] ?? $month; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Calculator/Ean.php: -------------------------------------------------------------------------------- 1 | = 0; $i -= 2) { 30 | $even += (int) $digits[$i]; 31 | } 32 | 33 | $odd = 0; 34 | for ($i = $length - 2; $i >= 0; $i -= 2) { 35 | $odd += (int) $digits[$i]; 36 | } 37 | 38 | return (10 - ((3 * $even + $odd) % 10)) % 10; 39 | } 40 | 41 | /** 42 | * Checks whether the provided number is an EAN compliant number and that 43 | * the checksum is correct. 44 | * 45 | * @param string $ean An EAN number 46 | * @return boolean 47 | */ 48 | public static function isValid(string $ean): bool 49 | { 50 | if (!preg_match(self::PATTERN, $ean)) { 51 | return false; 52 | } 53 | 54 | return self::checksum(substr($ean, 0, -1)) === (int) substr($ean, -1); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/JsonSchema/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": [ 20 | -10.0, 21 | 3.91, 22 | 2.1, 23 | -0.1 24 | ] 25 | }, 26 | "typeAndMinimum": { 27 | "type": "number", 28 | "minimum": -1 29 | }, 30 | "typeAndExcludeMinimum": { 31 | "type": "number", 32 | "minimum": -1, 33 | "exclusiveMinimum": true 34 | }, 35 | "typeAndMaximum": { 36 | "type": "number", 37 | "maximum": 3 38 | }, 39 | "typeAndExcludeMinimum": { 40 | "type": "number", 41 | "maximum": 3, 42 | "excludeMinimum": true 43 | }, 44 | "typeAndMinimumAndMaximum": { 45 | "type": "number", 46 | "minimum": -301.0210, 47 | "maximum": 312 48 | }, 49 | "typeAndMultipleOf": { 50 | "type": "number", 51 | "multipleOf": 3 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Calculator/TCNo.php: -------------------------------------------------------------------------------- 1 | $digit) { 29 | if ($index % 2 == 0) { 30 | $evenSum += $digit; 31 | } 32 | else { 33 | $oddSum += $digit; 34 | } 35 | } 36 | 37 | $tenthDigit = (7 * $evenSum - $oddSum) % 10; 38 | $eleventhDigit = ($evenSum + $oddSum + $tenthDigit) % 10; 39 | 40 | return $tenthDigit . $eleventhDigit; 41 | } 42 | 43 | /** 44 | * Checks whether a TCNo has a valid checksum 45 | * 46 | * @param string $tcNo 47 | * @return boolean 48 | */ 49 | public static function isValid($tcNo) 50 | { 51 | return self::checksum(substr($tcNo, 0, -2)) === substr($tcNo, -2, 2); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/UniqueGenerator.php: -------------------------------------------------------------------------------- 1 | unique() 8 | */ 9 | class UniqueGenerator 10 | { 11 | protected $generator; 12 | 13 | protected $maxRetries; 14 | 15 | protected $uniques = []; 16 | 17 | /** 18 | * @param Generator $generator 19 | * @param integer $maxRetries 20 | */ 21 | public function __construct(Generator $generator, $maxRetries = 10000) 22 | { 23 | $this->generator = $generator; 24 | $this->maxRetries = $maxRetries; 25 | } 26 | 27 | /** 28 | * Catch and proxy all generator calls but return only unique values 29 | * @param string $attribute 30 | * @return mixed 31 | */ 32 | public function __get($attribute) 33 | { 34 | return $this->__call($attribute, []); 35 | } 36 | 37 | /** 38 | * Catch and proxy all generator calls with arguments but return only unique values 39 | * @param string $name 40 | * @param array $arguments 41 | * @return mixed 42 | */ 43 | public function __call($name, $arguments) 44 | { 45 | if (!isset($this->uniques[$name])) { 46 | $this->uniques[$name] = []; 47 | } 48 | $i = 0; 49 | do { 50 | $res = call_user_func_array([$this->generator, $name], $arguments); 51 | $i++; 52 | if ($i > $this->maxRetries) { 53 | throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a unique value', $this->maxRetries)); 54 | } 55 | } while (array_key_exists(serialize($res), $this->uniques[$name])); 56 | $this->uniques[$name][serialize($res)] = null; 57 | 58 | return $res; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Calculator/Iban.php: -------------------------------------------------------------------------------- 1 | > 8) | (($tLo & 0xff000000) >> 24); 34 | $tMi = (($tMi & 0x00ff) << 8) | (($tMi & 0xff00) >> 8); 35 | $tHi = (($tHi & 0x00ff) << 8) | (($tHi & 0xff00) >> 8); 36 | } 37 | 38 | // apply version number 39 | $tHi &= 0x0fff; 40 | $tHi |= (3 << 12); 41 | 42 | // cast to string 43 | return sprintf( 44 | '%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x', 45 | $tLo, 46 | $tMi, 47 | $tHi, 48 | $csHi, 49 | $csLo, 50 | $byte[10], 51 | $byte[11], 52 | $byte[12], 53 | $byte[13], 54 | $byte[14], 55 | $byte[15] 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/JsonSchema/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": [ 24 | "aaa", 25 | "xabc1290あ_!#" 26 | ] 27 | }, 28 | "typeAndMaxLength": { 29 | "type": "string", 30 | "maxLength": 132 31 | }, 32 | "typeAndMinLength": { 33 | "type": "string", 34 | "minLength": 132 35 | }, 36 | "typeAndMinLengthAndMaxLength": { 37 | "type": "string", 38 | "minLength": 132, 39 | "maxLength": 13218 40 | }, 41 | "typeAndPattern": { 42 | "type": "string", 43 | "pattern": "^[0-9]{3}-[0-9]{4}$" 44 | }, 45 | "typeAndFormat(date-time)": { 46 | "type": "string", 47 | "format": "date-time" 48 | }, 49 | "typeAndFormat(email)": { 50 | "type": "string", 51 | "format": "email" 52 | }, 53 | "typeAndFormat(hostname)": { 54 | "type": "string", 55 | "format": "hostname" 56 | }, 57 | "typeAndFormat(ipv4)": { 58 | "type": "string", 59 | "format": "ipv4" 60 | }, 61 | "typeAndFormat(ipv6)": { 62 | "type": "string", 63 | "format": "ipv6" 64 | }, 65 | "typeAndFormat(uri)": { 66 | "type": "string", 67 | "format": "uri" 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Calculator/Luhn.php: -------------------------------------------------------------------------------- 1 | = 0; $i -= 2) { 27 | $sum += $number[$i]; 28 | } 29 | for ($i = $length - 2; $i >= 0; $i -= 2) { 30 | $sum += array_sum(str_split($number[$i] * 2)); 31 | } 32 | 33 | return $sum % 10; 34 | } 35 | 36 | /** 37 | * @param $partialNumber 38 | * @return string 39 | */ 40 | public static function computeCheckDigit($partialNumber) 41 | { 42 | $checkDigit = self::checksum($partialNumber . '0'); 43 | if ($checkDigit === 0) { 44 | return 0; 45 | } 46 | 47 | return (string) (10 - $checkDigit); 48 | } 49 | 50 | /** 51 | * Checks whether a number (partial number + check digit) is Luhn compliant 52 | * 53 | * @param string $number 54 | * @return bool 55 | */ 56 | public static function isValid($number) 57 | { 58 | return self::checksum($number) === 0; 59 | } 60 | 61 | /** 62 | * Generate a Luhn compliant number. 63 | * 64 | * @param string $partialValue 65 | * 66 | * @return string 67 | */ 68 | public static function generateLuhnNumber($partialValue) 69 | { 70 | if (!preg_match('/^\d+$/', $partialValue)) { 71 | throw new InvalidArgumentException('Argument should be an integer.'); 72 | } 73 | return $partialValue . Luhn::computeCheckDigit($partialValue); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Provider/Biased.php: -------------------------------------------------------------------------------- 1 | valid() 11 | */ 12 | class ValidGenerator 13 | { 14 | protected $generator; 15 | 16 | protected $validator; 17 | 18 | protected $maxRetries; 19 | 20 | /** 21 | * @param Generator $generator 22 | * @param callable|null $validator 23 | * @param integer $maxRetries 24 | */ 25 | public function __construct(Generator $generator, $validator = null, $maxRetries = 10000) 26 | { 27 | if (is_null($validator)) { 28 | $validator = function () { 29 | return true; 30 | }; 31 | } 32 | elseif (!is_callable($validator)) { 33 | throw new InvalidArgumentException('valid() only accepts callables as first argument'); 34 | } 35 | $this->generator = $generator; 36 | $this->validator = $validator; 37 | $this->maxRetries = $maxRetries; 38 | } 39 | 40 | /** 41 | * Catch and proxy all generator calls but return only valid values 42 | * @param string $attribute 43 | * 44 | * @return mixed 45 | */ 46 | public function __get($attribute) 47 | { 48 | return $this->__call($attribute, []); 49 | } 50 | 51 | /** 52 | * Catch and proxy all generator calls with arguments but return only valid values 53 | * @param string $name 54 | * @param array $arguments 55 | * 56 | * @return mixed 57 | */ 58 | public function __call($name, $arguments) 59 | { 60 | $i = 0; 61 | do { 62 | $res = call_user_func_array([$this->generator, $name], $arguments); 63 | $i++; 64 | if ($i > $this->maxRetries) { 65 | throw new OverflowException(sprintf('Maximum retries of %d reached without finding a valid value', $this->maxRetries)); 66 | } 67 | } while (!call_user_func($this->validator, $res)); 68 | 69 | return $res; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Factory.php: -------------------------------------------------------------------------------- 1 | addProvider(new $providerClassName($generator)); 29 | } 30 | 31 | return $generator; 32 | } 33 | 34 | /** 35 | * @param string $provider 36 | * @param string $locale 37 | * @return string 38 | */ 39 | protected static function getProviderClassname($provider, $locale = '') 40 | { 41 | if ($providerClass = self::findProviderClassname($provider, $locale)) { 42 | return $providerClass; 43 | } 44 | // fallback to default locale 45 | if ($providerClass = self::findProviderClassname($provider, static::DEFAULT_LOCALE)) { 46 | return $providerClass; 47 | } 48 | // fallback to no locale 49 | if ($providerClass = self::findProviderClassname($provider)) { 50 | return $providerClass; 51 | } 52 | throw new InvalidArgumentException(sprintf('Unable to find provider "%s" with locale "%s"', $provider, $locale)); 53 | } 54 | 55 | /** 56 | * @param string $provider 57 | * @param string $locale 58 | * @return string 59 | */ 60 | protected static function findProviderClassname($provider, $locale = '') 61 | { 62 | $providerClass = 'Poppy\\Faker\\' . ($locale ? sprintf('Provider\%s\%s', $locale, $provider) : sprintf('Provider\%s', $provider)); 63 | if (class_exists($providerClass)) { 64 | return $providerClass; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/JsonSchema/FakerTest.php: -------------------------------------------------------------------------------- 1 | getFixture($type); 21 | $validator = new Validator(); 22 | 23 | $actual = (new Faker)->generate($schema); 24 | $validator->validate($actual, $schema); 25 | 26 | $this->assertTrue($validator->isValid(), (string) json_encode($validator->getErrors(), JSON_PRETTY_PRINT)); 27 | } 28 | 29 | /** 30 | * @dataProvider getTypesAndFile 31 | */ 32 | public function testFakeFromFile($type) 33 | { 34 | $schema = $this->getFile($type); 35 | $validator = new Validator(); 36 | 37 | $actual = (new Faker)->generate(new SplFileInfo($schema)); 38 | $validator->validate($actual, $schema); 39 | 40 | $this->assertTrue($validator->isValid(), (string) json_encode($validator->getErrors(), JSON_PRETTY_PRINT)); 41 | } 42 | 43 | public function testGenerateInvalidParameter() 44 | { 45 | $this->expectException(InvalidArgumentException::class); 46 | (new Faker)->generate(null); 47 | } 48 | 49 | public function getTypes(): array 50 | { 51 | return [ 52 | ['boolean'], 53 | ['null'], 54 | ['integer'], 55 | ['number'], 56 | ['string'], 57 | ['array'], 58 | ['object'], 59 | ['combining'], 60 | ['ref_inline'], 61 | ]; 62 | } 63 | 64 | public function getTypesAndFile(): array 65 | { 66 | return [ 67 | ['boolean'], 68 | ['null'], 69 | ['integer'], 70 | ['number'], 71 | ['string'], 72 | ['array'], 73 | ['object'], 74 | ['combining'], 75 | ['ref_file'], 76 | ['ref_file_ref'], 77 | ['ref_file_double'], 78 | ['ref_array'], 79 | ]; 80 | } 81 | 82 | public function testFakeMustThrowExceptionIfInvalidType() 83 | { 84 | $this->expectException(UnsupportedTypeException::class); 85 | 86 | (new Faker)->generate((object) ['type' => 'xxxxx']); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/JsonSchema/FakeJsons.php: -------------------------------------------------------------------------------- 1 | files($schemaDir) as $fileInfo) { 23 | /* @var SplFileInfo $fileInfo */ 24 | try { 25 | $fake = $faker->generate($fileInfo); 26 | $schemaFilename = $fileInfo->getFilename(); 27 | if ($fake instanceof stdClass && is_string($schemaUri)) { 28 | $fake->{'$schema'} = sprintf('%s/%s', $schemaUri, $schemaFilename); 29 | } 30 | $targetPath = $distDir . str_replace($schemaDir, '', $fileInfo->getPath()); 31 | if (!file_exists($targetPath)) { 32 | if (!mkdir($targetPath, 0755, true) && !is_dir($targetPath)) { 33 | throw new RuntimeException(sprintf('Directory "%s" was not created', $targetPath)); 34 | } 35 | } 36 | $distFile = sprintf('%s/%s', $targetPath, $schemaFilename); 37 | $fakeJson = json_encode($fake, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL; 38 | fwrite(STDOUT, sprintf("Generate fake file: %s\n", $distFile)); 39 | file_put_contents($distFile, $fakeJson); 40 | } catch (Exception $e) { 41 | fwrite(STDOUT, sprintf("%s: %s %s on line %d\n", $fileInfo->getFilename(), $e->getMessage(), $e->getFile(), $e->getLine())); 42 | continue; 43 | } 44 | } 45 | } 46 | 47 | private function files(string $dir): Iterator 48 | { 49 | return 50 | new RegexIterator( 51 | new RecursiveIteratorIterator( 52 | new RecursiveDirectoryIterator( 53 | $dir, 54 | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::SKIP_DOTS 55 | ), 56 | RecursiveIteratorIterator::LEAVES_ONLY 57 | ), 58 | "/^.+\\.json/", 59 | RegexIterator::MATCH 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Provider/zh_CN/Color.php: -------------------------------------------------------------------------------- 1 | faker = $faker; 27 | $this->schemaDir = $schemaDir; 28 | } 29 | 30 | public function __invoke(stdClass $schema, stdClass $parentSchema = null) 31 | { 32 | $path = (string) $schema->{'$ref'}; 33 | if ($path[0] === '#') { 34 | $parentSchema = $parentSchema instanceof stdClass ? $parentSchema : $schema; 35 | 36 | return $this->inlineRef($parentSchema, $path); 37 | } 38 | 39 | return $this->externalRef($path, $parentSchema); 40 | } 41 | 42 | private function inlineRef(stdClass $parentSchema, string $path) 43 | { 44 | $paths = explode('/', substr($path, 2)); 45 | $prop = $parentSchema; 46 | foreach ($paths as $schemaPath) { 47 | $prop = $prop->{$schemaPath}; 48 | } 49 | 50 | return $this->faker->generate($prop, null); 51 | } 52 | 53 | private function externalRef(string $path, stdClass $parentSchema = null) 54 | { 55 | $jsonFileName = substr($path, 0, 2) === './' ? substr($path, 2) : $path; 56 | $jsonPath = sprintf('%s/%s', $this->schemaDir, $jsonFileName); 57 | $realPath = (string) realpath($jsonPath); 58 | if (is_int(strpos($jsonPath, '#'))) { 59 | return $this->inlineRefInExternalRef($jsonPath); 60 | } 61 | if (!file_exists($jsonPath)) { 62 | throw new RuntimeException("JSON file not exits:{$jsonPath}"); 63 | } 64 | if (!file_exists($realPath)) { 65 | return $this->inlineRefInExternalRef($realPath); 66 | } 67 | 68 | return $this->faker->generate(new SplFileInfo($realPath), $parentSchema, dirname($jsonPath)); 69 | } 70 | 71 | private function inlineRefInExternalRef(string $jsonPath) 72 | { 73 | $paths = explode('#', $jsonPath); 74 | if (count($paths) !== 2) { 75 | throw new RuntimeException("JSON file not exits:{$jsonPath}"); 76 | } 77 | $schemaFile = $paths[0]; 78 | $path = '.' . $paths[1]; 79 | if (!file_exists($schemaFile)) { 80 | throw new RuntimeException("JSON file not exits:{$jsonPath}"); 81 | } 82 | $json = json_decode((string) file_get_contents($schemaFile)); 83 | 84 | return $this->inlineRef($json, $path); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Documentor.php: -------------------------------------------------------------------------------- 1 | generator = $generator; 15 | } 16 | 17 | /** 18 | * @return array 19 | */ 20 | public function getFormatters() 21 | { 22 | $formatters = []; 23 | $providers = array_reverse($this->generator->getProviders()); 24 | $providers[] = new Provider\Base($this->generator); 25 | foreach ($providers as $provider) { 26 | $providerClass = get_class($provider); 27 | $formatters[$providerClass] = []; 28 | $refl = new \ReflectionObject($provider); 29 | foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflmethod) { 30 | if ($reflmethod->getDeclaringClass()->getName() == 'Poppy\Faker\Provider\Base' && $providerClass != 'Poppy\Faker\Provider\Base') { 31 | continue; 32 | } 33 | $methodName = $reflmethod->name; 34 | if ($reflmethod->isConstructor()) { 35 | continue; 36 | } 37 | $parameters = []; 38 | foreach ($reflmethod->getParameters() as $reflparameter) { 39 | $parameter = '$' . $reflparameter->getName(); 40 | if ($reflparameter->isDefaultValueAvailable()) { 41 | $parameter .= ' = ' . var_export($reflparameter->getDefaultValue(), true); 42 | } 43 | $parameters [] = $parameter; 44 | } 45 | $parameters = $parameters ? '(' . join(', ', $parameters) . ')' : ''; 46 | try { 47 | $example = $this->generator->format($methodName); 48 | } catch (\InvalidArgumentException $e) { 49 | $example = ''; 50 | } 51 | if (is_array($example)) { 52 | $example = "array('" . join("', '", $example) . "')"; 53 | } 54 | elseif ($example instanceof \DateTime) { 55 | $example = "DateTime('" . $example->format('Y-m-d H:i:s') . "')"; 56 | } 57 | elseif ($example instanceof Generator || $example instanceof UniqueGenerator) { // modifier 58 | $example = ''; 59 | } 60 | else { 61 | $example = var_export($example, true); 62 | } 63 | $formatters[$providerClass][$methodName . $parameters] = $example; 64 | } 65 | } 66 | 67 | return $formatters; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Provider/Barcode.php: -------------------------------------------------------------------------------- 1 | ean(13); 21 | } 22 | 23 | /** 24 | * Get a random EAN8 barcode. 25 | * @return string 26 | * @example '73513537' 27 | */ 28 | public function ean8() 29 | { 30 | return $this->ean(8); 31 | } 32 | 33 | /** 34 | * Get a random ISBN-10 code 35 | * @link http://en.wikipedia.org/wiki/International_Standard_Book_Number 36 | * 37 | * @return string 38 | * @example '4881416324' 39 | */ 40 | public function isbn10() 41 | { 42 | $code = static::numerify(str_repeat('#', 9)); 43 | 44 | return $code . static::isbnChecksum($code); 45 | } 46 | 47 | /** 48 | * Get a random ISBN-13 code 49 | * @link http://en.wikipedia.org/wiki/International_Standard_Book_Number 50 | * 51 | * @return string 52 | * @example '9790404436093' 53 | */ 54 | public function isbn13() 55 | { 56 | $code = '97' . static::numberBetween(8, 9) . static::numerify(str_repeat('#', 9)); 57 | 58 | return $code . static::eanChecksum($code); 59 | } 60 | 61 | /** 62 | * Utility function for computing EAN checksums 63 | * 64 | * @param string $input 65 | * 66 | * @return integer 67 | */ 68 | protected static function eanChecksum($input) 69 | { 70 | $sequence = (strlen($input) + 1) === 8 ? [3, 1] : [1, 3]; 71 | $sums = 0; 72 | foreach (str_split($input) as $n => $digit) { 73 | $sums += $digit * $sequence[$n % 2]; 74 | } 75 | return (10 - $sums % 10) % 10; 76 | } 77 | 78 | /** 79 | * ISBN-10 check digit 80 | * @link http://en.wikipedia.org/wiki/International_Standard_Book_Number#ISBN-10_check_digits 81 | * 82 | * @param string $input ISBN without check-digit 83 | * @throws LengthException When wrong input length passed 84 | * 85 | * @return string Check digit 86 | */ 87 | protected static function isbnChecksum($input): string 88 | { 89 | // We're calculating check digit for ISBN-10 90 | // so, the length of the input should be 9 91 | $length = 9; 92 | 93 | if (strlen($input) !== $length) { 94 | throw new LengthException(sprintf('Input length should be equal to %d', $length)); 95 | } 96 | 97 | $digits = str_split($input); 98 | array_walk( 99 | $digits, 100 | function (&$digit, $position) { 101 | $digit = (10 - $position) * $digit; 102 | } 103 | ); 104 | $result = (11 - array_sum($digits) % 11) % 11; 105 | 106 | // 10 is replaced by X 107 | return (string) (($result < 10) ? $result : 'X'); 108 | } 109 | 110 | private function ean($length = 13) 111 | { 112 | $code = static::numerify(str_repeat('#', $length - 1)); 113 | 114 | return $code . static::eanChecksum($code); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Provider/Person.php: -------------------------------------------------------------------------------- 1 | generator->parse($format); 60 | } 61 | 62 | /** 63 | * @param string|null $gender 'male', 'female' or null for any 64 | * @return string 65 | * @example 'John' 66 | */ 67 | public function firstName($gender = null) 68 | { 69 | if ($gender === static::GENDER_MALE) { 70 | return static::firstNameMale(); 71 | } 72 | elseif ($gender === static::GENDER_FEMALE) { 73 | return static::firstNameFemale(); 74 | } 75 | 76 | return $this->generator->parse(static::randomElement(static::$firstNameFormat)); 77 | } 78 | 79 | public static function firstNameMale() 80 | { 81 | return static::randomElement(static::$firstNameMale); 82 | } 83 | 84 | public static function firstNameFemale() 85 | { 86 | return static::randomElement(static::$firstNameFemale); 87 | } 88 | 89 | /** 90 | * @return string 91 | * @example 'Doe' 92 | */ 93 | public function lastName() 94 | { 95 | return static::randomElement(static::$lastName); 96 | } 97 | 98 | /** 99 | * @param string|null $gender 'male', 'female' or null for any 100 | * @return string 101 | * @example 'Mrs.' 102 | */ 103 | public function title($gender = null) 104 | { 105 | if ($gender === static::GENDER_MALE) { 106 | return static::titleMale(); 107 | } 108 | elseif ($gender === static::GENDER_FEMALE) { 109 | return static::titleFemale(); 110 | } 111 | 112 | return $this->generator->parse(static::randomElement(static::$titleFormat)); 113 | } 114 | 115 | /** 116 | * @example 'Mr.' 117 | */ 118 | public static function titleMale() 119 | { 120 | return static::randomElement(static::$titleMale); 121 | } 122 | 123 | /** 124 | * @example 'Mrs.' 125 | */ 126 | public static function titleFemale() 127 | { 128 | return static::randomElement(static::$titleFemale); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Provider/en_US/PhoneNumber.php: -------------------------------------------------------------------------------- 1 | generator->parse($format)); 71 | } 72 | 73 | /** 74 | * NPA-format area code 75 | * 76 | * @see https://en.wikipedia.org/wiki/North_American_Numbering_Plan#Numbering_system 77 | * 78 | * @return string 79 | */ 80 | public static function areaCode() 81 | { 82 | $digits[] = self::numberBetween(2, 9); 83 | $digits[] = self::randomDigit(); 84 | $digits[] = self::randomDigitNot($digits[1]); 85 | 86 | return join('', $digits); 87 | } 88 | 89 | /** 90 | * NXX-format central office exchange code 91 | * 92 | * @see https://en.wikipedia.org/wiki/North_American_Numbering_Plan#Numbering_system 93 | * 94 | * @return string 95 | */ 96 | public static function exchangeCode() 97 | { 98 | $digits[] = self::numberBetween(2, 9); 99 | $digits[] = self::randomDigit(); 100 | 101 | if ($digits[1] === 1) { 102 | $digits[] = self::randomDigitNot(1); 103 | } 104 | else { 105 | $digits[] = self::randomDigit(); 106 | } 107 | 108 | return join('', $digits); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Provider/Address.php: -------------------------------------------------------------------------------- 1 | generator->parse($format); 41 | } 42 | 43 | /** 44 | * @example 'Crist Parks' 45 | */ 46 | public function streetName() 47 | { 48 | $format = static::randomElement(static::$streetNameFormats); 49 | 50 | return $this->generator->parse($format); 51 | } 52 | 53 | /** 54 | * @example '791 Crist Parks' 55 | */ 56 | public function streetAddress() 57 | { 58 | $format = static::randomElement(static::$streetAddressFormats); 59 | 60 | return $this->generator->parse($format); 61 | } 62 | 63 | /** 64 | * @example '791 Crist Parks, Sashabury, IL 86039-9874' 65 | */ 66 | public function address() 67 | { 68 | $format = static::randomElement(static::$addressFormats); 69 | 70 | return $this->generator->parse($format); 71 | } 72 | 73 | /** 74 | * @example 'town' 75 | */ 76 | public static function citySuffix() 77 | { 78 | return static::randomElement(static::$citySuffix); 79 | } 80 | 81 | /** 82 | * @example 'Avenue' 83 | */ 84 | public static function streetSuffix() 85 | { 86 | return static::randomElement(static::$streetSuffix); 87 | } 88 | 89 | /** 90 | * @example '791' 91 | */ 92 | public static function buildingNumber() 93 | { 94 | return static::numerify(static::randomElement(static::$buildingNumber)); 95 | } 96 | 97 | /** 98 | * @example 86039-9874 99 | */ 100 | public static function postcode() 101 | { 102 | return static::toUpper(static::bothify(static::randomElement(static::$postcode))); 103 | } 104 | 105 | /** 106 | * @example 'Japan' 107 | */ 108 | public static function country():string 109 | { 110 | return static::randomElement(static::$country); 111 | } 112 | 113 | /** 114 | * 经纬度范围由于采取的数据是有限的,这里需要给予限制 115 | * @param float|int $min 116 | * @param float|int $max 117 | * @return float Uses signed degrees format (returns a float number between -90 and 90) 118 | * @example '77.147489' 119 | */ 120 | public static function latitude($min = -85.05, $max = 85.05): float 121 | { 122 | return static::randomFloat(6, $min, $max); 123 | } 124 | 125 | /** 126 | * @param float|int $min 127 | * @param float|int $max 128 | * @return float Uses signed degrees format (returns a float number between -180 and 180) 129 | * @example '86.211205' 130 | */ 131 | public static function longitude($min = -180, $max = 180) 132 | { 133 | return static::randomFloat(6, $min, $max); 134 | } 135 | 136 | /** 137 | * @return array [latitude, longitude] 138 | * @example array('77.147489', '86.211205') 139 | */ 140 | public static function localCoordinates() 141 | { 142 | return [ 143 | 'latitude' => static::latitude(), 144 | 'longitude' => static::longitude(), 145 | ]; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Provider/Image.php: -------------------------------------------------------------------------------- 1 | = 100 ? 100 : round($width / 10)); 54 | $url .= 'fs=' . $size . '&'; 55 | } 56 | 57 | return $baseUrl . rtrim($url, '&?'); 58 | } 59 | 60 | /** 61 | * Download a remote random image to disk and return its location 62 | * 63 | * Requires curl, or allow_url_fopen to be on in php.ini. 64 | * 65 | * @param null $dir 66 | * @param int $width 67 | * @param int $height 68 | * @param bool $fullPath 69 | * @return false|RuntimeException|string 70 | * @example '/path/to/dir/13b73edae8443990be1aa8f1a483bc27.jpg' 71 | */ 72 | public static function image($dir = null, $width = 640, $height = 480, $fullPath = true) 73 | { 74 | $dir = is_null($dir) ? sys_get_temp_dir() : $dir; // GNU/Linux / OS X / Windows compatible 75 | // Validate directory path 76 | if (!is_dir($dir) || !is_writable($dir)) { 77 | throw new InvalidArgumentException(sprintf('Cannot write to directory "%s"', $dir)); 78 | } 79 | 80 | // Generate a random filename. Use the server address so that a file 81 | // generated at the same time on a different server won't have a collision. 82 | $name = md5(uniqid(empty($_SERVER['SERVER_ADDR']) ? '' : $_SERVER['SERVER_ADDR'], true)); 83 | $filename = $name . '.jpg'; 84 | $filepath = $dir . DIRECTORY_SEPARATOR . $filename; 85 | 86 | $url = static::imageUrl($width, $height); 87 | 88 | // save file 89 | if (function_exists('curl_exec')) { 90 | // use cURL 91 | $fp = fopen($filepath, 'w'); 92 | $ch = curl_init($url); 93 | curl_setopt($ch, CURLOPT_FILE, $fp); 94 | $success = curl_exec($ch) && curl_getinfo($ch, CURLINFO_HTTP_CODE) === 200; 95 | fclose($fp); 96 | curl_close($ch); 97 | 98 | if (!$success) { 99 | unlink($filepath); 100 | 101 | // could not contact the distant URL or HTTP error - fail silently. 102 | return false; 103 | } 104 | } 105 | elseif (ini_get('allow_url_fopen')) { 106 | // use remote fopen() via copy() 107 | copy($url, $filepath); 108 | } 109 | else { 110 | return new RuntimeException('The image formatter downloads an image from a remote HTTP server. Therefore, it requires that PHP can request remote hosts, either via cURL or fopen()'); 111 | } 112 | 113 | return $fullPath ? $filepath : $filename; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /tests/JsonSchema/HelperTest.php: -------------------------------------------------------------------------------- 1 | true, 'maximum' => $maximum]; 21 | 22 | $actual = (new Faker)->getMaximum($schema); 23 | 24 | // -1 mean exclusive 25 | $this->assertSame($actual, $maximum - 1); 26 | } 27 | 28 | public function testGetMaximumMustReturnMaximumIfExclusiveMaximumFalse() 29 | { 30 | $maximum = 300; 31 | $schema = (object) ['exclusiveMaximum' => false, 'maximum' => $maximum]; 32 | 33 | $actual = (new Faker)->getMaximum($schema); 34 | 35 | $this->assertSame($actual, $maximum); 36 | } 37 | 38 | public function testGetMaximumMustReturnMaximumIfExclusiveMaximumAbsent() 39 | { 40 | $maximum = 300; 41 | $schema = (object) ['maximum' => $maximum]; 42 | 43 | $actual = (new Faker)->getMaximum($schema); 44 | 45 | $this->assertSame($actual, $maximum); 46 | } 47 | 48 | public function testGetMinimumMustReturnMinimumMinusOneIfExclusiveMinimumTrue() 49 | { 50 | $minimum = 300; 51 | $schema = (object) ['exclusiveMinimum' => true, 'minimum' => $minimum]; 52 | 53 | $actual = (new Faker)->getMinimum($schema); 54 | 55 | // +1 mean exclusive 56 | $this->assertSame($actual, $minimum + 1); 57 | } 58 | 59 | public function testGetMinimumMustReturnMinimumIfExclusiveMinimumFalse() 60 | { 61 | $minimum = 300; 62 | $schema = (object) ['exclusiveMinimum' => false, 'minimum' => $minimum]; 63 | 64 | $actual = (new Faker)->getMinimum($schema); 65 | 66 | $this->assertSame($actual, $minimum); 67 | } 68 | 69 | public function testGetMinimumMustReturnMinimumIfExclusiveMinimumAbsent() 70 | { 71 | $minimum = 300; 72 | $schema = (object) ['minimum' => $minimum]; 73 | 74 | $actual = (new Faker)->getMinimum($schema); 75 | 76 | $this->assertSame($actual, $minimum); 77 | } 78 | 79 | public function testGetMultipleOfMustReturnValueIfPresent() 80 | { 81 | $expected = 7; 82 | $schema = (object) ['multipleOf' => $expected]; 83 | 84 | $actual = (new Faker)->getMultipleOf($schema); 85 | 86 | $this->assertSame($actual, $expected); 87 | } 88 | 89 | public function testGetMultipleOfMustReturnOneIfAbsent() 90 | { 91 | $expected = 1; 92 | $schema = (object) []; 93 | 94 | $actual = (new Faker)->getMultipleOf($schema); 95 | 96 | $this->assertSame($actual, $expected); 97 | } 98 | 99 | public function testGetInternetFakerInstanceMustReturnInstance() 100 | { 101 | $actual = (new Faker)->getInternetFakerInstance(); 102 | 103 | $this->assertTrue($actual instanceof Internet); 104 | } 105 | 106 | /** 107 | * @dataProvider getFormats 108 | */ 109 | public function testGetFormattedValueMustReturnValidValue($format) 110 | { 111 | $schema = (object) ['type' => 'string', 'format' => $format]; 112 | $validator = new Validator(); 113 | 114 | $actual = (new Faker)->getFormattedValue($schema); 115 | $validator->validate($actual, $schema); 116 | 117 | $this->assertTrue($validator->isValid()); 118 | } 119 | 120 | public function testGetFormattedValueMustThrowExceptionIfInvalidFormat() 121 | { 122 | $this->expectException(Exception::class); 123 | 124 | (new Faker)->getFormattedValue((object) ['format' => 'xxxxx']); 125 | } 126 | 127 | /** 128 | * @see testGetFormattedValueMustReturnValidValue 129 | * @SuppressWarnings(PHPMD.UnusedPrivateMethod) 130 | */ 131 | public function getFormats(): array 132 | { 133 | return [ 134 | ['date-time'], 135 | ['email'], 136 | ['hostname'], 137 | ['ipv4'], 138 | ['ipv6'], 139 | ['uri'], 140 | ]; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Provider/Color.php: -------------------------------------------------------------------------------- 1 | city() . static::area(); 140 | } 141 | 142 | public static function postcode() 143 | { 144 | $prefix = str_pad(mt_rand(1, 85), 2, 0, STR_PAD_LEFT); 145 | $suffix = '00'; 146 | 147 | return $prefix . mt_rand(10, 88) . $suffix; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/Provider/Text.php: -------------------------------------------------------------------------------- 1 | 5) { 47 | throw new InvalidArgumentException('indexSize must be at most 5'); 48 | } 49 | 50 | $words = $this->getConsecutiveWords($indexSize); 51 | $result = []; 52 | $resultLength = 0; 53 | // take a random starting point 54 | $next = static::randomKey($words); 55 | while ($resultLength < $maxNbChars && isset($words[$next])) { 56 | // fetch a random word to append 57 | $word = static::randomElement($words[$next]); 58 | 59 | // calculate next index 60 | $currentWords = static::explode($next); 61 | $currentWords[] = $word; 62 | array_shift($currentWords); 63 | $next = static::implode($currentWords); 64 | 65 | // ensure text starts with an uppercase letter 66 | if ($resultLength == 0 && !static::validStart($word)) { 67 | continue; 68 | } 69 | 70 | // append the element 71 | $result[] = $word; 72 | $resultLength += static::strlen($word) + static::$separatorLen; 73 | } 74 | 75 | // remove the element that caused the text to overflow 76 | array_pop($result); 77 | 78 | // build result 79 | $result = static::implode($result); 80 | 81 | return static::appendEnd($result); 82 | } 83 | 84 | protected function getConsecutiveWords($indexSize) 85 | { 86 | if (!isset($this->consecutiveWords[$indexSize])) { 87 | $parts = $this->getExplodedText(); 88 | $words = []; 89 | $index = []; 90 | for ($i = 0; $i < $indexSize; $i++) { 91 | $index[] = array_shift($parts); 92 | } 93 | 94 | for ($i = 0, $count = count($parts); $i < $count; $i++) { 95 | $stringIndex = static::implode($index); 96 | if (!isset($words[$stringIndex])) { 97 | $words[$stringIndex] = []; 98 | } 99 | $word = $parts[$i]; 100 | $words[$stringIndex][] = $word; 101 | array_shift($index); 102 | $index[] = $word; 103 | } 104 | // cache look up words for performance 105 | $this->consecutiveWords[$indexSize] = $words; 106 | } 107 | 108 | return $this->consecutiveWords[$indexSize]; 109 | } 110 | 111 | protected function getExplodedText() 112 | { 113 | if ($this->explodedText === null) { 114 | $this->explodedText = static::explode(preg_replace('/\s+/u', ' ', static::$baseText)); 115 | } 116 | 117 | return $this->explodedText; 118 | } 119 | 120 | protected static function explode($text) 121 | { 122 | return explode(static::$separator, $text); 123 | } 124 | 125 | protected static function implode($words) 126 | { 127 | return implode(static::$separator, $words); 128 | } 129 | 130 | protected static function strlen($text) 131 | { 132 | return function_exists('mb_strlen') ? mb_strlen($text, 'UTF-8') : strlen($text); 133 | } 134 | 135 | protected static function validStart($word) 136 | { 137 | $isValid = true; 138 | if (static::$textStartsWithUppercase) { 139 | $isValid = preg_match('/^\p{Lu}/u', $word); 140 | } 141 | return $isValid; 142 | } 143 | 144 | protected static function appendEnd($text) 145 | { 146 | return preg_replace("/([ ,-:;\x{2013}\x{2014}]+$)/us", '', $text) . '.'; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /tests/JsonSchema/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}": { 43 | "type": "string" 44 | }, 45 | "^I_[A-Z]{3}[0-9]{4,8}": { 46 | "type": "integer" 47 | } 48 | }, 49 | "additionalProperties": false 50 | }, 51 | "typeAndPatternPropertiesAndAdditionalPropertiesTrue": { 52 | "type": "object", 53 | "patternProperties": { 54 | "^S_[a-z]{0,20}": { 55 | "type": "string" 56 | }, 57 | "^I_[A-Z]{3}[0-9]{4,8}": { 58 | "type": "integer" 59 | } 60 | }, 61 | "additionalProperties": true 62 | }, 63 | "typeAndPropertiesAndPatternPropertiesAndAdditionalPropertiesObject": { 64 | "type": "object", 65 | "properties": { 66 | "builtin": { 67 | "type": "number" 68 | } 69 | }, 70 | "patternProperties": { 71 | "^S_[a-z]{0,20}": { 72 | "type": "string" 73 | }, 74 | "^I_[A-Z]{3}[0-9]{4,8}": { 75 | "type": "integer" 76 | } 77 | }, 78 | "additionalProperties": { 79 | "type": "string" 80 | } 81 | }, 82 | "typeAndPropertiesAndPatternPropertiesAndAdditionalPropertiesObjectAndMinProperties": { 83 | "type": "object", 84 | "minProperties": 3, 85 | "properties": { 86 | "builtin": { 87 | "type": "number" 88 | } 89 | }, 90 | "patternProperties": { 91 | "^S_[a-z]{0,20}": { 92 | "type": "string" 93 | }, 94 | "^I_[A-Z]{3}[0-9]{4,8}": { 95 | "type": "integer" 96 | } 97 | }, 98 | "additionalProperties": { 99 | "type": "string" 100 | } 101 | }, 102 | "typeAndPropertiesAndPatternPropertiesAndAdditionalPropertiesObjectAndMinPropertiesAndRequied": { 103 | "type": "object", 104 | "minProperties": 3, 105 | "required": [ 106 | "builtin" 107 | ], 108 | "properties": { 109 | "builtin": { 110 | "type": "number" 111 | } 112 | }, 113 | "patternProperties": { 114 | "^S_[a-z]{0,20}": { 115 | "type": "string" 116 | }, 117 | "^I_[A-Z]{3}[0-9]{4,8}": { 118 | "type": "integer" 119 | } 120 | }, 121 | "additionalProperties": { 122 | "type": "string" 123 | } 124 | }, 125 | "deepNested": { 126 | "type": "object", 127 | "required": [ 128 | "first" 129 | ], 130 | "properties": { 131 | "first": { 132 | "type": "object", 133 | "required": [ 134 | "second" 135 | ], 136 | "properties": { 137 | "second": { 138 | "type": "object", 139 | "required": [ 140 | "third" 141 | ], 142 | "properties": { 143 | "third": { 144 | "type": "object" 145 | } 146 | } 147 | } 148 | } 149 | } 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Guesser/Name.php: -------------------------------------------------------------------------------- 1 | generator = $generator; 18 | } 19 | 20 | /** 21 | * @param string $name 22 | * @param int|null $size Length of field, if known 23 | * @return callable 24 | */ 25 | public function guessFormat($name, $size = null) 26 | { 27 | $name = Base::toLower($name); 28 | $generator = $this->generator; 29 | if (preg_match('/^is[_A-Z]/', $name)) { 30 | return function () use ($generator) { 31 | return $generator->boolean; 32 | }; 33 | } 34 | if (preg_match('/(_a|A)t$/', $name)) { 35 | return function () use ($generator) { 36 | return $generator->dateTime; 37 | }; 38 | } 39 | switch (str_replace('_', '', $name)) { 40 | case 'firstname': 41 | return function () use ($generator) { 42 | return $generator->firstName; 43 | }; 44 | case 'lastname': 45 | return function () use ($generator) { 46 | return $generator->lastName; 47 | }; 48 | case 'username': 49 | case 'login': 50 | return function () use ($generator) { 51 | return $generator->userName; 52 | }; 53 | case 'email': 54 | case 'emailaddress': 55 | return function () use ($generator) { 56 | return $generator->email; 57 | }; 58 | case 'phonenumber': 59 | case 'phone': 60 | case 'telephone': 61 | case 'telnumber': 62 | return function () use ($generator) { 63 | return $generator->phoneNumber; 64 | }; 65 | case 'address': 66 | return function () use ($generator) { 67 | return $generator->address; 68 | }; 69 | case 'city': 70 | case 'town': 71 | return function () use ($generator) { 72 | return $generator->city; 73 | }; 74 | case 'streetaddress': 75 | return function () use ($generator) { 76 | return $generator->streetAddress; 77 | }; 78 | case 'postcode': 79 | case 'zipcode': 80 | return function () use ($generator) { 81 | return $generator->postcode; 82 | }; 83 | case 'state': 84 | return function () use ($generator) { 85 | return $generator->state; 86 | }; 87 | case 'county': 88 | if ($this->generator->locale == 'en_US') { 89 | return function () use ($generator) { 90 | return sprintf('%s County', $generator->city); 91 | }; 92 | } 93 | 94 | return function () use ($generator) { 95 | return $generator->state; 96 | }; 97 | case 'country': 98 | switch ($size) { 99 | case 2: 100 | return function () use ($generator) { 101 | return $generator->countryCode; 102 | }; 103 | case 3: 104 | return function () use ($generator) { 105 | return $generator->countryISOAlpha3; 106 | }; 107 | case 5: 108 | case 6: 109 | return function () use ($generator) { 110 | return $generator->locale; 111 | }; 112 | default: 113 | return function () use ($generator) { 114 | return $generator->country; 115 | }; 116 | } 117 | case 'locale': 118 | return function () use ($generator) { 119 | return $generator->locale; 120 | }; 121 | case 'currency': 122 | case 'currencycode': 123 | return function () use ($generator) { 124 | return $generator->currencyCode; 125 | }; 126 | case 'url': 127 | case 'website': 128 | return function () use ($generator) { 129 | return $generator->url; 130 | }; 131 | case 'company': 132 | case 'companyname': 133 | case 'employer': 134 | return function () use ($generator) { 135 | return $generator->company; 136 | }; 137 | case 'title': 138 | if ($size !== null && $size <= 10) { 139 | return function () use ($generator) { 140 | return $generator->title; 141 | }; 142 | } 143 | 144 | return function () use ($generator) { 145 | return $generator->sentence; 146 | }; 147 | case 'body': 148 | case 'summary': 149 | case 'article': 150 | case 'description': 151 | return function () use ($generator) { 152 | return $generator->text; 153 | }; 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Provider/zh_CN/Company.php: -------------------------------------------------------------------------------- 1 | 10, 12 | 'B' => 11, 13 | 'C' => 12, 14 | 'D' => 13, 15 | 'E' => 14, 16 | 'F' => 15, 17 | 'G' => 16, 18 | 'H' => 17, 19 | 'I' => 34, 20 | 'J' => 18, 21 | 'K' => 19, 22 | 'M' => 21, 23 | 'N' => 22, 24 | 'O' => 35, 25 | 'P' => 23, 26 | 'Q' => 24, 27 | 'T' => 27, 28 | 'U' => 28, 29 | 'V' => 29, 30 | 'W' => 32, 31 | 'X' => 30, 32 | 'Z' => 33, 33 | ]; 34 | 35 | /** 36 | * @see https://zh.wikipedia.org/wiki/%E4%B8%AD%E8%8F%AF%E6%B0%91%E5%9C%8B%E5%9C%8B%E6%B0%91%E8%BA%AB%E5%88%86%E8%AD%89 37 | */ 38 | public static array $idDigitValidator = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1]; 39 | 40 | protected static array $maleNameFormats = [ 41 | '{{lastName}}{{firstNameMale}}', 42 | ]; 43 | 44 | protected static array $femaleNameFormats = [ 45 | '{{lastName}}{{firstNameFemale}}', 46 | ]; 47 | 48 | protected static array $titleMale = ['先生', '博士', '教授']; 49 | 50 | protected static array $titleFemale = ['小姐', '太太', '博士', '教授']; 51 | 52 | /** 53 | * @link http://zh.wikipedia.org/wiki/%E7%99%BE%E5%AE%B6%E5%A7%93 54 | */ 55 | protected static array $lastName = [ 56 | '趙', '錢', '孫', '李', '周', '吳', '鄭', '王', '馮', 57 | '陳', '褚', '衛', '蔣', '沈', '韓', '楊', '朱', '秦', 58 | '尤', '許', '何', '呂', '施', '張', '孔', '曹', '嚴', 59 | '華', '金', '魏', '陶', '姜', '戚', '謝', '鄒', '喻', 60 | '柏', '水', '竇', '章', '雲', '蘇', '潘', '葛', 61 | '奚', '范', '彭', '郎', '魯', '韋', '昌', '馬', 62 | '苗', '鳳', '花', '方', '俞', '任', '袁', '柳', 63 | '酆', '鮑', '史', '唐', '費', '廉', '岑', '薛', 64 | '雷', '賀', '倪', '湯', '滕', '殷', '羅', '畢', 65 | '郝', '鄔', '安', '常', '樂', '于', '時', '傅', 66 | '皮', '卞', '齊', '康', '伍', '余', '元', '卜', 67 | '顧', '孟', '平', '黃', '和', '穆', '蕭', '尹', 68 | '姚', '邵', '湛', '汪', '祁', '毛', '禹', '狄', 69 | '米', '貝', '明', '臧', '計', '伏', '成', '戴', 70 | '談', '宋', '茅', '龐', '熊', '紀', '舒', '屈', 71 | '項', '祝', '董', '梁', '杜', '阮', '藍', '閔', 72 | '席', '季', '麻', '強', '賈', '路', '婁', '危', 73 | '江', '童', '顏', '郭', '梅', '盛', '林', '刁', 74 | '鍾', '徐', '丘', '駱', '高', '夏', '蔡', '田', 75 | '樊', '胡', '凌', '霍', '虞', '萬', '支', '柯', 76 | '昝', '管', '盧', '莫', '經', '房', '裘', '繆', 77 | '干', '解', '應', '宗', '丁', '宣', '賁', '鄧', 78 | '郁', '單', '杭', '洪', '包', '諸', '左', '石', 79 | '崔', '吉', '鈕', '龔', '程', '嵇', '邢', '滑', 80 | '裴', '陸', '榮', '翁', '荀', '羊', '於', '惠', 81 | '甄', '麴', '家', '封', '芮', '羿', '儲', '靳', 82 | '汲', '邴', '糜', '松', '井', '段', '富', '巫', 83 | '烏', '焦', '巴', '弓', '牧', '隗', '山', '谷', 84 | '車', '侯', '宓', '蓬', '全', '郗', '班', '仰', 85 | '秋', '仲', '伊', '宮', '甯', '仇', '欒', '暴', 86 | '甘', '鈄', '厲', '戎', '祖', '武', '符', '劉', 87 | '景', '詹', '束', '龍', '葉', '幸', '司', '韶', 88 | '郜', '黎', '薊', '薄', '印', '宿', '白', '懷', 89 | '蒲', '邰', '從', '鄂', '索', '咸', '籍', '賴', 90 | '卓', '藺', '屠', '蒙', '池', '喬', '陰', '鬱', 91 | '胥', '能', '蒼', '雙', '聞', '莘', '黨', '翟', 92 | '譚', '貢', '勞', '逄', '姬', '申', '扶', '堵', 93 | '冉', '宰', '酈', '雍', '郤', '璩', '桑', '桂', 94 | '濮', '牛', '壽', '通', '邊', '扈', '燕', '冀', 95 | '郟', '浦', '尚', '農', '溫', '別', '莊', '晏', 96 | '柴', '瞿', '閻', '充', '慕', '連', '茹', '習', 97 | '宦', '艾', '魚', '容', '向', '古', '易', '慎', 98 | '戈', '廖', '庾', '終', '暨', '居', '衡', '步', 99 | '都', '耿', '滿', '弘', '匡', '國', '文', '寇', 100 | '廣', '祿', '闕', '東', '歐', '殳', '沃', '利', 101 | '蔚', '越', '夔', '隆', '師', '鞏', '厙', '聶', 102 | '晁', '勾', '敖', '融', '冷', '訾', '辛', '闞', 103 | '那', '簡', '饒', '空', '曾', '毋', '沙', '乜', 104 | '養', '鞠', '須', '豐', '巢', '關', '蒯', '相', 105 | '查', '后', '荊', '紅', '游', '竺', '權', '逯', 106 | '蓋', '益', '桓', '公', '万俟', '司馬', '上官', 107 | '歐陽', '夏侯', '諸葛', '聞人', '東方', '赫連', 108 | '皇甫', '尉遲', '公羊', '澹臺', '公冶', '宗政', 109 | '濮陽', '淳于', '單于', '太叔', '申屠', '公孫', 110 | '仲孫', '軒轅', '令狐', '鍾離', '宇文', '長孫', 111 | '慕容', '鮮于', '閭丘', '司徒', '司空', '亓官', 112 | '司寇', '仉', '督', '子車', '顓孫', '端木', '巫馬', 113 | '公西', '漆雕', '樂正', '壤駟', '公良', '拓跋', 114 | '夾谷', '宰父', '穀梁', '晉', '楚', '閆', '法', 115 | '汝', '鄢', '涂', '欽', '段干', '百里', '東郭', 116 | '南門', '呼延', '歸', '海', '羊舌', '微生', '岳', 117 | '帥', '緱', '亢', '況', '後', '有', '琴', '梁丘', 118 | '左丘', '東門', '西門', '商', '牟', '佘', '佴', 119 | '伯', '賞', '南宮', '墨', '哈', '譙', '笪', '年', 120 | '愛', '陽', '佟', '第五', '言', '福', 121 | ]; 122 | 123 | /** 124 | * @link http://technology.chtsai.org/namefreq/ 125 | */ 126 | protected static $characterMale = [ 127 | '佳', '俊', '信', '偉', '傑', '冠', '君', '哲', 128 | '嘉', '威', '宇', '安', '宏', '宗', '宜', '家', 129 | '庭', '廷', '建', '彥', '心', '志', '思', '承', 130 | '文', '柏', '樺', '瑋', '穎', '美', '翰', '華', 131 | '詩', '豪', '賢', '軒', '銘', '霖', 132 | ]; 133 | 134 | protected static $characterFemale = [ 135 | '伶', '佩', '佳', '依', '儀', '冠', '君', '嘉', 136 | '如', '娟', '婉', '婷', '安', '宜', '家', '庭', 137 | '心', '思', '怡', '惠', '慧', '文', '欣', '涵', 138 | '淑', '玲', '珊', '琪', '琬', '瑜', '穎', '筑', 139 | '筱', '美', '芬', '芳', '華', '萍', '萱', '蓉', 140 | '詩', '貞', '郁', '鈺', '雅', '雯', '靜', '馨', 141 | ]; 142 | 143 | public static function randomName($pool, $n) 144 | { 145 | $name = ''; 146 | for ($i = 0; $i < $n; ++$i) { 147 | $name .= static::randomElement($pool); 148 | } 149 | return $name; 150 | } 151 | 152 | public static function firstNameMale() 153 | { 154 | return static::randomName(static::$characterMale, mt_rand(1, 2)); 155 | } 156 | 157 | public static function firstNameFemale() 158 | { 159 | return static::randomName(static::$characterFemale, mt_rand(1, 2)); 160 | } 161 | 162 | public static function suffix() 163 | { 164 | return ''; 165 | } 166 | 167 | /** 168 | * @param string $gender Person::GENDER_MALE || Person::GENDER_FEMALE 169 | * 170 | * @return string Length 10 alphanumeric characters, begins with 1 latin character (birthplace), 171 | * 1 number (gender) and then 8 numbers (the last one is check digit). 172 | * @see https://en.wikipedia.org/wiki/National_Identification_Card_(Republic_of_China) 173 | * 174 | */ 175 | public function personalIdentityNumber($gender = null) 176 | { 177 | $birthPlace = self::randomKey(self::$idBirthplaceCode); 178 | $birthPlaceCode = self::$idBirthplaceCode[$birthPlace]; 179 | 180 | $gender = ($gender != null) ? $gender : self::randomElement([self::GENDER_FEMALE, self::GENDER_MALE]); 181 | $genderCode = ($gender === self::GENDER_MALE) ? 1 : 2; 182 | 183 | $randomNumberCode = self::randomNumber(7, true); 184 | 185 | $codes = str_split($birthPlaceCode . $genderCode . $randomNumberCode); 186 | $total = 0; 187 | 188 | foreach ($codes as $key => $code) { 189 | $total += $code * self::$idDigitValidator[$key]; 190 | } 191 | 192 | $checkSumDigit = 10 - ($total % 10); 193 | 194 | if ($checkSumDigit == 10) { 195 | $checkSumDigit = 0; 196 | } 197 | 198 | return $birthPlace . $genderCode . $randomNumberCode . $checkSumDigit; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/Provider/zh_TW/Company.php: -------------------------------------------------------------------------------- 1 | generator->parse($format); 231 | } 232 | 233 | public static function companyModifier() 234 | { 235 | return static::randomElement(static::$companyModifier); 236 | } 237 | 238 | public static function companyPrefix() 239 | { 240 | return static::randomElement(static::$companyPrefix); 241 | } 242 | 243 | public function catchPhrase() 244 | { 245 | return static::randomElement(static::$catchPhrase); 246 | } 247 | 248 | public function bs() 249 | { 250 | $result = ''; 251 | foreach (static::$bsWords as &$word) { 252 | $result .= static::randomElement($word); 253 | } 254 | return $result; 255 | } 256 | 257 | /** 258 | * return standard VAT / Tax ID / Uniform Serial Number 259 | * 260 | * @return int 261 | * @example 28263822 262 | * 263 | */ 264 | public function VAT() 265 | { 266 | return static::randomNumber(8, true); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/Provider/Lorem.php: -------------------------------------------------------------------------------- 1 | 24 | {$desc}{$codeHl}{$content}"; 25 | } 26 | 27 | } 28 | 29 | $md = isset($_GET['md']); 30 | 31 | /** 32 | * @param $title 33 | * @param $items 34 | * @param bool $md 35 | */ 36 | function py_example($title, $items, $md = true) 37 | { 38 | if ($md) { 39 | echo PHP_EOL . PHP_EOL . '### ' . $title . PHP_EOL . PHP_EOL; 40 | } 41 | else { 42 | echo "

{$title}

"; 43 | } 44 | 45 | if ($md) { 46 | echo '```' . PHP_EOL; 47 | } 48 | $maxLength = 0; 49 | foreach ($items as $item) { 50 | $length = strlen((string) $item[1]); 51 | $maxLength = $length > $maxLength ? $length : $maxLength; 52 | } 53 | 54 | if ($maxLength > 50) { 55 | $maxLength = 50; 56 | } 57 | 58 | foreach ($items as $item) { 59 | py_faker_desc($item[0], (string) $item[1], $maxLength, $md); 60 | } 61 | if ($md) { 62 | echo '```' . PHP_EOL; 63 | } 64 | } 65 | 66 | 67 | py_example('Base', [ 68 | ['生成随机整数 0 - 9', '$faker->randomDigit;'], 69 | ['生成唯一整数', '$faker->unique()->randomDigit;'], 70 | ['生成随机不为空的整数', '$faker->randomDigitNotNull;'], 71 | ['生成随机数字', '$faker->randomNumber($nbDigits = 5, $strict = false);'], 72 | ['生成随机浮点数', '$faker->randomFloat($nbMaxDecimals = null, $min = 0, $max = null);'], 73 | ['在指定范围内生成随机数', '$faker->numberBetween($min = 1000, $max = 9000);'], 74 | ['生成随机字符', '$faker->randomLetter;'], 75 | ['在给定的数组中,随机生成给定的个数字符', '$faker->randomElements($array = [\'a\', \'b\', \'c\'], $count = 2);'], 76 | ['在给定的数组中,生成单个随机字符', '$faker->randomElement($array = [\'a\', \'b\', \'c\']);'], 77 | ['打乱给定的字符串', '$faker->shuffle(\'hello, world\');'], 78 | ['打乱给定的数组', '$faker->shuffle([1, 2, 3]);'], 79 | ['给占位符生成随机整数 (数字为#)', '$faker->numerify(\'Hello ###\');'], 80 | ['给占位符生成随机字符串 (字符串为?)', '$faker->lexify(\'Hello ???\');'], 81 | ['给占位符生成混合的随机字符串', '$faker->bothify(\'Hello ##??\');'], 82 | ['给占位符生成随机的字符(字母、数字、符号)', '$faker->asciify(\'Hello ***\');'], 83 | ['根据正则规则生成随机字符', '$faker->regexify(\'[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\');'], 84 | ], $md); 85 | 86 | 87 | py_example('Lorem', [ 88 | ['生成随机个数的字符串', '$faker->word;'], 89 | ['随机生成指定个数的字符串', '$faker->words($nb = 3, $asText = false);'], 90 | ['随机生成一条语句', '$faker->sentence($nbWords = 6, $variableNbWords = true);'], 91 | ['随机生成指定条数的语句', '$faker->sentences($nb = 3, $asText = false);'], 92 | ['随机生成一个段落', '$faker->paragraph($nbSentences = 3, $variableNbSentences = true);'], 93 | ['随机生成指定个数段落', '$faker->paragraphs($nb = 3, $asText = false);'], 94 | ['随机生成一个文本', '$faker->text($maxNbChars = 200);'], 95 | ['随机生成一句诗词', '$faker->poem;'], 96 | ], $md); 97 | 98 | 99 | py_example('Person', [ 100 | ['职位', '$faker->title;'], 101 | ['称谓', '$faker->titleMale;'], 102 | ['女性称谓', '$faker->titleFemale;'], 103 | ['姓名', '$faker->name'], 104 | ['名字', '$faker->firstName'], 105 | ['男性名字', '$faker->firstNameMale'], 106 | ['女性名字', '$faker->firstNameFemale'], 107 | ['姓', '$faker->lastName'], 108 | ['随机生成一个可以校验的身份证号', '$faker->idNumber'], 109 | ], $md); 110 | 111 | 112 | py_example('Address', [ 113 | ['随机生成省份/州', '$faker->state;'], 114 | ['随机城市省份/州缩写', '$faker->stateAbbr;'], 115 | ['随机生成城市后缀', '$faker->citySuffix;'], 116 | ['随机生成街道后缀', '$faker->streetSuffix;'], 117 | ['随机生成建筑编号', '$faker->buildingNumber;'], 118 | ['随机生成城市', '$faker->city;'], 119 | ['随机生成街道名', '$faker->streetName; '], 120 | ['随机生成街道地址', '$faker->streetAddress;'], 121 | ['随机生成邮编', '$faker->postcode;'], 122 | ['随机生成地址', '$faker->address;'], 123 | ['随机生成国家', '$faker->country;'], 124 | ['随机生成纬度', '$faker->latitude($min = -90, $max = 90);'], 125 | ['随机生成经度', '$faker->longitude($min = -180, $max = 180);'], 126 | ], $md); 127 | 128 | 129 | py_example('Phone Number', [ 130 | ['生成随机电话号码', '$faker->phoneNumber;'], 131 | ['随机生成e164电话', '$faker->e164PhoneNumber;'], 132 | ], $md); 133 | 134 | 135 | py_example('Company', [ 136 | ['随机生成公司', '$faker->company;'], 137 | ['随机生成公司后缀', '$faker->companySuffix;'], 138 | ['随机生成职务', '$faker->jobTitle;'], 139 | ['随机生成广告语', '$faker->catchPhrase;'], 140 | ], $md); 141 | 142 | 143 | py_example('Text', [ 144 | ['随机生成一段文本', '$faker->realText($maxNbChars = 200, $indexSize = 2);'], 145 | ], $md); 146 | 147 | 148 | py_example('Datetime', [ 149 | ['随机生成时间戳', '$faker->unixTime($max = \'now\');'], 150 | ['随机生成时间', '$faker->dateTime($max = \'now\', $timezone = date_default_timezone_get());'], 151 | [' dateTimeAd', '$faker->dateTimeAD;'], 152 | ['随机生成ios8601时间', '$faker->iso8601($max = \'now\');'], 153 | ['根据格式随机生成日期', '$faker->date($format = \'Y-m-d\', $max = \'now\');'], 154 | ['根据格式随机生成时间', '$faker->time($format = \'H:i:s\', $max = \'now\');'], 155 | ['生成指定范围的时间', '$faker->dateTimeBetween($startDate = \'-30 years\', $endDate = \'now\');'], 156 | ['随机生成一个指定间隔的时间', '$faker->dateTimeInInterval($startDate = \'-30 years\', $interval = \'+ 5 days\', $timezone = date_default_timezone_get());'], 157 | ['随机生成当前世纪的时间', '$faker->dateTimeThisCentury($max = \'now\', $timezone = date_default_timezone_get());'], 158 | ['随机生成当前十年的时间', '$faker->dateTimeThisDecade($max = \'now\', $timezone = date_default_timezone_get());'], 159 | ['随机生成当前年的时间', '$faker->dateTimeThisYear($max = \'now\', $timezone = date_default_timezone_get());'], 160 | ['随机生成当前月的时间', '$faker->dateTimeThisMonth($max = \'now\', $timezone = date_default_timezone_get());'], 161 | ['随机生成 am/pm', '$faker->amPm($max = \'now\');'], 162 | ['随机生成月份的某一天', '$faker->dayOfMonth($max = \'now\');'], 163 | ['随机生成星期', '$faker->dayOfWeek($max = \'now\');'], 164 | ['随机生成月份', '$faker->month($max = \'now\');'], 165 | ['随机生成月份的名称', '$faker->monthName($max = \'now\');'], 166 | ['随机生成年份', '$faker->year($max = \'now\');'], 167 | ['随机生成世纪', '$faker->century;'], 168 | ['随机生成时区', '$faker->timezone;'], 169 | ], $md); 170 | 171 | 172 | py_example('Internet', [ 173 | ['随机生成邮箱地址', '$faker->email;'], 174 | ['随机生成安全的邮箱地址', '$faker->safeEmail;'], 175 | ['随机生成免费的邮箱地址', '$faker->freeEmail;'], 176 | ['随机生成公司邮箱地址', '$faker->companyEmail;'], 177 | ['随机生成免费邮箱域名', '$faker->freeEmailDomain;'], 178 | ['随机生成安全邮箱域名', '$faker->safeEmailDomain;'], 179 | ['随机生成用户名', '$faker->userName;'], 180 | ['随机生成密码', '$faker->password;'], 181 | ['随机生成域名', '$faker->domainName;'], 182 | ['随机生成域', '$faker->domainWord;'], 183 | ['todo Tld', '$faker->tld;'], 184 | ['随机生成url地址', '$faker->url;'], 185 | ['随机生成块', '$faker->slug;'], 186 | ['随机生成ipv4地址', '$faker->ipv4;'], 187 | ['随机生成本地ipv4地址', '$faker->localIpv4;'], 188 | ['随机生成ipv6地址', '$faker->ipv6;'], 189 | ['随机生成mac地址', '$faker->macAddress;'], 190 | ], $md); 191 | 192 | 193 | py_example('UserAgent', [ 194 | ['用户代理', '$faker->userAgent;'], 195 | ['谷歌', '$faker->chrome;'], 196 | ['火狐', '$faker->firefox; '], 197 | ['Safari', '$faker->safari; '], 198 | ['欧朋', '$faker->opera;'], 199 | ['ie', '$faker->internetExplorer;'], 200 | ], $md); 201 | 202 | 203 | py_example('Payment', [ 204 | ['随机生成信用卡类型', '$faker->creditCardType;'], 205 | ['随机生成信用卡号', '$faker->creditCardNumber;'], 206 | ['随机生成信用卡有效日期', '$faker->creditCardExpirationDate;'], 207 | ['随机生成信用卡有效日期', '$faker->creditCardExpirationDateString;'], 208 | ['随机生成信用卡明细', '$faker->creditCardDetails;'], 209 | ['随机生成国际银行账号', '$faker->iban($countryCode = null); '], 210 | ['todo 瑞士银行账号', '$faker->swiftBicNumber; '], 211 | ], $md); 212 | 213 | 214 | py_example('Color', [ 215 | ['随机生成16进制颜色', '$faker->hexColor;'], 216 | ['随机生成rgb格式的颜色', '$faker->rgbColor;'], 217 | ['随机生成数组格式的rgb颜色', '$faker->rgbColorAsArray;'], 218 | ['随机生成css格式的rgb颜色', '$faker->rgbCssColor;'], 219 | ['随机生成颜色名称', '$faker->safeColorName; '], 220 | [' 颜色名称', '$faker->colorName;'], 221 | ], $md); 222 | 223 | 224 | py_example('File', [ 225 | ['随机生成文件扩展名', '$faker->fileExtension;'], 226 | ['随机生成mime类型', '$faker->mimeType;'], 227 | ], $md); 228 | 229 | 230 | py_example('Image', [ 231 | ['随机生成图片地址', '$faker->imageUrl($width = 640, $height = 480);'], 232 | ], $md); 233 | 234 | 235 | py_example('UUID', [ 236 | ['随机生成一个唯一字串', '$faker->uuid'], 237 | ], $md); 238 | 239 | 240 | py_example('Calculator', [ 241 | ['随机生成13位ean码', '$faker->ean13;'], 242 | ['随机生成8位ean码', '$faker->ean8;'], 243 | ['随机生成13位isbn码', '$faker->isbn13;'], 244 | ['随机生成10位isbn码', '$faker->isbn10;'], 245 | ], $md); 246 | 247 | 248 | py_example('Miscellaneous', [ 249 | ['随机生成bool值 false', '$faker->boolean;'], 250 | ['平衡的生成bool值', '$faker->boolean($chanceOfGettingTrue = 50);'], 251 | [' Md5', '$faker->md5;'], 252 | [' Sha1', '$faker->sha1;'], 253 | [' Sha256', '$faker->sha256;'], 254 | [' Locale', '$faker->locale;'], 255 | ['随机生成国家编码', '$faker->countryCode;'], 256 | ['随机生成语言编码', '$faker->languageCode;'], 257 | ['随机生成货币代码', '$faker->currencyCode;'], 258 | [' Emoji', '$faker->emoji;'], 259 | ], $md); 260 | 261 | 262 | py_example('Biased', [ 263 | ['在10到20之间得到一个随机数,有更多的几率接近20', '$faker->biasedNumberBetween($min = 10, $max = 20, $function = \'sqrt\');'], 264 | ], $md); 265 | 266 | 267 | py_example('Html', [ 268 | ['随机生成一个不超过 $maxDepth层的html, 任何级别上都不超过$maxWidth个元素', '$faker->randomHtml($maxDepth = 2, $maxWidth = 3);'], 269 | ], $md); 270 | -------------------------------------------------------------------------------- /src/Provider/HtmlLorem.php: -------------------------------------------------------------------------------- 1 | addProvider(new Lorem($generator)); 42 | $generator->addProvider(new Internet($generator)); 43 | } 44 | 45 | /** 46 | * @param integer $maxDepth 47 | * @param integer $maxWidth 48 | * 49 | * @return string 50 | */ 51 | public function randomHtml($maxDepth = 4, $maxWidth = 4) 52 | { 53 | $document = new DOMDocument(); 54 | $this->idGenerator = new UniqueGenerator($this->generator); 55 | 56 | $head = $document->createElement("head"); 57 | $this->addRandomTitle($head); 58 | 59 | $body = $document->createElement("body"); 60 | $this->addLoginForm($body); 61 | $this->addRandomSubTree($body, $maxDepth, $maxWidth); 62 | 63 | $html = $document->createElement("html"); 64 | $html->appendChild($head); 65 | $html->appendChild($body); 66 | 67 | $document->appendChild($html); 68 | return $document->saveHTML(); 69 | } 70 | 71 | private function addRandomSubTree(DOMElement $root, $maxDepth, $maxWidth) 72 | { 73 | $maxDepth--; 74 | if ($maxDepth <= 0) { 75 | return $root; 76 | } 77 | 78 | $siblings = mt_rand(1, $maxWidth); 79 | for ($i = 0; $i < $siblings; $i++) { 80 | if ($maxDepth == 1) { 81 | $this->addRandomLeaf($root); 82 | } 83 | else { 84 | $sibling = $root->ownerDocument->createElement("div"); 85 | $root->appendChild($sibling); 86 | $this->addRandomAttribute($sibling); 87 | $this->addRandomSubTree($sibling, mt_rand(0, $maxDepth), $maxWidth); 88 | } 89 | } 90 | return $root; 91 | } 92 | 93 | private function addRandomLeaf(DOMElement $node) 94 | { 95 | $rand = mt_rand(1, 10); 96 | switch ($rand) { 97 | case 1: 98 | $this->addRandomP($node); 99 | break; 100 | case 2: 101 | $this->addRandomA($node); 102 | break; 103 | case 3: 104 | $this->addRandomSpan($node); 105 | break; 106 | case 4: 107 | $this->addRandomUL($node); 108 | break; 109 | case 5: 110 | $this->addRandomH($node); 111 | break; 112 | case 6: 113 | $this->addRandomB($node); 114 | break; 115 | case 7: 116 | $this->addRandomI($node); 117 | break; 118 | case 8: 119 | $this->addRandomTable($node); 120 | break; 121 | default: 122 | $this->addRandomText($node); 123 | break; 124 | } 125 | } 126 | 127 | private function addRandomAttribute(DOMElement $node) 128 | { 129 | $rand = mt_rand(1, 2); 130 | switch ($rand) { 131 | case 1: 132 | $node->setAttribute("class", $this->generator->word); 133 | break; 134 | case 2: 135 | $node->setAttribute("id", (string) $this->idGenerator->randomNumber(5)); 136 | break; 137 | } 138 | } 139 | 140 | private function addRandomP(DOMElement $element, $maxLength = 10) 141 | { 142 | 143 | $node = $element->ownerDocument->createElement(static::P_TAG); 144 | $node->textContent = $this->generator->sentence(mt_rand(1, $maxLength)); 145 | $element->appendChild($node); 146 | } 147 | 148 | private function addRandomText(DOMElement $element, $maxLength = 10) 149 | { 150 | $text = $element->ownerDocument->createTextNode($this->generator->sentence(mt_rand(1, $maxLength))); 151 | $element->appendChild($text); 152 | } 153 | 154 | private function addRandomA(DOMElement $element, $maxLength = 10) 155 | { 156 | $text = $element->ownerDocument->createTextNode($this->generator->sentence(mt_rand(1, $maxLength))); 157 | $node = $element->ownerDocument->createElement(static::A_TAG); 158 | $node->setAttribute("href", $this->generator->safeEmailDomain); 159 | $node->appendChild($text); 160 | $element->appendChild($node); 161 | } 162 | 163 | private function addRandomTitle(DOMElement $element, $maxLength = 10) 164 | { 165 | $text = $element->ownerDocument->createTextNode($this->generator->sentence(mt_rand(1, $maxLength))); 166 | $node = $element->ownerDocument->createElement(static::TITLE_TAG); 167 | $node->appendChild($text); 168 | $element->appendChild($node); 169 | } 170 | 171 | private function addRandomH(DOMElement $element, $maxLength = 10) 172 | { 173 | $h = static::H_TAG . (string) mt_rand(1, 3); 174 | $text = $element->ownerDocument->createTextNode($this->generator->sentence(mt_rand(1, $maxLength))); 175 | $node = $element->ownerDocument->createElement($h); 176 | $node->appendChild($text); 177 | $element->appendChild($node); 178 | } 179 | 180 | private function addRandomB(DOMElement $element, $maxLength = 10) 181 | { 182 | $text = $element->ownerDocument->createTextNode($this->generator->sentence(mt_rand(1, $maxLength))); 183 | $node = $element->ownerDocument->createElement(static::B_TAG); 184 | $node->appendChild($text); 185 | $element->appendChild($node); 186 | } 187 | 188 | private function addRandomI(DOMElement $element, $maxLength = 10) 189 | { 190 | $text = $element->ownerDocument->createTextNode($this->generator->sentence(mt_rand(1, $maxLength))); 191 | $node = $element->ownerDocument->createElement(static::I_TAG); 192 | $node->appendChild($text); 193 | $element->appendChild($node); 194 | } 195 | 196 | private function addRandomSpan(DOMElement $element, $maxLength = 10) 197 | { 198 | $text = $element->ownerDocument->createTextNode($this->generator->sentence(mt_rand(1, $maxLength))); 199 | $node = $element->ownerDocument->createElement(static::SPAN_TAG); 200 | $node->appendChild($text); 201 | $element->appendChild($node); 202 | } 203 | 204 | private function addLoginForm(DOMElement $element) 205 | { 206 | 207 | $textInput = $element->ownerDocument->createElement(static::INPUT_TAG); 208 | $textInput->setAttribute("type", "text"); 209 | $textInput->setAttribute("id", "username"); 210 | 211 | $textLabel = $element->ownerDocument->createElement(static::LABEL_TAG); 212 | $textLabel->setAttribute("for", "username"); 213 | $textLabel->textContent = $this->generator->word; 214 | 215 | $passwordInput = $element->ownerDocument->createElement(static::INPUT_TAG); 216 | $passwordInput->setAttribute("type", "password"); 217 | $passwordInput->setAttribute("id", "password"); 218 | 219 | $passwordLabel = $element->ownerDocument->createElement(static::LABEL_TAG); 220 | $passwordLabel->setAttribute("for", "password"); 221 | $passwordLabel->textContent = $this->generator->word; 222 | 223 | $submit = $element->ownerDocument->createElement(static::INPUT_TAG); 224 | $submit->setAttribute("type", "submit"); 225 | $submit->setAttribute("value", $this->generator->word); 226 | 227 | $submit = $element->ownerDocument->createElement(static::FORM_TAG); 228 | $submit->setAttribute("action", $this->generator->safeEmailDomain); 229 | $submit->setAttribute("method", "POST"); 230 | $submit->appendChild($textLabel); 231 | $submit->appendChild($textInput); 232 | $submit->appendChild($passwordLabel); 233 | $submit->appendChild($passwordInput); 234 | $element->appendChild($submit); 235 | } 236 | 237 | private function addRandomTable(DOMElement $element, $maxRows = 10, $maxCols = 6, $maxTitle = 4, $maxLength = 10) 238 | { 239 | $rows = mt_rand(1, $maxRows); 240 | $cols = mt_rand(1, $maxCols); 241 | 242 | $table = $element->ownerDocument->createElement(static::TABLE_TAG); 243 | $thead = $element->ownerDocument->createElement(static::THEAD_TAG); 244 | $tbody = $element->ownerDocument->createElement(static::TBODY_TAG); 245 | 246 | $table->appendChild($thead); 247 | $table->appendChild($tbody); 248 | 249 | $tr = $element->ownerDocument->createElement(static::TR_TAG); 250 | $thead->appendChild($tr); 251 | for ($i = 0; $i < $cols; $i++) { 252 | $th = $element->ownerDocument->createElement(static::TH_TAG); 253 | $th->textContent = $this->generator->sentence(mt_rand(1, $maxTitle)); 254 | $tr->appendChild($th); 255 | } 256 | for ($i = 0; $i < $rows; $i++) { 257 | $tr = $element->ownerDocument->createElement(static::TR_TAG); 258 | $tbody->appendChild($tr); 259 | for ($j = 0; $j < $cols; $j++) { 260 | $th = $element->ownerDocument->createElement(static::TD_TAG); 261 | $th->textContent = $this->generator->sentence(mt_rand(1, $maxLength)); 262 | $tr->appendChild($th); 263 | } 264 | } 265 | $element->appendChild($table); 266 | } 267 | 268 | private function addRandomUL(DOMElement $element, $maxItems = 11, $maxLength = 4) 269 | { 270 | $num = mt_rand(1, $maxItems); 271 | $ul = $element->ownerDocument->createElement(static::UL_TAG); 272 | for ($i = 0; $i < $num; $i++) { 273 | $li = $element->ownerDocument->createElement(static::LI_TAG); 274 | $li->textContent = $this->generator->sentence(mt_rand(1, $maxLength)); 275 | $ul->appendChild($li); 276 | } 277 | $element->appendChild($ul); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/Provider/Payment.php: -------------------------------------------------------------------------------- 1 | [ 25 | "4539###########", 26 | "4556###########", 27 | "4916###########", 28 | "4532###########", 29 | "4929###########", 30 | "40240071#######", 31 | "4485###########", 32 | "4716###########", 33 | "4##############", 34 | ], 35 | 'Visa Retired' => [ 36 | "4539########", 37 | "4556########", 38 | "4916########", 39 | "4532########", 40 | "4929########", 41 | "40240071####", 42 | "4485########", 43 | "4716########", 44 | "4###########", 45 | ], 46 | 'MasterCard' => [ 47 | "2221###########", 48 | "23#############", 49 | "24#############", 50 | "25#############", 51 | "26#############", 52 | "2720###########", 53 | "51#############", 54 | "52#############", 55 | "53#############", 56 | "54#############", 57 | "55#############", 58 | ], 59 | 'American Express' => [ 60 | "34############", 61 | "37############", 62 | ], 63 | 'Discover Card' => [ 64 | "6011###########", 65 | ], 66 | ]; 67 | 68 | /** 69 | * @var array list of IBAN formats, source: @link https://www.swift.com/standards/data-standards/iban 70 | */ 71 | protected static $ibanFormats = [ 72 | 'AD' => [['n', 4], ['n', 4], ['c', 12]], 73 | 'AE' => [['n', 3], ['n', 16]], 74 | 'AL' => [['n', 8], ['c', 16]], 75 | 'AT' => [['n', 5], ['n', 11]], 76 | 'AZ' => [['a', 4], ['c', 20]], 77 | 'BA' => [['n', 3], ['n', 3], ['n', 8], ['n', 2]], 78 | 'BE' => [['n', 3], ['n', 7], ['n', 2]], 79 | 'BG' => [['a', 4], ['n', 4], ['n', 2], ['c', 8]], 80 | 'BH' => [['a', 4], ['c', 14]], 81 | 'BR' => [['n', 8], ['n', 5], ['n', 10], ['a', 1], ['c', 1]], 82 | 'CH' => [['n', 5], ['c', 12]], 83 | 'CR' => [['n', 4], ['n', 14]], 84 | 'CY' => [['n', 3], ['n', 5], ['c', 16]], 85 | 'CZ' => [['n', 4], ['n', 6], ['n', 10]], 86 | 'DE' => [['n', 8], ['n', 10]], 87 | 'DK' => [['n', 4], ['n', 9], ['n', 1]], 88 | 'DO' => [['c', 4], ['n', 20]], 89 | 'EE' => [['n', 2], ['n', 2], ['n', 11], ['n', 1]], 90 | 'ES' => [['n', 4], ['n', 4], ['n', 1], ['n', 1], ['n', 10]], 91 | 'FI' => [['n', 6], ['n', 7], ['n', 1]], 92 | 'FR' => [['n', 5], ['n', 5], ['c', 11], ['n', 2]], 93 | 'GB' => [['a', 4], ['n', 6], ['n', 8]], 94 | 'GE' => [['a', 2], ['n', 16]], 95 | 'GI' => [['a', 4], ['c', 15]], 96 | 'GR' => [['n', 3], ['n', 4], ['c', 16]], 97 | 'GT' => [['c', 4], ['c', 20]], 98 | 'HR' => [['n', 7], ['n', 10]], 99 | 'HU' => [['n', 3], ['n', 4], ['n', 1], ['n', 15], ['n', 1]], 100 | 'IE' => [['a', 4], ['n', 6], ['n', 8]], 101 | 'IL' => [['n', 3], ['n', 3], ['n', 13]], 102 | 'IS' => [['n', 4], ['n', 2], ['n', 6], ['n', 10]], 103 | 'IT' => [['a', 1], ['n', 5], ['n', 5], ['c', 12]], 104 | 'KW' => [['a', 4], ['n', 22]], 105 | 'KZ' => [['n', 3], ['c', 13]], 106 | 'LB' => [['n', 4], ['c', 20]], 107 | 'LI' => [['n', 5], ['c', 12]], 108 | 'LT' => [['n', 5], ['n', 11]], 109 | 'LU' => [['n', 3], ['c', 13]], 110 | 'LV' => [['a', 4], ['c', 13]], 111 | 'MC' => [['n', 5], ['n', 5], ['c', 11], ['n', 2]], 112 | 'MD' => [['c', 2], ['c', 18]], 113 | 'ME' => [['n', 3], ['n', 13], ['n', 2]], 114 | 'MK' => [['n', 3], ['c', 10], ['n', 2]], 115 | 'MR' => [['n', 5], ['n', 5], ['n', 11], ['n', 2]], 116 | 'MT' => [['a', 4], ['n', 5], ['c', 18]], 117 | 'MU' => [['a', 4], ['n', 2], ['n', 2], ['n', 12], ['n', 3], ['a', 3]], 118 | 'NL' => [['a', 4], ['n', 10]], 119 | 'NO' => [['n', 4], ['n', 6], ['n', 1]], 120 | 'PK' => [['a', 4], ['c', 16]], 121 | 'PL' => [['n', 8], ['n', 16]], 122 | 'PS' => [['a', 4], ['c', 21]], 123 | 'PT' => [['n', 4], ['n', 4], ['n', 11], ['n', 2]], 124 | 'RO' => [['a', 4], ['c', 16]], 125 | 'RS' => [['n', 3], ['n', 13], ['n', 2]], 126 | 'SA' => [['n', 2], ['c', 18]], 127 | 'SE' => [['n', 3], ['n', 16], ['n', 1]], 128 | 'SI' => [['n', 5], ['n', 8], ['n', 2]], 129 | 'SK' => [['n', 4], ['n', 6], ['n', 10]], 130 | 'SM' => [['a', 1], ['n', 5], ['n', 5], ['c', 12]], 131 | 'TN' => [['n', 2], ['n', 3], ['n', 13], ['n', 2]], 132 | 'TR' => [['n', 5], ['n', 1], ['c', 16]], 133 | 'VG' => [['a', 4], ['n', 16]], 134 | ]; 135 | 136 | /** 137 | * @return string Returns a credit card vendor name 138 | * 139 | * @example 'MasterCard' 140 | */ 141 | public static function creditCardType() 142 | { 143 | return static::randomElement(static::$cardVendors); 144 | } 145 | 146 | /** 147 | * Returns the String of a credit card number. 148 | * 149 | * @param string $type Supporting any of 'Visa', 'MasterCard', 'American Express', and 'Discover' 150 | * @param boolean $formatted Set to true if the output string should contain one separator every 4 digits 151 | * @param string $separator Separator string for formatting card number. Defaults to dash (-). 152 | * @return string 153 | * 154 | * @example '4485480221084675' 155 | */ 156 | public static function creditCardNumber($type = null, $formatted = false, $separator = '-') 157 | { 158 | if (is_null($type)) { 159 | $type = static::creditCardType(); 160 | } 161 | $mask = static::randomElement(static::$cardParams[$type]); 162 | 163 | $number = static::numerify($mask); 164 | $number .= Luhn::computeCheckDigit($number); 165 | 166 | if ($formatted) { 167 | $p1 = substr($number, 0, 4); 168 | $p2 = substr($number, 4, 4); 169 | $p3 = substr($number, 8, 4); 170 | $p4 = substr($number, 12); 171 | $number = $p1 . $separator . $p2 . $separator . $p3 . $separator . $p4; 172 | } 173 | 174 | return $number; 175 | } 176 | 177 | /** 178 | * @param boolean $valid True (by default) to get a valid expiration date, false to get a maybe valid date 179 | * @return \DateTime 180 | * @example 04/13 181 | */ 182 | public function creditCardExpirationDate($valid = true) 183 | { 184 | if ($valid) { 185 | return $this->generator->dateTimeBetween('now', '36 months'); 186 | } 187 | 188 | return $this->generator->dateTimeBetween('-36 months', '36 months'); 189 | } 190 | 191 | /** 192 | * @param boolean $valid True (by default) to get a valid expiration date, false to get a maybe valid date 193 | * @param string $expirationDateFormat 194 | * @return string 195 | * @example '04/13' 196 | */ 197 | public function creditCardExpirationDateString($valid = true, $expirationDateFormat = null) 198 | { 199 | return $this->creditCardExpirationDate($valid)->format(is_null($expirationDateFormat) ? static::$expirationDateFormat : $expirationDateFormat); 200 | } 201 | 202 | /** 203 | * @param boolean $valid True (by default) to get a valid expiration date, false to get a maybe valid date 204 | * @return array 205 | */ 206 | public function creditCardDetails($valid = true) 207 | { 208 | $type = static::creditCardType(); 209 | 210 | return [ 211 | 'type' => $type, 212 | 'number' => static::creditCardNumber($type), 213 | 'name' => $this->generator->name(), 214 | 'expirationDate' => $this->creditCardExpirationDateString($valid), 215 | ]; 216 | } 217 | 218 | /** 219 | * International Bank Account Number (IBAN) 220 | * 221 | * @link http://en.wikipedia.org/wiki/International_Bank_Account_Number 222 | * @param string $countryCode ISO 3166-1 alpha-2 country code 223 | * @param string $prefix for generating bank account number of a specific bank 224 | * @param integer $length total length without country code and 2 check digits 225 | * @return string 226 | */ 227 | public static function iban($countryCode = null, $prefix = '', $length = null) 228 | { 229 | $countryCode = is_null($countryCode) ? self::randomKey(self::$ibanFormats) : strtoupper($countryCode); 230 | 231 | $format = !isset(static::$ibanFormats[$countryCode]) ? null : static::$ibanFormats[$countryCode]; 232 | if ($length === null) { 233 | if ($format === null) { 234 | $length = 24; 235 | } 236 | else { 237 | $length = 0; 238 | foreach ($format as $part) { 239 | [$class, $groupCount] = $part; 240 | $length += $groupCount; 241 | } 242 | } 243 | } 244 | if ($format === null) { 245 | $format = [['n', $length]]; 246 | } 247 | 248 | $expandedFormat = ''; 249 | foreach ($format as $item) { 250 | [$class, $length] = $item; 251 | $expandedFormat .= str_repeat($class, $length); 252 | } 253 | 254 | $result = $prefix; 255 | $expandedFormat = substr($expandedFormat, strlen($result)); 256 | foreach (str_split($expandedFormat) as $class) { 257 | switch ($class) { 258 | default: 259 | case 'c': 260 | $result .= mt_rand(0, 100) <= 50 ? static::randomDigit() : strtoupper(static::randomLetter()); 261 | break; 262 | case 'a': 263 | $result .= strtoupper(static::randomLetter()); 264 | break; 265 | case 'n': 266 | $result .= static::randomDigit(); 267 | break; 268 | } 269 | } 270 | 271 | $checksum = Iban::checksum($countryCode . '00' . $result); 272 | 273 | return $countryCode . $checksum . $result; 274 | } 275 | 276 | /** 277 | * Return the String of a SWIFT/BIC number 278 | * 279 | * @return string Swift/Bic number 280 | * @link http://en.wikipedia.org/wiki/ISO_9362 281 | * @example 'RZTIAT22263' 282 | */ 283 | public static function swiftBicNumber() 284 | { 285 | return self::regexify("^([A-Z]){4}([A-Z]){2}([0-9A-Z]){2}([0-9A-Z]{3})?$"); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/Generator.php: -------------------------------------------------------------------------------- 1 | providers, $provider); 213 | } 214 | 215 | public function getProviders() 216 | { 217 | return $this->providers; 218 | } 219 | 220 | public function seed($seed = null) 221 | { 222 | if ($seed === null) { 223 | mt_srand(); 224 | } 225 | else { 226 | if (PHP_VERSION_ID < 70100) { 227 | mt_srand((int) $seed); 228 | } 229 | else { 230 | mt_srand((int) $seed, MT_RAND_PHP); 231 | } 232 | } 233 | } 234 | 235 | public function format($formatter, $arguments = []) 236 | { 237 | return call_user_func_array($this->getFormatter($formatter), $arguments); 238 | } 239 | 240 | /** 241 | * @param string $formatter 242 | * 243 | * @return Callable 244 | */ 245 | public function getFormatter($formatter) 246 | { 247 | if (isset($this->formatters[$formatter])) { 248 | return $this->formatters[$formatter]; 249 | } 250 | foreach ($this->providers as $provider) { 251 | if (method_exists($provider, $formatter)) { 252 | $this->formatters[$formatter] = [$provider, $formatter]; 253 | 254 | return $this->formatters[$formatter]; 255 | } 256 | } 257 | throw new InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter)); 258 | } 259 | 260 | /** 261 | * Replaces tokens ('{{ tokenName }}') with the result from the token method call 262 | * 263 | * @param string $string String that needs to bet parsed 264 | * @return string 265 | */ 266 | public function parse($string) 267 | { 268 | return preg_replace_callback('/\{\{\s?(\w+)\s?}}/u', [$this, 'callFormatWithMatches'], $string); 269 | } 270 | 271 | /** 272 | * @param string $attribute 273 | * 274 | * @return mixed 275 | */ 276 | public function __get($attribute) 277 | { 278 | return $this->format($attribute); 279 | } 280 | 281 | /** 282 | * @param string $method 283 | * @param array $attributes 284 | * 285 | * @return mixed 286 | */ 287 | public function __call($method, $attributes) 288 | { 289 | return $this->format($method, $attributes); 290 | } 291 | 292 | public function __destruct() 293 | { 294 | $this->seed(); 295 | } 296 | 297 | protected function callFormatWithMatches($matches) 298 | { 299 | return $this->format($matches[1]); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/JsonSchema/Faker.php: -------------------------------------------------------------------------------- 1 | 'fakeNull', 27 | 'boolean' => 'fakeBoolean', 28 | 'integer' => 'fakeInteger', 29 | 'number' => 'fakeNumber', 30 | 'string' => 'fakeString', 31 | 'array' => 'fakeArray', 32 | 'object' => 'fakeObject', 33 | ]; 34 | 35 | /** 36 | * @var string 37 | */ 38 | private string $schemaDir = ''; 39 | 40 | /** 41 | * Create fake data with JSON schema 42 | * 43 | * @param SplFileInfo|stdClass $schema Data structure written in JSON Schema 44 | * @param stdClass|null $parentSchema parent schema when it is subschema 45 | * @param string|null $schemaDir forced directory in object loop 46 | * 47 | * @throws UnsupportedTypeException Throw when unsupported type specified 48 | */ 49 | public function generate($schema, stdClass $parentSchema = null, string $schemaDir = null) 50 | { 51 | if ($schema instanceof SplFileInfo) { 52 | $file = (string) $schema->getRealPath(); 53 | if (file_exists($file)) { 54 | $this->schemaDir = dirname($file); 55 | } 56 | $schema = json_decode((string) file_get_contents($file)); 57 | } 58 | if (!$schema instanceof stdClass) { 59 | throw new InvalidArgumentException(gettype($schema)); 60 | } 61 | $schema = $this->resolveOf($schema); 62 | if (property_exists($schema, '$ref')) { 63 | $currentDir = $schemaDir ?? $this->schemaDir; 64 | 65 | return (new Ref($this, $currentDir))($schema, $parentSchema); 66 | } 67 | $type = is_array($schema->type) ? Base::randomElement($schema->type) : $schema->type; 68 | 69 | if (isset($schema->enum)) { 70 | return Base::randomElement($schema->enum); 71 | } 72 | 73 | if (isset($schema->const)) { 74 | return Base::randomElement([$schema->const]); 75 | } 76 | 77 | if (!isset($this->fakers[$type])) { 78 | throw new UnsupportedTypeException($type); 79 | } 80 | 81 | $faker = [$this, $this->fakers[$type]]; 82 | if (is_callable($faker)) { 83 | return call_user_func($faker, $schema); 84 | } 85 | 86 | throw new LogicException; 87 | } 88 | 89 | public function mergeObject(): object 90 | { 91 | $merged = []; 92 | $objList = func_get_args(); 93 | 94 | foreach ($objList as $obj) { 95 | array_push($merged, ...(array) $obj); 96 | } 97 | 98 | 99 | return (object) $merged; 100 | } 101 | 102 | public function getMaximum($schema): int 103 | { 104 | $offset = ($schema->exclusiveMaximum ?? false) ? 1 : 0; 105 | 106 | return (int) (($schema->maximum ?? mt_getrandmax()) - $offset); 107 | } 108 | 109 | public function getMinimum($schema): int 110 | { 111 | $offset = ($schema->exclusiveMinimum ?? false) ? 1 : 0; 112 | 113 | return (int) (($schema->minimum ?? -mt_getrandmax()) + $offset); 114 | } 115 | 116 | public function resolveDependencies(stdClass $schema, array $keys): array 117 | { 118 | $resolved = []; 119 | $dependencies = $schema->dependencies ?? new stdClass(); 120 | 121 | foreach ($keys as $key) { 122 | $resolved = array_merge($resolved, [$key], $dependencies->{$key} ?? []); 123 | } 124 | 125 | return $resolved; 126 | } 127 | 128 | public function getRandomSchema(): object 129 | { 130 | $fakerNames = array_keys($this->fakers); 131 | 132 | return (object) [ 133 | 'type' => Base::randomElement($fakerNames), 134 | ]; 135 | } 136 | 137 | public function resolveOf(stdClass $schema): stdClass 138 | { 139 | if (isset($schema->allOf)) { 140 | return call_user_func_array([$this, 'mergeObject'], $schema->allOf); 141 | } 142 | if (isset($schema->anyOf)) { 143 | return call_user_func_array([$this, 'mergeObject'], Base::randomElements($schema->anyOf)); 144 | } 145 | if (isset($schema->oneOf)) { 146 | return Base::randomElement($schema->oneOf); 147 | } 148 | 149 | return $schema; 150 | } 151 | 152 | public function getMultipleOf($schema): int 153 | { 154 | return $schema->multipleOf ?? 1; 155 | } 156 | 157 | public function getInternetFakerInstance(): Internet 158 | { 159 | return new Internet(Factory::create()); 160 | } 161 | 162 | public function getFormattedValue($schema) 163 | { 164 | switch ($schema->format) { 165 | // Date representation, as defined by RFC 3339, section 5.6. 166 | case 'date-time': 167 | return DateTime::dateTime()->format(DATE_RFC3339); 168 | // Internet email address, see RFC 5322, section 3.4.1. 169 | case 'email': 170 | return $this->getInternetFakerInstance()->safeEmail(); 171 | // Internet host name, see RFC 1034, section 3.1. 172 | case 'hostname': 173 | return $this->getInternetFakerInstance()->domainName(); 174 | // IPv4 address, according to dotted-quad ABNF syntax as defined in RFC 2673, section 3.2. 175 | case 'ipv4': 176 | return $this->getInternetFakerInstance()->ipv4(); 177 | // IPv6 address, as defined in RFC 2373, section 2.2. 178 | case 'ipv6': 179 | return $this->getInternetFakerInstance()->ipv6(); 180 | // A universal resource identifier (URI), according to RFC3986. 181 | case 'uri': 182 | return $this->getInternetFakerInstance()->url(); 183 | default: 184 | throw new UnsupportedTypeException("Unsupported type: {$schema->format}"); 185 | } 186 | } 187 | 188 | /** 189 | * @return string[] Property names 190 | */ 191 | public function getProperties(stdClass $schema): array 192 | { 193 | $requiredKeys = $schema->required ?? []; 194 | $optionalKeys = array_keys((array) ($schema->properties ?? new stdClass())); 195 | $maxProperties = $schema->maxProperties ?? count($optionalKeys) - count($requiredKeys); 196 | $pickSize = Base::numberBetween(0, min(count($optionalKeys), $maxProperties)); 197 | $additionalKeys = $this->resolveDependencies($schema, Base::randomElements($optionalKeys, $pickSize)); 198 | $propertyNames = array_unique(array_merge($requiredKeys, $additionalKeys)); 199 | 200 | $additionalProperties = $schema->additionalProperties ?? true; 201 | $patternProperties = $schema->patternProperties ?? new stdClass(); 202 | $patterns = array_keys((array) $patternProperties); 203 | while (count($propertyNames) < ($schema->minProperties ?? 0)) { 204 | $name = $additionalProperties ? Lorem::word() : Lorem::regexify(Base::randomElement($patterns)); 205 | if (!in_array($name, $propertyNames, true)) { 206 | $propertyNames[] = $name; 207 | } 208 | } 209 | 210 | return $propertyNames; 211 | } 212 | 213 | private function fakeNull() 214 | { 215 | return null; 216 | } 217 | 218 | private function fakeBoolean(): bool 219 | { 220 | return Base::randomElement([true, false]); 221 | } 222 | 223 | private function fakeInteger(stdClass $schema): int 224 | { 225 | $minimum = $this->getMinimum($schema); 226 | $maximum = $this->getMaximum($schema); 227 | $multipleOf = $this->getMultipleOf($schema); 228 | 229 | return Base::numberBetween($minimum, $maximum) * $multipleOf; 230 | } 231 | 232 | private function fakeNumber(stdClass $schema) 233 | { 234 | $minimum = $this->getMinimum($schema); 235 | $maximum = $this->getMaximum($schema); 236 | $multipleOf = $this->getMultipleOf($schema); 237 | 238 | return Base::randomFloat(null, $minimum, $maximum) * $multipleOf; 239 | } 240 | 241 | private function fakeString(stdClass $schema): string 242 | { 243 | if (isset($schema->format)) { 244 | return $this->getFormattedValue($schema); 245 | } 246 | if (isset($schema->pattern)) { 247 | return Lorem::regexify($schema->pattern); 248 | } 249 | $min = $schema->minLength ?? 1; 250 | $max = $schema->maxLength ?? max(5, $min + 1); 251 | if ($max < 5) { 252 | return substr(Lorem::text(5), 0, $max); 253 | } 254 | $lorem = Lorem::text($max); 255 | 256 | if (mb_strlen($lorem) < $min) { 257 | $lorem = str_repeat($lorem, $min); 258 | } 259 | 260 | return mb_substr($lorem, 0, $max); 261 | } 262 | 263 | private function fakeArray(stdClass $schema): array 264 | { 265 | if (!isset($schema->items)) { 266 | $subschemas = [$this->getRandomSchema()]; 267 | // List 268 | } 269 | elseif (is_object($schema->items)) { 270 | $subschemas = [$schema->items]; 271 | // Tuple 272 | } 273 | elseif (is_array($schema->items)) { 274 | $subschemas = $schema->items; 275 | } 276 | else { 277 | throw new InvalidItemsException; 278 | } 279 | 280 | $dummies = []; 281 | $itemSize = Base::numberBetween(($schema->minItems ?? 0), $schema->maxItems ?? count($subschemas)); 282 | $subschemas = array_slice($subschemas, 0, $itemSize); 283 | $dir = $this->schemaDir; 284 | for ($i = 0; $i < $itemSize; $i++) { 285 | $subschema = $subschemas[$i % count($subschemas)]; 286 | $dummies[] = $this->generate($subschema, $schema, $dir); 287 | } 288 | $this->schemaDir = $dir; 289 | 290 | return ($schema->uniqueItems ?? false) ? array_unique($dummies) : $dummies; 291 | } 292 | 293 | private function fakeObject(stdClass $schema): stdClass 294 | { 295 | $dir = $this->schemaDir; 296 | $properties = $schema->properties ?? new stdClass(); 297 | $propertyNames = $this->getProperties($schema); 298 | 299 | $dummy = new stdClass(); 300 | $schemaDir = $this->schemaDir; 301 | foreach ($propertyNames as $key) { 302 | if (isset($properties->{$key})) { 303 | $subschema = $properties->{$key}; 304 | } 305 | else { 306 | $subschema = $this->getAdditionalPropertySchema($schema, $key) ?: $this->getRandomSchema(); 307 | } 308 | 309 | $dummy->{$key} = $this->generate($subschema, $schema, $schemaDir); 310 | } 311 | $this->schemaDir = $dir; 312 | 313 | return $dummy; 314 | } 315 | 316 | private function getAdditionalPropertySchema(stdClass $schema, $property) 317 | { 318 | $patternProperties = $schema->patternProperties ?? new stdClass(); 319 | $additionalProperties = $schema->additionalProperties ?? true; 320 | 321 | foreach ($patternProperties as $pattern => $sub) { 322 | if (preg_match("/{$pattern}/", $property)) { 323 | return $sub; 324 | } 325 | } 326 | 327 | if (is_object($additionalProperties)) { 328 | return $additionalProperties; 329 | } 330 | } 331 | } 332 | --------------------------------------------------------------------------------