Skip to content

Commit 7d4cce7

Browse files
committed
feat: add faker support
2 parents 4aea710 + 6bbebce commit 7d4cce7

27 files changed

+2237
-0
lines changed

src/Core/Fake/Faker.php

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Constructo\Core\Fake;
6+
7+
use Constructo\Contract\Formatter;
8+
use Constructo\Contract\Testing\Faker as Contract;
9+
use Constructo\Core\Fake\Resolver\FromCollection;
10+
use Constructo\Core\Fake\Resolver\FromDefaultValue;
11+
use Constructo\Core\Fake\Resolver\FromDependency;
12+
use Constructo\Core\Fake\Resolver\FromEnum;
13+
use Constructo\Core\Fake\Resolver\FromPreset;
14+
use Constructo\Core\Fake\Resolver\FromTypeAttributes;
15+
use Constructo\Core\Fake\Resolver\FromTypeBuiltin;
16+
use Constructo\Core\Fake\Resolver\FromTypeDate;
17+
use Constructo\Support\Reflective\Engine;
18+
use Constructo\Support\Reflective\Factory\Target;
19+
use Constructo\Support\Reflective\Notation;
20+
use Constructo\Support\Set;
21+
use DateTime;
22+
use Faker\Factory;
23+
use Faker\Generator;
24+
use ReflectionException;
25+
use ReflectionParameter;
26+
27+
use function Constructo\Cast\stringify;
28+
use function getenv;
29+
30+
/**
31+
* @method string citySuffix()
32+
* @method string streetSuffix()
33+
* @method string buildingNumber()
34+
* @method string city()
35+
* @method string streetName()
36+
* @method string streetAddress()
37+
* @method string postcode()
38+
* @method string address()
39+
* @method string country()
40+
* @method float latitude($min = -90, $max = 90)
41+
* @method float longitude($min = -180, $max = 180)
42+
* @method float[] localCoordinates()
43+
* @method int randomDigitNotNull()
44+
* @method mixed passthrough($value)
45+
* @method string randomLetter()
46+
* @method string randomAscii()
47+
* @method array randomElements($array = ['a', 'b', 'c'], $count = 1, $allowDuplicates = false)
48+
* @method mixed randomElement($array = ['a', 'b', 'c'])
49+
* @method int|string|null randomKey($array = [])
50+
* @method array|string shuffle($arg = '')
51+
* @method array shuffleArray($array = [])
52+
* @method string shuffleString($string = '', $encoding = 'UTF-8')
53+
* @method string numerify($string = '###')
54+
* @method string lexify($string = '????')
55+
* @method string bothify($string = '## ??')
56+
* @method string asciify($string = '****')
57+
* @method string regexify($regex = '')
58+
* @method string toLower($string = '')
59+
* @method string toUpper($string = '')
60+
* @method int biasedNumberBetween($min = 0, $max = 100, $function = 'sqrt')
61+
* @method string hexColor()
62+
* @method string safeHexColor()
63+
* @method array rgbColorAsArray()
64+
* @method string rgbColor()
65+
* @method string rgbCssColor()
66+
* @method string rgbaCssColor()
67+
* @method string safeColorName()
68+
* @method string colorName()
69+
* @method string hslColor()
70+
* @method array hslColorAsArray()
71+
* @method string company()
72+
* @method string companySuffix()
73+
* @method string jobTitle()
74+
* @method int unixTime($max = 'now')
75+
* @method DateTime dateTime($max = 'now', $timezone = null)
76+
* @method DateTime dateTimeAD($max = 'now', $timezone = null)
77+
* @method string iso8601($max = 'now')
78+
* @method string date($format = 'Y-m-d', $max = 'now')
79+
* @method string time($format = 'H:i:s', $max = 'now')
80+
* @method DateTime dateTimeBetween($startDate = '-30 years', $endDate = 'now', $timezone = null)
81+
* @method DateTime dateTimeInInterval($date = '-30 years', $interval = '+5 days', $timezone = null)
82+
* @method DateTime dateTimeThisCentury($max = 'now', $timezone = null)
83+
* @method DateTime dateTimeThisDecade($max = 'now', $timezone = null)
84+
* @method DateTime dateTimeThisYear($max = 'now', $timezone = null)
85+
* @method DateTime dateTimeThisMonth($max = 'now', $timezone = null)
86+
* @method string amPm($max = 'now')
87+
* @method string dayOfMonth($max = 'now')
88+
* @method string dayOfWeek($max = 'now')
89+
* @method string month($max = 'now')
90+
* @method string monthName($max = 'now')
91+
* @method string year($max = 'now')
92+
* @method string century()
93+
* @method string timezone($countryCode = null)
94+
* @method void setDefaultTimezone($timezone = null)
95+
* @method string getDefaultTimezone()
96+
* @method string file($sourceDirectory = '/tmp', $targetDirectory = '/tmp', $fullPath = true)
97+
* @method string randomHtml($maxDepth = 4, $maxWidth = 4)
98+
* @method string imageUrl($width = 640, $height = 480, $category = null, $randomize = true, $word = null, $gray = false, string $format = 'png')
99+
* @method string image($dir = null, $width = 640, $height = 480, $category = null, $fullPath = true, $randomize = true, $word = null, $gray = false, string $format = 'png')
100+
* @method string email()
101+
* @method string safeEmail()
102+
* @method string freeEmail()
103+
* @method string companyEmail()
104+
* @method string freeEmailDomain()
105+
* @method string safeEmailDomain()
106+
* @method string userName()
107+
* @method string password($minLength = 6, $maxLength = 20)
108+
* @method string domainName()
109+
* @method string domainWord()
110+
* @method string tld()
111+
* @method string url()
112+
* @method string slug($nbWords = 6, $variableNbWords = true)
113+
* @method string ipv4()
114+
* @method string ipv6()
115+
* @method string localIpv4()
116+
* @method string macAddress()
117+
* @method string word()
118+
* @method array|string words($nb = 3, $asText = false)
119+
* @method string sentence($nbWords = 6, $variableNbWords = true)
120+
* @method array|string sentences($nb = 3, $asText = false)
121+
* @method string paragraph($nbSentences = 3, $variableNbSentences = true)
122+
* @method array|string paragraphs($nb = 3, $asText = false)
123+
* @method string text($maxNbChars = 200)
124+
* @method bool boolean($chanceOfGettingTrue = 50)
125+
* @method string md5()
126+
* @method string sha1()
127+
* @method string sha256()
128+
* @method string countryCode()
129+
* @method string countryISOAlpha3()
130+
* @method string languageCode()
131+
* @method string currencyCode()
132+
* @method string emoji()
133+
* @method string creditCardType()
134+
* @method string creditCardNumber($type = null, $formatted = false, $separator = '-')
135+
* @method DateTime creditCardExpirationDate($valid = true)
136+
* @method string creditCardExpirationDateString($valid = true, $expirationDateFormat = null)
137+
* @method array creditCardDetails($valid = true)
138+
* @method string iban($countryCode = null, $prefix = '', $length = null)
139+
* @method string swiftBicNumber()
140+
* @method string name($gender = null)
141+
* @method string firstName($gender = null)
142+
* @method string firstNameMale()
143+
* @method string firstNameFemale()
144+
* @method string lastName($gender = null)
145+
* @method string title($gender = null)
146+
* @method string titleMale()
147+
* @method string titleFemale()
148+
* @method string phoneNumber()
149+
* @method string e164PhoneNumber()
150+
* @method int imei()
151+
* @method string realText($maxNbChars = 200, $indexSize = 2)
152+
* @method string realTextBetween($minNbChars = 160, $maxNbChars = 200, $indexSize = 2)
153+
* @method string macProcessor()
154+
* @method string linuxProcessor()
155+
* @method string userAgent()
156+
* @method string chrome()
157+
* @method string msedge()
158+
* @method string firefox()
159+
* @method string safari()
160+
* @method string opera()
161+
* @method string internetExplorer()
162+
* @method string windowsPlatformToken()
163+
* @method string macPlatformToken()
164+
* @method string iosMobileToken()
165+
* @method string linuxPlatformToken()
166+
* @method string uuid()
167+
* @method string mimeType()
168+
* @method string fileExtension()
169+
* @method string filePath()
170+
* @method string bloodType()
171+
* @method string bloodRh()
172+
* @method string bloodGroup()
173+
* @method string ean13()
174+
* @method string ean8()
175+
* @method string isbn10()
176+
* @method string isbn13()
177+
* @method int numberBetween($int1 = 0, $int2 = 2147483647)
178+
* @method int randomDigit()
179+
* @method int randomDigitNot($except)
180+
* @method int randomDigitNotZero()
181+
* @method float randomFloat($nbMaxDecimals = null, $min = 0, $max = null)
182+
* @method int randomNumber($nbDigits = null, $strict = false)
183+
* @method string semver(bool $preRelease = false, bool $build = false)
184+
* @method string vat($spacedNationalPrefix = true)
185+
*/
186+
class Faker extends Engine implements Contract
187+
{
188+
protected readonly Generator $generator;
189+
190+
/**
191+
* @param array<callable|Formatter> $formatters
192+
* @SuppressWarnings(StaticAccess)
193+
*/
194+
public function __construct(
195+
Notation $case = Notation::SNAKE,
196+
array $formatters = [],
197+
?string $locale = null,
198+
) {
199+
parent::__construct($case, $formatters);
200+
201+
$this->generator = Factory::create($this->locale($locale));
202+
}
203+
204+
public function __call(string $name, array $arguments): mixed
205+
{
206+
return $this->generate($name, $arguments);
207+
}
208+
209+
/**
210+
* @template U of object
211+
* @param class-string<U> $class
212+
* @throws ReflectionException
213+
*/
214+
public function fake(string $class, array $presets = []): Set
215+
{
216+
$target = Target::createFrom($class);
217+
$parameters = $target->getReflectionParameters();
218+
if (empty($parameters)) {
219+
return Set::createFrom([]);
220+
}
221+
222+
return $this->resolveParameters($parameters, new Set($presets));
223+
}
224+
225+
public function generate(string $name, array $arguments = []): mixed
226+
{
227+
return $this->generator->__call($name, $arguments);
228+
}
229+
230+
public function generator(): Generator
231+
{
232+
return $this->generator;
233+
}
234+
235+
/**
236+
* @param array<ReflectionParameter> $parameters
237+
*/
238+
private function resolveParameters(array $parameters, Set $presets): Set
239+
{
240+
$values = [];
241+
foreach ($parameters as $parameter) {
242+
$field = $this->casedField($parameter);
243+
$generated = (new FromDependency($this->notation, $this->formatters))
244+
->then(new FromTypeDate($this->notation, $this->formatters))
245+
->then(new FromCollection($this->notation, $this->formatters))
246+
->then(new FromTypeBuiltin($this->notation, $this->formatters))
247+
->then(new FromTypeAttributes($this->notation, $this->formatters))
248+
->then(new FromEnum($this->notation, $this->formatters))
249+
->then(new FromDefaultValue($this->notation, $this->formatters))
250+
->then(new FromPreset($this->notation, $this->formatters))
251+
->resolve($parameter, $presets);
252+
253+
if ($generated === null) {
254+
continue;
255+
}
256+
$values[$field] = $generated->content;
257+
}
258+
return Set::createFrom($values);
259+
}
260+
261+
private function locale(?string $locale): string
262+
{
263+
$fallback = static function (string $default = 'en_US'): string {
264+
$locale = stringify(getenv('FAKER_LOCALE'), $default);
265+
return empty($locale) ? $default : $locale;
266+
};
267+
return $locale ?? $fallback();
268+
}
269+
}

src/Core/Fake/Resolver.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Constructo\Core\Fake;
6+
7+
use Constructo\Support\Set;
8+
use Constructo\Support\Value;
9+
use ReflectionNamedType;
10+
use ReflectionParameter;
11+
use ReflectionType;
12+
use ReflectionUnionType;
13+
14+
use function count;
15+
16+
abstract class Resolver extends Faker
17+
{
18+
protected ?Resolver $previous = null;
19+
20+
final public function then(Resolver $resolver): Resolver
21+
{
22+
$resolver->previous($this);
23+
return $resolver;
24+
}
25+
26+
public function resolve(ReflectionParameter $parameter, Set $presets): ?Value
27+
{
28+
if (isset($this->previous)) {
29+
return $this->previous->resolve($parameter, $presets);
30+
}
31+
return null;
32+
}
33+
34+
final protected function previous(Resolver $previous): void
35+
{
36+
$this->previous = $previous;
37+
}
38+
39+
protected function detectReflectionType(?ReflectionType $type): ?string
40+
{
41+
if ($type instanceof ReflectionNamedType) {
42+
return $type->getName();
43+
}
44+
if ($type instanceof ReflectionUnionType) {
45+
$reflectionNamedTypes = $type->getTypes();
46+
$index = $this->generator->numberBetween(0, count($reflectionNamedTypes) - 1);
47+
return $this->detectReflectionType($reflectionNamedTypes[$index]);
48+
}
49+
return null;
50+
}
51+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Constructo\Core\Fake\Resolver;
6+
7+
use Constructo\Core\Fake\Resolver;
8+
use Constructo\Support\Set;
9+
use Constructo\Support\Value;
10+
use Constructo\Testing\MakeExtension;
11+
use Constructo\Testing\ManagedExtension;
12+
use Constructo\Type\Collection;
13+
use ReflectionClass;
14+
use ReflectionException;
15+
use ReflectionParameter;
16+
17+
final class FromCollection extends Resolver
18+
{
19+
use MakeExtension;
20+
use ManagedExtension;
21+
22+
/**
23+
* @throws ReflectionException
24+
*/
25+
public function resolve(ReflectionParameter $parameter, Set $presets): ?Value
26+
{
27+
$collectionName = $this->detectCollectionName($parameter);
28+
if ($collectionName) {
29+
return $this->resolveCollection($collectionName);
30+
}
31+
return parent::resolve($parameter, $presets);
32+
}
33+
34+
/**
35+
* @param class-string<Collection> $collectionName
36+
* @throws ReflectionException
37+
*/
38+
private function resolveCollection(string $collectionName): Value
39+
{
40+
$reflection = new ReflectionClass($collectionName);
41+
$type = $this->detectCollectionType($reflection);
42+
43+
return new Value($type === null ? [] : $this->resolveCollectionFake($type));
44+
}
45+
46+
/**
47+
* @param class-string<object> $type
48+
* @throws ReflectionException
49+
*/
50+
private function resolveCollectionFake(string $type): array
51+
{
52+
$total = $this->generator->numberBetween(1, 5);
53+
return array_map(fn () => $this->fake($type)->toArray(), range(1, $total));
54+
}
55+
}

0 commit comments

Comments
 (0)