icomefromthenet/reverse-regex
Generate sample strings from regular expressions for test data and validation. ReverseRegex parses a supported subset of regex syntax (literals, groups, character classes, quantifiers, escapes, some Unicode via \X{####}) and outputs randomized matching text via PHP generators.
Installation:
Add to composer.json:
"require": {
"icomefromthenet/reverse-regex": "dev-master"
}
Run composer update.
Basic Usage: Generate a string from a simple regex in a Laravel test or helper:
use ReverseRegex\Lexer;
use ReverseRegex\Random\SimpleRandom;
use ReverseRegex\Parser;
use ReverseRegex\Generator\Scope;
$lexer = new Lexer('[a-z]{5}');
$gen = new SimpleRandom(10007);
$parser = new Parser($lexer, new Scope(), new Scope());
$result = $parser->parse()->getResult()->generate('', $gen);
// Outputs: e.g., "abcde"
First Use Case:
Replace hardcoded test data in a Laravel FeatureTest:
public function test_form_validation() {
$fakeInput = generateFromRegex('[a-z]{5,20}'); // Dynamic test string
$response = $this->post('/register', ['name' => $fakeInput]);
$response->assertValid();
}
ausphone.php, auspostcode.php).[a-z], \d, {1,5}).PHPUnit Data Providers:
public function regexProvider() {
return [
['[a-z]{5}', 'abcde'],
['\d{10}', '1234567890'],
['[a-z]{3}-\d{4}', 'abc-1234'],
];
}
public function test_generated_data_matches_regex() {
foreach ($this->regexProvider() as [$pattern, $expected]) {
$generated = generateFromRegex($pattern);
$this->assertMatchesRegularExpression($pattern, $generated);
}
}
Pest Test Helpers:
beforeEach(function () {
$this->fakeName = generateFromRegex('[A-Za-z]{5,20}');
$this->fakeEmail = generateFromRegex('[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}');
});
it('validates user input', function () {
$response = post('/register', [
'name' => $this->fakeName,
'email' => $this->fakeEmail,
]);
expect($response)->toBeValid();
});
Use in DatabaseSeeder to populate test data:
public function run() {
$fakeUsers = collect(range(1, 100))->map(fn($i) => [
'name' => generateFromRegex('[A-Za-z]{5,20}'),
'email' => generateFromRegex('[a-z0-9._%+-]+@example\.com'),
]);
User::insert($fakeUsers->toArray());
}
Generate fake payloads for API tests:
$fakePayload = [
'phone' => generateFromRegex('04\d{8}'), // Australian mobile
'postcode' => generateFromRegex('\d{4}'), // Australian postcode
];
$this->postJson('/api/users', $fakePayload)->assertOk();
Test regex boundaries (e.g., max/min lengths):
// Test max length (e.g., Laravel's max:5 rule)
$maxLengthString = generateFromRegex('[a-z]{5}');
$this->assertEquals(5, strlen($maxLengthString));
// Test min length (e.g., Laravel's min:3 rule)
$minLengthString = generateFromRegex('[a-z]{3}');
$this->assertEquals(3, strlen($minLengthString));
$testEmails = ['user@example.com', 'test123@domain.com'];
$testEmails = collect(range(1, 5))->map(fn($i) =>
generateFromRegex('[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}')
);
Use in GitHub Actions to generate test data for every PR:
# .github/workflows/tests.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: composer install
- run: php artisan test -- --filter "test_dynamic_data"
// TestCase
public function test_dynamic_data() {
$dynamicData = generateFromRegex('[a-z]{10}');
$this->assertTrue(preg_match('/^[a-z]{10}$/', $dynamicData));
}
Generate malformed inputs to break regex-based validators:
public function test_regex_fuzz() {
$malformedInput = generateFromRegex('[a-z]{100}'); // Exceed max length
$response = $this->post('/validate', ['input' => $malformedInput]);
$response->assertSessionHasErrors('input');
}
Register the generator as a singleton:
// app/Providers/AppServiceProvider.php
public function register() {
$this->app->singleton('regex.generator', function () {
return new class {
public function generate(string $pattern, int $seed = null): string {
$lexer = new Lexer($pattern);
$gen = new SimpleRandom($seed);
return (new Parser($lexer, new Scope(), new Scope()))
->parse()
->getResult()
->generate('', $gen);
}
};
});
}
Use in tests:
$this->app->make('regex.generator')->generate('[a-z]{5}');
Extend SimpleRandom for seeded reproducibility:
class SeededRandom extends SimpleRandom {
public function __construct(int $seed) {
parent::__construct($seed);
}
}
Ensure generated data matches the regex:
function assertMatchesRegex(string $pattern, string $subject): void {
$this->assertTrue(
preg_match($pattern, $subject),
"Generated string '$subject' does not match pattern '$pattern'"
);
}
Generate bulk test data via CLI:
// app/Console/Commands/GenerateTestData.php
public function handle() {
$count = $this->option('count') ?? 10;
for ($i = 0; $i < $count; $i++) {
$data = generateFromRegex('[a-z]{5}-\d{4}');
$this->info($data);
}
}
Run:
php artisan generate:test-data --count=50
* or + can generate strings up to PHP_INT_MAX characters, causing memory issues or infinite loops.{1,10} instead of +).
// Bad: Unbounded
$lexer = new Lexer('[a-z]*'); // Risk of infinite loop
// Good: Bounded
$lexer = new Lexer('[a-z]{1,10}');
., *, ?) in the input pattern.function escapeRegex(string $input): string {
return preg_replace('/[.*+?^${}()|[\]\\]/', '\\$0', $input);
}
$safePattern = escapeRegex('file.name.txt');
\p{} (Unicode properties) and complex grapheme clusters areHow can I help you explore Laravel packages today?