nicobleiler/php-passphrase
Generate secure, memorable passphrases in PHP with nicobleiler/php-passphrase. Configure word counts, separators, and wordlists to create Diceware-style passwords with strong entropy for user-friendly authentication.
Install the package and publish its config:
composer require nicobleiler/php-passphrase
php artisan vendor:publish --provider="NicoBleiler\PhpPassphrase\PassphraseServiceProvider"
The first place to look is the published config/passphrase.php (or config/php-passphrase.php, depending on the publish tag) — it holds every knob the generator reads: num_words, word_separator, capitalize, include_number, the word list, and excluded words. Second, open the Passphrase facade class; as of v2.2.0-beta.1 it carries a full docblock, so your IDE will autocomplete the public API without you reading the service provider.
The quickest first use case is generating a human-readable secret for an invitation or a seeded test user:
use NicoBleiler\PhpPassphrase\Facades\Passphrase;
$plain = Passphrase::generate(); // e.g. "correct-horse-battery-staple"
Run it once in php artisan tinker before wiring it into anything — it tells you immediately whether your published config's word list and separator settings are what you expect.
Note that v2.2.0 is a beta. Pin it explicitly ("nicobleiler/php-passphrase": "2.2.0-beta.1") if you depend on the new config validation, or stay on ^2.1 in production until it's promoted to stable.
Config-driven profiles. Keep one config file and one call site. If you need different strengths for different contexts (e.g. short passphrases for low-value throwaway accounts, long ones for account recovery), don't duplicate the config — resolve the generator with overrides at the call site or wrap it in your own small service:
class PassphraseGenerator
{
public function recovery(): string
{
return Passphrase::generate(); // long, from config
}
public function invite(): string
{
config(['passphrase.num_words' => 4]);
return Passphrase::generate();
}
}
Mutating config at runtime is fine in tests and seeders; in request-handling code prefer constructor injection of the underlying generator over global config() mutation so concurrent resolutions within one request stay predictable.
Seeders and factories. This is the highest-value daily use. Generating passphrases instead of Str::random() gives testers credentials they can actually retype from a screen or log line:
User::factory()->create([
'password' => Hash::make(Passphrase::generate()),
]);
Because they're memorable, you can log the generated value in local/dev seeding and reuse it across a manual test session.
Filtering the word list. excluded_words is where you earn your keep in a customer-facing product: strip profanity, words that read badly alongside your brand, and homoglyph-adjacent words that cause support tickets. Keep the exclusions in config (reviewable, diffable) rather than filtering the output string after generation — post-hoc filtering can silently produce a shorter or malformed passphrase.
Offering a choice. For signup flows, generate 3–5 candidates and let the user pick one. Passphrase strength comes from the word list, not from you hiding it; showing candidates improves acceptance and reduces "I'll just reuse my old password" drop-off.
Catch the config exception at the boundary. With the new ConfigException, resolve the generator once at boot or in a health-check command so a bad .env value fails loudly at deploy time rather than mid-request:
try {
app(Passphrase::class); // or your wrapper
} catch (ConfigException $e) {
// log and fail the deploy / health check
}
env() returns strings; the validator is now strict. The headline change in v2.2.0-beta.1 is type validation in PassphraseServiceProvider for num_words (int), word_separator (string), capitalize (bool), and include_number (bool), backed by a new ConfigException. If you drive these from .env — PASSPHRASE_NUM_WORDS=5, PASSPHRASE_CAPITALIZE=true — Laravel hands you "5" and "true", not 5 and true. Cast in the config file ((int) env(...), (bool) env(...)) or you'll now get a hard exception where earlier versions quietly coerced.
Beta upgrade path. The validation is stricter than v2.1.0, so a config that "worked" before may throw on upgrade. Read your published config, cast the values, then bump. Do this in a branch — the exception surfaces at resolution time, which in a long-running queue worker means a failed job rather than a failed boot.
Excluded words cost entropy. Every word you exclude shrinks the effective word list. The loss is negligible for a handful of exclusions and material if you filter aggressively (hundreds of words). If you're generating credentials for real accounts, compute your bits of entropy from the post-exclusion list size and num_words, and size num_words accordingly — the separator and capitalization add readability, not entropy.
Word list loading is I/O. loadBundledEff() reads the bundled EFF list on resolution. That's fine per request, but don't resolve the generator inside a tight loop in a seeder — resolve once and reuse, or you'll re-read the file hundreds of times.
Type annotations landed, but verify at runtime. v2.2.0-beta.1 adds docblocks and PHPStan annotations (and raises the package's own PHPStan level to 10). That improves your static analysis, but it doesn't guarantee your array of excluded words is all strings — the provider now validates this too, and there are dedicated tests for non-string elements. If you build excluded_words dynamically (e.g. from a DB query), cast to array<string> before handing it to config.
Unused rector rule removed, PHPStan tightened. Purely internal housekeeping commits (NewlineBetweenClassLikeStmtsRector, script ordering) — nothing to act on, but they signal the package is actively linted, so a failing PHPStan run in your code after upgrading is more likely to be a genuine annotation mismatch than package noise.
Extension point. If you need a custom word list (domain-specific vocabulary, another language), point the word list config at your own file rather than forking the package — the provider validates it as an array of strings, so a JSON-decoded list with stray nulls will throw.
How can I help you explore Laravel packages today?