1
0
mirror of https://github.com/fzaninotto/Faker.git synced 2025-03-19 06:49:50 +01:00

Add unique() modifier

* Add `BaseProvider::unique($reset = false)
* Add `UniqueGenerator` proxy class
* Refactor `optional()` tests for better coverage
* Merge `optional()` and `unique()` documentations

Closes #171
This commit is contained in:
Francois Zaninotto 2013-09-23 22:35:04 +02:00
parent 34e1b2de0f
commit fac6a3839c
4 changed files with 209 additions and 14 deletions

View File

@ -195,15 +195,46 @@ Each of the generator properties (like `name`, `address`, and `lorem`) are calle
safeColorName // 'fuchsia'
colorName // 'Gainsbor'
## Optional data
## Unique and Optional modifiers
All formatters can be made optional by chaining `optional`. When optional, the formatter will randomly return `NULL`, which can be useful for seeding non-required fields. For example:
Faker provides two special providers, `unique()` and `optional()`, to be called before any provider. `optional()` can be useful for seeding non-required fields, like a mobile telephone number ; `unique()` is required to populate fields that cannot accept twice the same value, like primary identifiers.
$faker->optional->country
You can skew the randomization towards more nulls or less by passing an argument to `optional()`. At 0, *only* `NULL` is returned. At 1, it is never returned.
```php
// unique() forces providers to return unique values
$values = array();
for ($i=0; $i < 10; $i++) {
// get a random digit, but always a new one, to avoid duplicates
$values []= $faker->unique()->randomDigit;
}
print_r($values); // [4, 1, 8, 5, 0, 2, 6, 9, 7, 3]
$faker->optional(.75)->country
// providers with a limited range will throw an exception when no new unique value can be generated
$values = array();
try {
for ($i=0; $i < 10; $i++) {
$values []= $faker->unique()->randomDigitNotNull;
}
} catch (\OverflowException $e) {
echo "There are only 9 unique digits not null, Faker can't generate 10 of them!";
}
// you can reset the unique modifier for all providers by passing true as first argument
$faker->unique($reset = true)->randomDigitNotNull; // will not throw OverflowException since unique() was reset
// tip: unique() keeps one array of values per provider
// optional() sometimes bypasses the provider to return null instead
$values = array();
for ($i=0; $i < 10; $i++) {
// get a random digit, but also null sometimes
$values []= $faker->optional()->randomDigit;
}
print_r($values); // [1, 4, null, 9, 5, null, null, 4, 6, null]
// optional takes a weight argument to make the null occurrence impossible (value 0) or systematic (value 1)
$faker->optional($weight = 0.1)->randomDigit; // 10% chance to get null
$faker->optional($weight = 0.9)->randomDigit; // 90% chance to get null
// the default $weight value is 0.5
```
## Localization

View File

@ -3,6 +3,7 @@
namespace Faker\Provider;
use Faker\Generator;
use Faker\UniqueGenerator;
class Base
{
@ -11,6 +12,11 @@ class Base
*/
protected $generator;
/**
* @var \Faker\UniqueGenerator
*/
protected $unique;
/**
* @param \Faker\Generator $generator
*/
@ -203,4 +209,28 @@ class Base
return new \Faker\NullGenerator();
}
/**
* Chainable method for making any formatter unique.
*
* <code>
* // will never return twice the same value
* $faker->unique()->randomElement(array(1, 2, 3));
* </code>
*
* @param boolean $reset If set to true, resets the list of existing values
* @param integer $maxRetries Maximum number of retries to find a unique value,
* After which an OverflowExcption is thrown.
* @throws OverflowException When no unique value can be found by iterating $maxRetries times
*
* @return UniqueGenerator A proxy class returning only existing values
*/
public function unique($reset = false, $maxRetries = 10000)
{
if ($reset || !$this->unique) {
$this->unique = new UniqueGenerator($this->generator, $maxRetries);
}
return $this->unique;
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Faker;
/**
* Proxy for other generators, to return only unique values. Works with
* Faker\Generator\Base->unique()
*/
class UniqueGenerator
{
protected $generator;
protected $maxRetries;
protected $uniques = array();
public function __construct(Generator $generator, $maxRetries)
{
$this->generator = $generator;
$this->maxRetries = $maxRetries;
}
/**
* Catch and proxy all generator calls but return only unique values
*/
public function __get($attribute)
{
return $this->__call($attribute, array());
}
/**
* Catch and proxy all generator calls with arguments but return only unique values
*/
public function __call($name, $arguments)
{
if (!isset($this->uniques[$name])) {
$this->uniques[$name] = array();
}
$i = 0;
do {
$res = call_user_func_array(array($this->generator, $name), $arguments);
$i++;
if ($i > $this->maxRetries) {
throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a unique value', $this->maxRetries));
}
} while (in_array($res, $this->uniques[$name]));
$this->uniques[$name][]= $res;
return $res;
}
}

View File

@ -131,17 +131,102 @@ class BaseTest extends \PHPUnit_Framework_TestCase
$this->assertRegExp('/foo[a-z]Ba\dr/', BaseProvider::bothify('foo?Ba#r'));
}
public function testOptionalChainingOfProperty()
public function testOptionalReturnsProviderValueWhenCalledWithWeight1()
{
$faker = \Faker\Factory::create();
$this->assertNotNull($faker->optional(1)->randomNumber);
$this->assertNull($faker->optional(0)->randomNumber);
$faker = new \Faker\Generator();
$faker->addProvider(new \Faker\Provider\Base($faker));
$this->assertNotNull($faker->optional(1)->randomDigit);
}
public function testOptionalChainingOfMethod()
public function testOptionalReturnsNullWhenCalledWithWeight0()
{
$faker = \Faker\Factory::create();
$this->assertNotNull($faker->optional(1)->randomNumber(4));
$this->assertNull($faker->optional(0)->randomNumber(4));
$faker = new \Faker\Generator();
$faker->addProvider(new \Faker\Provider\Base($faker));
$this->assertNull($faker->optional(0)->randomDigit);
}
public function testOptionalAllowsChainingPropertyAccess()
{
$faker = new \Faker\Generator();
$faker->addProvider(new \Faker\Provider\Base($faker));
$faker->addProvider(new \ArrayObject(array(1))); // hack because method_exists forbids stubs
$this->assertEquals(1, $faker->optional(1)->count);
$this->assertNull($faker->optional(0)->count);
}
public function testOptionalAllowsChainingMethodCall()
{
$faker = new \Faker\Generator();
$faker->addProvider(new \Faker\Provider\Base($faker));
$faker->addProvider(new \ArrayObject(array(1))); // hack because method_exists forbids stubs
$this->assertEquals(1, $faker->optional(1)->count());
$this->assertNull($faker->optional(0)->count());
}
public function testOptionalAllowsChainingProviderCallRandomlyReturnNull()
{
$faker = new \Faker\Generator();
$faker->addProvider(new \Faker\Provider\Base($faker));
$values = array();
for ($i=0; $i < 10; $i++) {
$values[]= $faker->optional()->randomDigit;
}
$this->assertContains(null, $values);
}
public function testUniqueAllowsChainingPropertyAccess()
{
$faker = new \Faker\Generator();
$faker->addProvider(new \Faker\Provider\Base($faker));
$faker->addProvider(new \ArrayObject(array(1))); // hack because method_exists forbids stubs
$this->assertEquals(1, $faker->unique()->count);
}
public function testUniqueAllowsChainingMethodCall()
{
$faker = new \Faker\Generator();
$faker->addProvider(new \Faker\Provider\Base($faker));
$faker->addProvider(new \ArrayObject(array(1))); // hack because method_exists forbids stubs
$this->assertEquals(1, $faker->unique()->count());
}
public function testUniqueReturnsOnlyUniqueValues()
{
$faker = new \Faker\Generator();
$faker->addProvider(new \Faker\Provider\Base($faker));
$values = array();
for ($i=0; $i < 10; $i++) {
$values[]= $faker->unique()->randomDigit;
}
sort($values);
$this->assertEquals(array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), $values);
}
/**
* @expectedException OverflowException
*/
public function testUniqueThrowsExceptionWhenNoUniqueValueCanBeGenerated()
{
$faker = new \Faker\Generator();
$faker->addProvider(new \Faker\Provider\Base($faker));
for ($i=0; $i < 11; $i++) {
$faker->unique()->randomDigit;
}
}
public function testUniqueCanResetUniquesWhenPassedTrueAsArgument()
{
$faker = new \Faker\Generator();
$faker->addProvider(new \Faker\Provider\Base($faker));
$values = array();
for ($i=0; $i < 10; $i++) {
$values[]= $faker->unique()->randomDigit;
}
$values[]= $faker->unique(true)->randomDigit;
for ($i=0; $i < 9; $i++) {
$values[]= $faker->unique()->randomDigit;
}
sort($values);
$this->assertEquals(array(0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9), $values);
}
}