diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 62eeaf6ac5..69f1d7d141 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -20,6 +20,21 @@ parameters: count: 1 path: src/Faker/Extension/ContainerBuilder.php + - + message: "#^Method Faker\\\\Generator\\:\\:optional\\(\\) should return Faker\\\\Generator but returns Faker\\\\ChanceGenerator\\.$#" + count: 1 + path: src/Faker/Generator.php + + - + message: "#^Method Faker\\\\Generator\\:\\:unique\\(\\) should return Faker\\\\Generator but returns Faker\\\\UniqueGenerator\\.$#" + count: 1 + path: src/Faker/Generator.php + + - + message: "#^Method Faker\\\\Generator\\:\\:valid\\(\\) should return Faker\\\\Generator but returns Faker\\\\ValidGenerator\\.$#" + count: 1 + path: src/Faker/Generator.php + - message: "#^Unreachable statement \\- code above always terminates\\.$#" count: 1 @@ -735,6 +750,15 @@ parameters: count: 2 path: src/Faker/Provider/Base.php + - + message: + """ + #^Instantiation of deprecated class Faker\\\\DefaultGenerator\\: + Use ChanceGenerator instead$# + """ + count: 1 + path: src/Faker/Provider/Base.php + - message: "#^Negated boolean expression is always false\\.$#" count: 1 diff --git a/psalm.baseline.xml b/psalm.baseline.xml index 3908786358..04ca4ec424 100644 --- a/psalm.baseline.xml +++ b/psalm.baseline.xml @@ -1,5 +1,5 @@ - + 0 @@ -9,10 +9,17 @@ - - ContainerExceptionInterface - - + + $this->uniqueGenerator + new ChanceGenerator($this, $weight, $default) + new ValidGenerator($this, $validator, $maxRetries) + + + self + self + self + + TableRegistry diff --git a/src/Faker/ChanceGenerator.php b/src/Faker/ChanceGenerator.php new file mode 100644 index 0000000000..25aaa4c6e6 --- /dev/null +++ b/src/Faker/ChanceGenerator.php @@ -0,0 +1,60 @@ +default = $default; + $this->generator = $generator; + $this->weight = $weight; + } + + public function ext(string $id) + { + return new self($this->generator->ext($id), $this->weight, $this->default); + } + + /** + * Catch and proxy all generator calls but return only valid values + * + * @param string $attribute + * + * @deprecated Use a method instead. + */ + public function __get($attribute) + { + trigger_deprecation('fakerphp/faker', '1.14', 'Accessing property "%s" is deprecated, use "%s()" instead.', $attribute, $attribute); + + return $this->__call($attribute, []); + } + + /** + * @param string $name + * @param array $arguments + */ + public function __call($name, $arguments) + { + if (mt_rand(1, 100) <= (100 * $this->weight)) { + return call_user_func_array([$this->generator, $name], $arguments); + } + + return $this->default; + } +} diff --git a/src/Faker/Core/Number.php b/src/Faker/Core/Number.php index 17903052d7..f7af0c2960 100644 --- a/src/Faker/Core/Number.php +++ b/src/Faker/Core/Number.php @@ -40,7 +40,7 @@ public function randomDigitNotZero(): int return mt_rand(1, 9); } - public function randomFloat(int $nbMaxDecimals = null, float $min = 0, float $max = null): float + public function randomFloat(?int $nbMaxDecimals = null, float $min = 0, ?float $max = null): float { if (null === $nbMaxDecimals) { $nbMaxDecimals = $this->randomDigit(); diff --git a/src/Faker/DefaultGenerator.php b/src/Faker/DefaultGenerator.php index 81aa4f6452..688f4766a8 100644 --- a/src/Faker/DefaultGenerator.php +++ b/src/Faker/DefaultGenerator.php @@ -4,9 +4,11 @@ /** * This generator returns a default value for all called properties - * and methods. It works with Faker\Generator\Base->optional(). + * and methods. * * @mixin Generator + * + * @deprecated Use ChanceGenerator instead */ class DefaultGenerator { @@ -14,9 +16,16 @@ class DefaultGenerator public function __construct($default = null) { + trigger_deprecation('fakerphp/faker', '1.16', 'Class "%s" is deprecated, use "%s" instead.', __CLASS__, ChanceGenerator::class); + $this->default = $default; } + public function ext() + { + return $this; + } + /** * @param string $attribute * diff --git a/src/Faker/Extension/NumberExtension.php b/src/Faker/Extension/NumberExtension.php index 7d0b45860b..ebfa8c3113 100644 --- a/src/Faker/Extension/NumberExtension.php +++ b/src/Faker/Extension/NumberExtension.php @@ -37,7 +37,7 @@ public function randomDigitNotZero(): int; * * @example 48.8932 */ - public function randomFloat(?int $nbMaxDecimals, float $min, float $max): float; + public function randomFloat(?int $nbMaxDecimals, float $min, ?float $max): float; /** * Returns a random integer with 0 to $nbDigits digits. diff --git a/src/Faker/Generator.php b/src/Faker/Generator.php index e01db1cdbb..3d7ce6a6c9 100644 --- a/src/Faker/Generator.php +++ b/src/Faker/Generator.php @@ -2,7 +2,6 @@ namespace Faker; -use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; /** @@ -122,18 +121,6 @@ * * @method string toUpper($string = '') * - * @property mixed $optional - * - * @method mixed optional($weight = null, $default = null) - * - * @property UniqueGenerator $unique - * - * @method UniqueGenerator unique($reset = false, $maxRetries = 10000) - * - * @property ValidGenerator $valid - * - * @method ValidGenerator valid($validator = null, $maxRetries = 10000) - * * @property int $biasedNumberBetween * * @method int biasedNumberBetween($min = 0, $max = 100, $function = 'sqrt') @@ -565,6 +552,11 @@ class Generator private $container; + /** + * @var UniqueGenerator + */ + private $uniqueGenerator; + public function __construct(ContainerInterface $container = null) { $this->container = $container ?: Extension\ContainerBuilder::getDefault(); @@ -575,7 +567,6 @@ public function __construct(ContainerInterface $container = null) * * @param class-string $id * - * @throws ContainerExceptionInterface * @throws Extension\ExtensionNotFound * * @return T @@ -610,6 +601,79 @@ public function getProviders() return $this->providers; } + /** + * With the unique generator you are guaranteed to never get the same two + * values. + * + * + * // will never return twice the same value + * $faker->unique()->randomElement(array(1, 2, 3)); + * + * + * @param bool $reset If set to true, resets the list of existing values + * @param int $maxRetries Maximum number of retries to find a unique value, + * After which an OverflowException is thrown. + * + * @throws \OverflowException When no unique value can be found by iterating $maxRetries times + * + * @return self A proxy class returning only non-existing values + */ + public function unique($reset = false, $maxRetries = 10000) + { + if ($reset || $this->uniqueGenerator === null) { + $this->uniqueGenerator = new UniqueGenerator($this, $maxRetries); + } + + return $this->uniqueGenerator; + } + + /** + * Get a value only some percentage of the time. + * + * @param float $weight A probability between 0 and 1, 0 means that we always get the default value. + * + * @return self + */ + public function optional(float $weight = 0.5, $default = null) + { + if ($weight > 1) { + trigger_deprecation('fakerphp/faker', '1.16', 'First argument ($weight) to method "optional()" must be between 0 and 1. You passed %f, we assume you meant %f.', $weight, $weight / 100); + $weight = $weight / 100; + } + + return new ChanceGenerator($this, $weight, $default); + } + + /** + * To make sure the value meet some criteria, pass a callable that verifies the + * output. If the validator fails, the generator will try again. + * + * The value validity is determined by a function passed as first argument. + * + * + * $values = array(); + * $evenValidator = function ($digit) { + * return $digit % 2 === 0; + * }; + * for ($i=0; $i < 10; $i++) { + * $values []= $faker->valid($evenValidator)->randomDigit; + * } + * print_r($values); // [0, 4, 8, 4, 2, 6, 0, 8, 8, 6] + * + * + * @param ?\Closure $validator A function returning true for valid values + * @param int $maxRetries Maximum number of retries to find a valid value, + * After which an OverflowException is thrown. + * + * @throws \OverflowException When no valid value can be found by iterating $maxRetries times + * + * @return self A proxy class returning only valid values + */ + public function valid(?\Closure $validator = null, int $maxRetries = 10000) + { + return new ValidGenerator($this, $validator, $maxRetries); + } + public function seed($seed = null) { if ($seed === null) { diff --git a/src/Faker/UniqueGenerator.php b/src/Faker/UniqueGenerator.php index 8dddeec283..fef167b6d1 100644 --- a/src/Faker/UniqueGenerator.php +++ b/src/Faker/UniqueGenerator.php @@ -2,6 +2,8 @@ namespace Faker; +use Faker\Extension\Extension; + /** * Proxy for other generators that returns only unique values. * @@ -11,14 +13,7 @@ */ class UniqueGenerator { - /** - * @var Generator - */ protected $generator; - - /** - * @var int - */ protected $maxRetries; /** @@ -34,12 +29,20 @@ class UniqueGenerator protected $uniques = []; /** - * @param int $maxRetries + * @param Extension|Generator $generator + * @param int $maxRetries + * @param array> $uniques */ - public function __construct(Generator $generator, $maxRetries = 10000) + public function __construct($generator, $maxRetries = 10000, &$uniques = []) { $this->generator = $generator; $this->maxRetries = $maxRetries; + $this->uniques = &$uniques; + } + + public function ext(string $id) + { + return new self($this->generator->ext($id), $this->maxRetries, $this->uniques); } /** diff --git a/src/Faker/ValidGenerator.php b/src/Faker/ValidGenerator.php index 691d46dda9..bf40945697 100644 --- a/src/Faker/ValidGenerator.php +++ b/src/Faker/ValidGenerator.php @@ -2,6 +2,8 @@ namespace Faker; +use Faker\Extension\Extension; + /** * Proxy for other generators, to return only valid values. Works with * Faker\Generator\Base->valid() @@ -15,10 +17,11 @@ class ValidGenerator protected $maxRetries; /** - * @param callable|null $validator - * @param int $maxRetries + * @param Extension|Generator $generator + * @param callable|null $validator + * @param int $maxRetries */ - public function __construct(Generator $generator, $validator = null, $maxRetries = 10000) + public function __construct($generator, $validator = null, $maxRetries = 10000) { if (null === $validator) { $validator = static function () { @@ -32,6 +35,11 @@ public function __construct(Generator $generator, $validator = null, $maxRetries $this->maxRetries = $maxRetries; } + public function ext(string $id) + { + return new self($this->generator->ext($id), $this->validator, $this->maxRetries); + } + /** * Catch and proxy all generator calls but return only valid values * diff --git a/test/Faker/DefaultGeneratorTest.php b/test/Faker/DefaultGeneratorTest.php index e009c5ccef..23eced28ea 100644 --- a/test/Faker/DefaultGeneratorTest.php +++ b/test/Faker/DefaultGeneratorTest.php @@ -25,6 +25,9 @@ public function testGeneratorReturnsDefaultValueForAnyPropertyGet() self::assertNotNull($generator->bar); } + /** + * @group legacy + */ public function testGeneratorReturnsDefaultValueForAnyMethodCall() { $generator = new DefaultGenerator(123); diff --git a/test/Faker/Provider/BaseTest.php b/test/Faker/Provider/BaseTest.php index 2e874eafa5..9e1f8ef59f 100644 --- a/test/Faker/Provider/BaseTest.php +++ b/test/Faker/Provider/BaseTest.php @@ -401,7 +401,7 @@ public function testOptionalAllowsChainingProviderCallRandomlyReturnNull() $values = []; for ($i = 0; $i < 10; ++$i) { - $values[] = $faker->optional(50)->randomDigit; + $values[] = $faker->optional(0.5)->randomDigit; } self::assertContains(null, $values); } @@ -532,11 +532,11 @@ public function testValidThrowsExceptionWhenNoValidValueCanBeGenerated() } } - public function testValidThrowsExceptionWhenParameterIsNotCollable() + public function testValidThrowsExceptionWhenParameterIsNotCallable() { - $this->expectException(\InvalidArgumentException::class); $faker = new \Faker\Generator(); $faker->addProvider(new \Faker\Provider\Base($faker)); + $this->expectException(\TypeError::class); $faker->valid(12)->randomElement([1, 3, 5, 7, 9]); } diff --git a/test/Faker/UniqueGeneratorTest.php b/test/Faker/UniqueGeneratorTest.php new file mode 100644 index 0000000000..bf7b147c8d --- /dev/null +++ b/test/Faker/UniqueGeneratorTest.php @@ -0,0 +1,27 @@ +expectException(\OverflowException::class); + + for ($i = 0; $i < 3; ++$i) { + $this->faker->unique()->ext(NumberExtension::class)->numberBetween(0, 1); + } + } + + public function testUniqueGeneratorRetries(): void + { + for ($i = 0; $i < 10; ++$i) { + $this->faker->unique()->ext(NumberExtension::class)->numberBetween(0, 9); + } + } +} diff --git a/test/Faker/ValidGeneratorTest.php b/test/Faker/ValidGeneratorTest.php new file mode 100644 index 0000000000..e9fafe967f --- /dev/null +++ b/test/Faker/ValidGeneratorTest.php @@ -0,0 +1,19 @@ +expectException(\OverflowException::class); + + $validator = static function ($value) { + return $value !== 0; + }; + $this->faker->valid($validator, 10)->numberBetween(0, 0); + } +}