Skip to content

Commit bf99021

Browse files
committed
string generator library
1 parent fa59f3d commit bf99021

File tree

15 files changed

+1144
-6
lines changed

15 files changed

+1144
-6
lines changed

.editorconfig

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
9+
[*.php]
10+
indent_style = space
11+
indent_size = 4
12+
13+
[*.{yml,yaml}]
14+
indent_style = space
15+
indent_size = 2
16+
17+
[*.{json,xml,neon}]
18+
indent_style = space
19+
indent_size = 4
20+
21+
[*.md]
22+
trim_trailing_whitespace = false

.gitattributes

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
* text=auto eol=lf
2+
3+
/.github/ export-ignore
4+
/tests/ export-ignore
5+
/.editorconfig export-ignore
6+
/.gitattributes export-ignore
7+
/.gitignore export-ignore
8+
/phpbench.json export-ignore
9+
/phpstan.neon export-ignore
10+
/phpunit.xml.dist export-ignore

.github/workflows/ci.yml

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
branches: [master]
8+
9+
permissions:
10+
contents: read
11+
12+
env:
13+
PHP_PRIMARY: '8.5'
14+
15+
jobs:
16+
validate:
17+
name: Validate composer.json
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: actions/checkout@v6
21+
22+
- uses: shivammathur/setup-php@v2
23+
with:
24+
php-version: ${{ env.PHP_PRIMARY }}
25+
tools: composer:v2
26+
27+
- run: composer validate --strict
28+
29+
tests:
30+
name: Tests / PHP ${{ matrix.php }}
31+
runs-on: ubuntu-latest
32+
needs: validate
33+
strategy:
34+
fail-fast: false
35+
matrix:
36+
php: ['8.2', '8.3', '8.4', '8.5', '8.6']
37+
38+
steps:
39+
- uses: actions/checkout@v6
40+
41+
- uses: shivammathur/setup-php@v2
42+
with:
43+
php-version: ${{ matrix.php }}
44+
coverage: ${{ matrix.php == env.PHP_PRIMARY && 'xdebug' || 'none' }}
45+
tools: composer:v2
46+
47+
- name: Cache Composer packages
48+
uses: actions/cache@v4
49+
with:
50+
path: vendor
51+
key: php-${{ matrix.php }}-${{ hashFiles('composer.json') }}
52+
restore-keys: php-${{ matrix.php }}-
53+
54+
- run: composer install --no-interaction --no-progress --prefer-dist
55+
56+
- name: Run tests
57+
if: matrix.php != env.PHP_PRIMARY
58+
run: vendor/bin/phpunit --colors=always
59+
60+
- name: Run tests with coverage
61+
if: matrix.php == env.PHP_PRIMARY
62+
run: vendor/bin/phpunit --colors=always --coverage-text
63+
env:
64+
XDEBUG_MODE: coverage
65+
66+
phpstan:
67+
name: PHPStan
68+
runs-on: ubuntu-latest
69+
needs: validate
70+
steps:
71+
- uses: actions/checkout@v6
72+
73+
- uses: shivammathur/setup-php@v2
74+
with:
75+
php-version: ${{ env.PHP_PRIMARY }}
76+
tools: composer:v2
77+
78+
- name: Cache Composer packages
79+
uses: actions/cache@v4
80+
with:
81+
path: vendor
82+
key: php-${{ env.PHP_PRIMARY }}-${{ hashFiles('composer.json') }}
83+
restore-keys: php-${{ env.PHP_PRIMARY }}-
84+
85+
- run: composer install --no-interaction --no-progress --prefer-dist
86+
87+
- run: vendor/bin/phpstan analyse --no-progress --error-format=github
88+
89+
benchmark:
90+
name: Benchmark
91+
runs-on: ubuntu-latest
92+
needs: tests
93+
steps:
94+
- uses: actions/checkout@v6
95+
with:
96+
fetch-depth: 0
97+
98+
- uses: shivammathur/setup-php@v2
99+
with:
100+
php-version: ${{ env.PHP_PRIMARY }}
101+
coverage: none
102+
extensions: curl, dom, mbstring, tokenizer, xml
103+
ini-values: opcache.enable_cli=1, opcache.jit=1255, opcache.jit_buffer_size=128M
104+
tools: composer:v2
105+
106+
- name: Cache Composer packages
107+
uses: actions/cache@v4
108+
with:
109+
path: vendor
110+
key: php-${{ env.PHP_PRIMARY }}-bench-${{ hashFiles('composer.json') }}
111+
restore-keys: php-${{ env.PHP_PRIMARY }}-bench-
112+
113+
- run: composer install --no-interaction --no-progress --prefer-dist
114+
115+
- name: Benchmark baseline (master)
116+
if: github.event_name == 'pull_request'
117+
run: |
118+
git checkout ${{ github.event.pull_request.base.sha }}
119+
if [ ! -f phpbench.json ]; then
120+
echo "No phpbench.json on base — skipping baseline"
121+
git checkout ${{ github.event.pull_request.head.sha }}
122+
exit 0
123+
fi
124+
composer install --no-interaction --no-progress --prefer-dist --quiet
125+
vendor/bin/phpbench run --tag=baseline --progress=plain
126+
git checkout ${{ github.event.pull_request.head.sha }}
127+
composer install --no-interaction --no-progress --prefer-dist --quiet
128+
129+
- name: Benchmark (compare with baseline)
130+
if: github.event_name == 'pull_request'
131+
run: |
132+
if vendor/bin/phpbench report --ref=baseline > /dev/null 2>&1; then
133+
vendor/bin/phpbench run --ref=baseline --report=default --progress=plain
134+
else
135+
echo "No baseline available — running without comparison"
136+
vendor/bin/phpbench run --report=default --progress=plain
137+
fi
138+
139+
- name: Benchmark
140+
if: github.event_name == 'push'
141+
run: vendor/bin/phpbench run --report=default --progress=plain

.gitignore

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
composer.phar
1+
/.phpunit.cache/
22
/vendor/
3-
4-
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
5-
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
6-
# composer.lock
3+
/composer.lock
4+
/composer.phar
5+
/coverage.xml

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Changelog
2+
3+
## [Unreleased]
4+
5+
## [1.0.0] - 2026-03-11
6+
7+
### Added
8+
9+
- `StringGenerator` — final stateless service with `generate(int $length, CharRange|string $alphabet)` method
10+
- `CharRange` enum — 7 predefined ASCII ranges: Numeric, Lowercase, Uppercase, Alpha, AlphaNumeric, Special, Any
11+
- Custom string alphabet support (any string with ≥ 2 distinct bytes)
12+
- Binary-safe generation (works with null bytes and full 0x00–0xFF range)
13+
- Static alphabet caching in `CharRange::alphabet()`
14+
- PHPStan level 9
15+
- PHPUnit test suite
16+
- PHPBench benchmark suite with baseline comparison in CI
17+
18+
[Unreleased]: https://github.com/taranovegor/string-generator/compare/v1.0.0...HEAD
19+
[1.0.0]: https://github.com/taranovegor/string-generator/releases/tag/v1.0.0

README.md

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,87 @@
1-
# string-generator
1+
# String Generator
2+
3+
[![CI](https://github.com/taranovegor/string-generator/actions/workflows/ci.yml/badge.svg)](https://github.com/taranovegor/string-generator/actions/workflows/ci.yml)
4+
[![PHPStan Level 9](https://img.shields.io/badge/PHPStan-level%209-brightgreen.svg)](https://phpstan.org/)
5+
[![PHP 8.2+](https://img.shields.io/badge/PHP-8.2%2B-8892BF.svg)](https://www.php.net/)
6+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7+
28
Fast, cryptographically-secure random string generator for PHP 8.2+.
9+
10+
Zero external dependencies. Uses `random_bytes()` with bit-packing and rejection sampling for efficient, unbiased output.
11+
12+
## Installation
13+
14+
```bash
15+
composer require taranovegor/string-generator
16+
```
17+
18+
## Quick start
19+
20+
```php
21+
use TaranovEgor\StringGenerator\CharRange;
22+
use TaranovEgor\StringGenerator\StringGenerator;
23+
24+
$generator = new StringGenerator();
25+
26+
// Default — alphabetic (a-zA-Z)
27+
$token = $generator->generate(32);
28+
29+
// Numeric only
30+
$pin = $generator->generate(6, CharRange::Numeric);
31+
32+
// Alphanumeric
33+
$code = $generator->generate(16, CharRange::AlphaNumeric);
34+
35+
// All printable ASCII
36+
$password = $generator->generate(20, CharRange::Any);
37+
```
38+
39+
### All predefined ranges
40+
41+
| Case | Characters | Size |
42+
|----------------|-----------------------------------------|------|
43+
| `Numeric` | `0-9` | 10 |
44+
| `Lowercase` | `a-z` | 26 |
45+
| `Uppercase` | `A-Z` | 26 |
46+
| `Alpha` | `a-z A-Z` | 52 |
47+
| `AlphaNumeric` | `a-z A-Z 0-9` | 62 |
48+
| `Special` | `! @ # $ % ^ & *` and other punctuation | 32 |
49+
| `Any` | All printable ASCII (33–126) | 94 |
50+
51+
### Custom alphabet
52+
53+
Pass a string as the second argument — every byte in it becomes a valid character:
54+
55+
```php
56+
$generator = new StringGenerator();
57+
58+
// Hex string
59+
echo $generator->generate(32, '0123456789abcdef');
60+
61+
// Base58
62+
echo $generator->generate(22, '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
63+
64+
// Binary-safe — works with any byte values, including null bytes
65+
echo $generator->generate(64, "\x00\x01\x02\x03");
66+
```
67+
68+
> **Note:** the alphabet must contain at least 2 distinct characters.
69+
70+
## Development
71+
72+
```bash
73+
composer install
74+
75+
# Run tests
76+
composer test
77+
78+
# Static analysis (PHPStan)
79+
composer analyse
80+
81+
# Run benchmarks
82+
compose bench
83+
```
84+
85+
## License
86+
87+
The scripts and documentation in this project are released under the [MIT License](LICENSE)

composer.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "taranovegor/string-generator",
3+
"description": "Fast, cryptographically-secure random string generator with configurable ASCII character ranges",
4+
"type": "library",
5+
"license": "MIT",
6+
"minimum-stability": "stable",
7+
"prefer-stable": true,
8+
"require": {
9+
"php": ">=8.2"
10+
},
11+
"require-dev": {
12+
"phpbench/phpbench": "^1.5",
13+
"phpstan/phpstan": "^2.1",
14+
"phpunit/phpunit": "^11.0"
15+
},
16+
"autoload": {
17+
"psr-4": {
18+
"TaranovEgor\\StringGenerator\\": "src/"
19+
}
20+
},
21+
"autoload-dev": {
22+
"psr-4": {
23+
"TaranovEgor\\StringGenerator\\Tests\\": "tests/"
24+
}
25+
},
26+
"scripts": {
27+
"test": "phpunit",
28+
"analyse": "phpstan analyse -c phpstan.neon",
29+
"bench": "phpbench run --report=default"
30+
},
31+
"config": {
32+
"sort-packages": true
33+
}
34+
}

phpbench.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"$schema": "./vendor/phpbench/phpbench/phpbench.schema.json",
3+
"runner.bootstrap": "vendor/autoload.php",
4+
"runner.path": "tests/Benchmark"
5+
}

phpstan.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
parameters:
2+
level: 9
3+
paths:
4+
- src
5+
treatPhpDocTypesAsCertain: false

phpunit.xml.dist

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.0/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
colors="true"
6+
failOnRisky="true"
7+
failOnWarning="true"
8+
cacheDirectory=".phpunit.cache"
9+
>
10+
<source>
11+
<include>
12+
<directory>src</directory>
13+
</include>
14+
</source>
15+
16+
<testsuites>
17+
<testsuite name="default">
18+
<directory>tests</directory>
19+
</testsuite>
20+
</testsuites>
21+
</phpunit>

0 commit comments

Comments
 (0)