PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Faker/Provider/Base.php

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