1<?php 2 3namespace Faker\Provider; 4 5use Faker\Generator; 6use Faker\DefaultGenerator; 7use Faker\UniqueGenerator; 8use Faker\ValidGenerator; 9 10class Base 11{ 12 /** 13 * @var \Faker\Generator 14 */ 15 protected $generator; 16 17 /** 18 * @var \Faker\UniqueGenerator 19 */ 20 protected $unique; 21 22 /** 23 * @param \Faker\Generator $generator 24 */ 25 public function __construct(Generator $generator) 26 { 27 $this->generator = $generator; 28 } 29 30 /** 31 * Returns a random number between 0 and 9 32 * 33 * @return integer 34 */ 35 public static function randomDigit() 36 { 37 return mt_rand(0, 9); 38 } 39 40 /** 41 * Returns a random number between 1 and 9 42 * 43 * @return integer 44 */ 45 public static function randomDigitNotNull() 46 { 47 return mt_rand(1, 9); 48 } 49 50 /** 51 * Generates a random digit, which cannot be $except 52 * 53 * @param int $except 54 * @return int 55 */ 56 public static function randomDigitNot($except) 57 { 58 $result = self::numberBetween(0, 8); 59 if ($result >= $except) { 60 $result++; 61 } 62 return $result; 63 } 64 65 /** 66 * Returns a random integer with 0 to $nbDigits digits. 67 * 68 * The maximum value returned is mt_getrandmax() 69 * 70 * @param integer $nbDigits Defaults to a random number between 1 and 9 71 * @param boolean $strict Whether the returned number should have exactly $nbDigits 72 * @example 79907610 73 * 74 * @return integer 75 */ 76 public static function randomNumber($nbDigits = null, $strict = false) 77 { 78 if (!is_bool($strict)) { 79 throw new \InvalidArgumentException('randomNumber() generates numbers of fixed width. To generate numbers between two boundaries, use numberBetween() instead.'); 80 } 81 if (null === $nbDigits) { 82 $nbDigits = static::randomDigitNotNull(); 83 } 84 $max = pow(10, $nbDigits) - 1; 85 if ($max > mt_getrandmax()) { 86 throw new \InvalidArgumentException('randomNumber() can only generate numbers up to mt_getrandmax()'); 87 } 88 if ($strict) { 89 return mt_rand(pow(10, $nbDigits - 1), $max); 90 } 91 92 return mt_rand(0, $max); 93 } 94 95 /** 96 * Return a random float number 97 * 98 * @param int $nbMaxDecimals 99 * @param int|float $min 100 * @param int|float $max 101 * @example 48.8932 102 * 103 * @return float 104 */ 105 public static function randomFloat($nbMaxDecimals = null, $min = 0, $max = null) 106 { 107 if (null === $nbMaxDecimals) { 108 $nbMaxDecimals = static::randomDigit(); 109 } 110 111 if (null === $max) { 112 $max = static::randomNumber(); 113 if ($min > $max) { 114 $max = $min; 115 } 116 } 117 118 if ($min > $max) { 119 $tmp = $min; 120 $min = $max; 121 $max = $tmp; 122 } 123 124 return round($min + mt_rand() / mt_getrandmax() * ($max - $min), $nbMaxDecimals); 125 } 126 127 /** 128 * Returns a random number between $int1 and $int2 (any order) 129 * 130 * @param integer $int1 default to 0 131 * @param integer $int2 defaults to 32 bit max integer, ie 2147483647 132 * @example 79907610 133 * 134 * @return integer 135 */ 136 public static function numberBetween($int1 = 0, $int2 = 2147483647) 137 { 138 $min = $int1 < $int2 ? $int1 : $int2; 139 $max = $int1 < $int2 ? $int2 : $int1; 140 return mt_rand($min, $max); 141 } 142 143 /** 144 * Returns the passed value 145 * 146 * @param mixed $value 147 * 148 * @return mixed 149 */ 150 public static function passthrough($value) 151 { 152 return $value; 153 } 154 155 /** 156 * Returns a random letter from a to z 157 * 158 * @return string 159 */ 160 public static function randomLetter() 161 { 162 return chr(mt_rand(97, 122)); 163 } 164 165 /** 166 * Returns a random ASCII character (excluding accents and special chars) 167 */ 168 public static function randomAscii() 169 { 170 return chr(mt_rand(33, 126)); 171 } 172 173 /** 174 * Returns randomly ordered subsequence of $count elements from a provided array 175 * 176 * @param array $array Array to take elements from. Defaults to a-c 177 * @param integer $count Number of elements to take. 178 * @param boolean $allowDuplicates Allow elements to be picked several times. Defaults to false 179 * @throws \LengthException When requesting more elements than provided 180 * 181 * @return array New array with $count elements from $array 182 */ 183 public static function randomElements($array = array('a', 'b', 'c'), $count = 1, $allowDuplicates = false) 184 { 185 $traversables = array(); 186 187 if ($array instanceof \Traversable) { 188 foreach ($array as $element) { 189 $traversables[] = $element; 190 } 191 } 192 193 $arr = count($traversables) ? $traversables : $array; 194 195 $allKeys = array_keys($arr); 196 $numKeys = count($allKeys); 197 198 if (!$allowDuplicates && $numKeys < $count) { 199 throw new \LengthException(sprintf('Cannot get %d elements, only %d in array', $count, $numKeys)); 200 } 201 202 $highKey = $numKeys - 1; 203 $keys = $elements = array(); 204 $numElements = 0; 205 206 while ($numElements < $count) { 207 $num = mt_rand(0, $highKey); 208 209 if (!$allowDuplicates) { 210 if (isset($keys[$num])) { 211 continue; 212 } 213 $keys[$num] = true; 214 } 215 216 $elements[] = $arr[$allKeys[$num]]; 217 $numElements++; 218 } 219 220 return $elements; 221 } 222 223 /** 224 * Returns a random element from a passed array 225 * 226 * @param array $array 227 * @return mixed 228 */ 229 public static function randomElement($array = array('a', 'b', 'c')) 230 { 231 if (!$array || ($array instanceof \Traversable && !count($array))) { 232 return null; 233 } 234 $elements = static::randomElements($array, 1); 235 236 return $elements[0]; 237 } 238 239 /** 240 * Returns a random key from a passed associative array 241 * 242 * @param array $array 243 * @return int|string|null 244 */ 245 public static function randomKey($array = array()) 246 { 247 if (!$array) { 248 return null; 249 } 250 $keys = array_keys($array); 251 $key = $keys[mt_rand(0, count($keys) - 1)]; 252 253 return $key; 254 } 255 256 /** 257 * Returns a shuffled version of the argument. 258 * 259 * This function accepts either an array, or a string. 260 * 261 * @example $faker->shuffle([1, 2, 3]); // [2, 1, 3] 262 * @example $faker->shuffle('hello, world'); // 'rlo,h eold!lw' 263 * 264 * @see shuffleArray() 265 * @see shuffleString() 266 * 267 * @param array|string $arg The set to shuffle 268 * @return array|string The shuffled set 269 */ 270 public static function shuffle($arg = '') 271 { 272 if (is_array($arg)) { 273 return static::shuffleArray($arg); 274 } 275 if (is_string($arg)) { 276 return static::shuffleString($arg); 277 } 278 throw new \InvalidArgumentException('shuffle() only supports strings or arrays'); 279 } 280 281 /** 282 * Returns a shuffled version of the array. 283 * 284 * This function does not mutate the original array. It uses the 285 * Fisher–Yates algorithm, which is unbiased, together with a Mersenne 286 * twister random generator. This function is therefore more random than 287 * PHP's shuffle() function, and it is seedable. 288 * 289 * @link http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 290 * 291 * @example $faker->shuffleArray([1, 2, 3]); // [2, 1, 3] 292 * 293 * @param array $array The set to shuffle 294 * @return array The shuffled set 295 */ 296 public static function shuffleArray($array = array()) 297 { 298 $shuffledArray = array(); 299 $i = 0; 300 reset($array); 301 foreach ($array as $key => $value) { 302 if ($i == 0) { 303 $j = 0; 304 } else { 305 $j = mt_rand(0, $i); 306 } 307 if ($j == $i) { 308 $shuffledArray[]= $value; 309 } else { 310 $shuffledArray[]= $shuffledArray[$j]; 311 $shuffledArray[$j] = $value; 312 } 313 $i++; 314 } 315 return $shuffledArray; 316 } 317 318 /** 319 * Returns a shuffled version of the string. 320 * 321 * This function does not mutate the original string. It uses the 322 * Fisher–Yates algorithm, which is unbiased, together with a Mersenne 323 * twister random generator. This function is therefore more random than 324 * PHP's shuffle() function, and it is seedable. Additionally, it is 325 * UTF8 safe if the mb extension is available. 326 * 327 * @link http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 328 * 329 * @example $faker->shuffleString('hello, world'); // 'rlo,h eold!lw' 330 * 331 * @param string $string The set to shuffle 332 * @param string $encoding The string encoding (defaults to UTF-8) 333 * @return string The shuffled set 334 */ 335 public static function shuffleString($string = '', $encoding = 'UTF-8') 336 { 337 if (function_exists('mb_strlen')) { 338 // UTF8-safe str_split() 339 $array = array(); 340 $strlen = mb_strlen($string, $encoding); 341 for ($i = 0; $i < $strlen; $i++) { 342 $array []= mb_substr($string, $i, 1, $encoding); 343 } 344 } else { 345 $array = str_split($string, 1); 346 } 347 return implode('', static::shuffleArray($array)); 348 } 349 350 private static function replaceWildcard($string, $wildcard = '#', $callback = 'static::randomDigit') 351 { 352 if (($pos = strpos($string, $wildcard)) === false) { 353 return $string; 354 } 355 for ($i = $pos, $last = strrpos($string, $wildcard, $pos) + 1; $i < $last; $i++) { 356 if ($string[$i] === $wildcard) { 357 $string[$i] = call_user_func($callback); 358 } 359 } 360 return $string; 361 } 362 363 /** 364 * Replaces all hash sign ('#') occurrences with a random number 365 * Replaces all percentage sign ('%') occurrences with a not null number 366 * 367 * @param string $string String that needs to bet parsed 368 * @return string 369 */ 370 public static function numerify($string = '###') 371 { 372 // instead of using randomDigit() several times, which is slow, 373 // count the number of hashes and generate once a large number 374 $toReplace = array(); 375 if (($pos = strpos($string, '#')) !== false) { 376 for ($i = $pos, $last = strrpos($string, '#', $pos) + 1; $i < $last; $i++) { 377 if ($string[$i] === '#') { 378 $toReplace[] = $i; 379 } 380 } 381 } 382 if ($nbReplacements = count($toReplace)) { 383 $maxAtOnce = strlen((string) mt_getrandmax()) - 1; 384 $numbers = ''; 385 $i = 0; 386 while ($i < $nbReplacements) { 387 $size = min($nbReplacements - $i, $maxAtOnce); 388 $numbers .= str_pad(static::randomNumber($size), $size, '0', STR_PAD_LEFT); 389 $i += $size; 390 } 391 for ($i = 0; $i < $nbReplacements; $i++) { 392 $string[$toReplace[$i]] = $numbers[$i]; 393 } 394 } 395 $string = self::replaceWildcard($string, '%', 'static::randomDigitNotNull'); 396 397 return $string; 398 } 399 400 /** 401 * Replaces all question mark ('?') occurrences with a random letter 402 * 403 * @param string $string String that needs to bet parsed 404 * @return string 405 */ 406 public static function lexify($string = '????') 407 { 408 return self::replaceWildcard($string, '?', 'static::randomLetter'); 409 } 410 411 /** 412 * Replaces hash signs ('#') and question marks ('?') with random numbers and letters 413 * An asterisk ('*') is replaced with either a random number or a random letter 414 * 415 * @param string $string String that needs to bet parsed 416 * @return string 417 */ 418 public static function bothify($string = '## ??') 419 { 420 $string = self::replaceWildcard($string, '*', function () { 421 return mt_rand(0, 1) ? '#' : '?'; 422 }); 423 return static::lexify(static::numerify($string)); 424 } 425 426 /** 427 * Replaces * signs with random numbers and letters and special characters 428 * 429 * @example $faker->asciify(''********'); // "s5'G!uC3" 430 * 431 * @param string $string String that needs to bet parsed 432 * @return string 433 */ 434 public static function asciify($string = '****') 435 { 436 return preg_replace_callback('/\*/u', 'static::randomAscii', $string); 437 } 438 439 /** 440 * Transforms a basic regular expression into a random string satisfying the expression. 441 * 442 * @example $faker->regexify('[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'); // sm0@y8k96a.ej 443 * 444 * Regex delimiters '/.../' and begin/end markers '^...$' are ignored. 445 * 446 * Only supports a small subset of the regex syntax. For instance, 447 * unicode, negated classes, unbounded ranges, subpatterns, back references, 448 * assertions, recursive patterns, and comments are not supported. Escaping 449 * support is extremely fragile. 450 * 451 * This method is also VERY slow. Use it only when no other formatter 452 * can generate the fake data you want. For instance, prefer calling 453 * `$faker->email` rather than `regexify` with the previous regular 454 * expression. 455 * 456 * Also note than `bothify` can probably do most of what this method does, 457 * but much faster. For instance, for a dummy email generation, try 458 * `$faker->bothify('?????????@???.???')`. 459 * 460 * @see https://github.com/icomefromthenet/ReverseRegex for a more robust implementation 461 * 462 * @param string $regex A regular expression (delimiters are optional) 463 * @return string 464 */ 465 public static function regexify($regex = '') 466 { 467 // ditch the anchors 468 $regex = preg_replace('/^\/?\^?/', '', $regex); 469 $regex = preg_replace('/\$?\/?$/', '', $regex); 470 // All {2} become {2,2} 471 $regex = preg_replace('/\{(\d+)\}/', '{\1,\1}', $regex); 472 // Single-letter quantifiers (?, *, +) become bracket quantifiers ({0,1}, {0,rand}, {1, rand}) 473 $regex = preg_replace('/(?<!\\\)\?/', '{0,1}', $regex); 474 $regex = preg_replace('/(?<!\\\)\*/', '{0,' . static::randomDigitNotNull() . '}', $regex); 475 $regex = preg_replace('/(?<!\\\)\+/', '{1,' . static::randomDigitNotNull() . '}', $regex); 476 // [12]{1,2} becomes [12] or [12][12] 477 $regex = preg_replace_callback('/(\[[^\]]+\])\{(\d+),(\d+)\}/', function ($matches) { 478 return str_repeat($matches[1], Base::randomElement(range($matches[2], $matches[3]))); 479 }, $regex); 480 // (12|34){1,2} becomes (12|34) or (12|34)(12|34) 481 $regex = preg_replace_callback('/(\([^\)]+\))\{(\d+),(\d+)\}/', function ($matches) { 482 return str_repeat($matches[1], Base::randomElement(range($matches[2], $matches[3]))); 483 }, $regex); 484 // A{1,2} becomes A or AA or \d{3} becomes \d\d\d 485 $regex = preg_replace_callback('/(\\\?.)\{(\d+),(\d+)\}/', function ($matches) { 486 return str_repeat($matches[1], Base::randomElement(range($matches[2], $matches[3]))); 487 }, $regex); 488 // (this|that) becomes 'this' or 'that' 489 $regex = preg_replace_callback('/\((.*?)\)/', function ($matches) { 490 return Base::randomElement(explode('|', str_replace(array('(', ')'), '', $matches[1]))); 491 }, $regex); 492 // All A-F inside of [] become ABCDEF 493 $regex = preg_replace_callback('/\[([^\]]+)\]/', function ($matches) { 494 return '[' . preg_replace_callback('/(\w|\d)\-(\w|\d)/', function ($range) { 495 return implode('', range($range[1], $range[2])); 496 }, $matches[1]) . ']'; 497 }, $regex); 498 // All [ABC] become B (or A or C) 499 $regex = preg_replace_callback('/\[([^\]]+)\]/', function ($matches) { 500 return Base::randomElement(str_split($matches[1])); 501 }, $regex); 502 // replace \d with number and \w with letter and . with ascii 503 $regex = preg_replace_callback('/\\\w/', 'static::randomLetter', $regex); 504 $regex = preg_replace_callback('/\\\d/', 'static::randomDigit', $regex); 505 $regex = preg_replace_callback('/(?<!\\\)\./', 'static::randomAscii', $regex); 506 // remove remaining backslashes 507 $regex = str_replace('\\', '', $regex); 508 // phew 509 return $regex; 510 } 511 512 /** 513 * Converts string to lowercase. 514 * Uses mb_string extension if available. 515 * 516 * @param string $string String that should be converted to lowercase 517 * @return string 518 */ 519 public static function toLower($string = '') 520 { 521 return extension_loaded('mbstring') ? mb_strtolower($string, 'UTF-8') : strtolower($string); 522 } 523 524 /** 525 * Converts string to uppercase. 526 * Uses mb_string extension if available. 527 * 528 * @param string $string String that should be converted to uppercase 529 * @return string 530 */ 531 public static function toUpper($string = '') 532 { 533 return extension_loaded('mbstring') ? mb_strtoupper($string, 'UTF-8') : strtoupper($string); 534 } 535 536 /** 537 * Chainable method for making any formatter optional. 538 * 539 * @param float|integer $weight Set the probability of receiving a null value. 540 * "0" will always return null, "1" will always return the generator. 541 * If $weight is an integer value, then the same system works 542 * between 0 (always get false) and 100 (always get true). 543 * @return mixed|null 544 */ 545 public function optional($weight = 0.5, $default = null) 546 { 547 // old system based on 0.1 <= $weight <= 0.9 548 // TODO: remove in v2 549 if ($weight > 0 && $weight < 1 && mt_rand() / mt_getrandmax() <= $weight) { 550 return $this->generator; 551 } 552 553 // new system with percentage 554 if (is_int($weight) && mt_rand(1, 100) <= $weight) { 555 return $this->generator; 556 } 557 558 return new DefaultGenerator($default); 559 } 560 561 /** 562 * Chainable method for making any formatter unique. 563 * 564 * <code> 565 * // will never return twice the same value 566 * $faker->unique()->randomElement(array(1, 2, 3)); 567 * </code> 568 * 569 * @param boolean $reset If set to true, resets the list of existing values 570 * @param integer $maxRetries Maximum number of retries to find a unique value, 571 * After which an OverflowException is thrown. 572 * @throws \OverflowException When no unique value can be found by iterating $maxRetries times 573 * 574 * @return UniqueGenerator A proxy class returning only non-existing values 575 */ 576 public function unique($reset = false, $maxRetries = 10000) 577 { 578 if ($reset || !$this->unique) { 579 $this->unique = new UniqueGenerator($this->generator, $maxRetries); 580 } 581 582 return $this->unique; 583 } 584 585 /** 586 * Chainable method for forcing any formatter to return only valid values. 587 * 588 * The value validity is determined by a function passed as first argument. 589 * 590 * <code> 591 * $values = array(); 592 * $evenValidator = function ($digit) { 593 * return $digit % 2 === 0; 594 * }; 595 * for ($i=0; $i < 10; $i++) { 596 * $values []= $faker->valid($evenValidator)->randomDigit; 597 * } 598 * print_r($values); // [0, 4, 8, 4, 2, 6, 0, 8, 8, 6] 599 * </code> 600 * 601 * @param Closure $validator A function returning true for valid values 602 * @param integer $maxRetries Maximum number of retries to find a unique value, 603 * After which an OverflowException is thrown. 604 * @throws \OverflowException When no valid value can be found by iterating $maxRetries times 605 * 606 * @return ValidGenerator A proxy class returning only valid values 607 */ 608 public function valid($validator = null, $maxRetries = 10000) 609 { 610 return new ValidGenerator($this->generator, $validator, $maxRetries); 611 } 612} 613