rasuvaeff/property-testing-names

Localised person-name generators (en, ru) for rasuvaeff/property-testing-core: first, last, patronymic and gender-consistent full names

Maintainers

Package info

github.com/rasuvaeff/property-testing-names

pkg:composer/rasuvaeff/property-testing-names

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-08-13 18:34 UTC

This package is auto-updated.

Last update: 2026-08-15 13:56:48 UTC


README

Latest Stable Version Total Downloads Build Static analysis Psalm level PHP License

Русская версия

Person-name generators for the property-testing engine: given names, surnames and patronymics for en and ru, with grammatical gender kept consistent across the parts of one name. Names shrink toward the shortest entries of their dataset, so a counterexample minimises into a plain name instead of turning into random text.

Using an AI coding assistant? llms.txt contains a compact API reference you can share with the model.

Why a separate package

The core Gen facade holds format-derived primitives — Gen::email(), Gen::uuid(), Gen::ipv4() — things a formula produces. Name lists are versioned data with their own update policy, so they live here, behind their own Names:: facade. There is no Gen::name(): a core method that silently depends on an optional data package would pass composer-require-checker in your project and fail at runtime.

Package Use it when
rasuvaeff/property-testing-core The engine itself: arbitraries, shrinking, corpus
rasuvaeff/property-testing-testo You test with Testo — the #[Property] attribute
rasuvaeff/property-testing-phpunit You test with PHPUnit — the forAll()->check() trait
rasuvaeff/property-testing-names (this package) Your inputs are people: forms, profiles, auth, validators, reports

Requirements

  • PHP 8.3 – 8.5
  • ext-mbstring
  • rasuvaeff/property-testing-core ^0.1 || ^0.2

Installation

composer require --dev rasuvaeff/property-testing-names

Usage

Every factory returns an ArbitraryInterface — a recipe for values, not a value. The comments below show what each one generates:

use Rasuvaeff\PropertyTesting\Names\Names;
use Rasuvaeff\PropertyTesting\Names\Gender;

$firstNames = Names::first();                     // 'Ian', 'Emma', …
$surnames = Names::last(locale: 'ru');            // 'Попов', 'Иванова', …
$femaleNames = Names::first('ru', Gender::Female); // 'Мария', 'Ольга', …
$patronymics = Names::middle('ru');               // 'Ивановна', 'Петрович', …
$displayNames = Names::full('ru', middle: true);  // 'Иван Иванович Иванов'
$people = Names::person('ru', middle: true);      // PersonName objects

The runner draws from them; nothing is generated until it does. To see values while debugging, sample explicitly:

Gen::sample($displayNames, 3, 6);   // fixed seed → the same three names every time

Inside a property test the factories go into the generators method, exactly like the core ones:

use Rasuvaeff\PropertyTesting\ArbitraryInterface;
use Rasuvaeff\PropertyTesting\Names\Names;
use Rasuvaeff\PropertyTesting\Property;
use Testo\Assert;

#[Property(runs: 300)]
public function displayNameFitsTheColumn(string $first, string $last): void
{
    Assert::true(mb_strlen((new Profile($first, $last))->displayName()) <= 255);
}

/** @return array<string, ArbitraryInterface> */
public static function displayNameFitsTheColumnGenerators(): array
{
    return [
        'first' => Names::first(locale: 'ru'),
        'last' => Names::last(locale: 'ru'),
    ];
}

API

Factory Returns Notes
Names::first(string $locale = 'en', ?Gender $gender = null) ArbitraryInterface<non-empty-string> Without a gender the male and female lists are merged
Names::last(string $locale = 'en', ?Gender $gender = null) ArbitraryInterface<non-empty-string> Inflected per gender where the locale requires it
Names::middle(string $locale, ?Gender $gender = null) ArbitraryInterface<non-empty-string> Patronymics; the locale is required because the dataset is not universal
Names::full(string $locale = 'en', ?Gender $gender = null, bool $middle = false) ArbitraryInterface<non-empty-string> First [Middle] Last, rendered from person()
Names::person(string $locale = 'en', ?Gender $gender = null, bool $middle = false) ArbitraryInterface<PersonName> The parts, kept together

PersonName is a final readonly class with $first, $middle (nullable), $last, $gender and three display forms:

Method en ru
full() John Smith Иван Иванович Иванов
initialLast() J. Smith И. Иванов
lastInitials() Smith J. Иванов И. И.

Any other form is one Gen::map() away:

Gen::map(Names::person(), static fn (PersonName $p): string => $p->last . ', ' . $p->first);

Locales

Locale Given names Surnames Patronymics
en 50 male + 50 female 100, shared by both genders
ru 50 male + 50 female 50 + 50, index-aligned pairs 40 + 40, index-aligned pairs

An unregistered locale raises InvalidArgumentException when the arbitrary is built, not when it first generates a value; the same is true for asking en for middle names. Locale tags are matched literally: 'EN', 'en-US' and 'en ' are all unknown.

Gender consistency

Names::first() and Names::last() are independent draws — combining them by hand can produce Мария Иванов, which no Russian form renders. When the parts must agree, draw them together:

$person = Names::person('ru', middle: true);   // Мария Ивановна Иванова

Gender has two cases, Male and Female, declared in that order because shrinking walks toward the first case.

Security

The lists are synthetic test data: they are not a register of real people and make no claim of cultural completeness. Generated values are printable UTF-8 without control characters, so they are safe to embed in test reports and failure messages — but they are still generated input, and code under test should validate them like any other user data.

Dataset changes alter the values a given seed produces, so they ship as minor releases and are listed in CHANGELOG.md.

Examples

Runnable scripts live in examples/.

Development

make build          # validate + normalize + require-checker + cs + psalm + test
make cs-fix
make psalm
make test
make test-coverage
make mutation
make release-check

No PHP on the host is required — every target runs in the composer:2 Docker image.

License

BSD-3-Clause. See LICENSE.md.