claudiodekker/word-generator
A lightweight PHP package for generating random words on demand. Handy for seeding databases, building test fixtures, and creating placeholder content in Laravel apps, with a simple API that drops easily into any project.
Install via Composer and the package auto-discovers its Laravel service provider:
composer require claudiodekker/word-generator
If the package ships a config file (custom word lists, default word count, formatting), publish it once:
php artisan vendor:publish --provider="Claudiodekker\WordGenerator\WordGeneratorServiceProvider"
Where to look first
config/word-generator.php (if present) — this is where the bundled list and any custom lists are wired.vendor/claudiodekker/word-generator/ — read it once so you know the vocabulary you're actually getting (length, casing, profanity, plural forms).First use case: seed a factory with human-readable placeholder text instead of lorem ipsum, so seeded records look like real data in staging.
use Claudiodekker\WordGenerator\WordGenerator;
WordGenerator::generate(); // one word
WordGenerator::generate(3); // a short phrase
The framework-agnostic core means the same call works in a plain PHP script or an artisan command — no Laravel bootstrap required, which is handy for throwaway scripts.
The highest-leverage integration is inside model factories. It gives you deterministic-looking, readable fixtures without dragging Faker's locale providers around:
class ProjectFactory extends Factory
{
public function definition(): array
{
$name = WordGenerator::generate(2);
return [
'name' => ucwords($name),
'slug' => Str::slug($name).'-'.Str::lower(Str::random(4)),
'description' => WordGenerator::generate(8),
];
}
}
Compute the phrase once and derive name/slug from the same value — otherwise the slug won't match the title and reviewers will notice immediately.
For staging seeders, wrap the generator in a small helper so you can swap the source later:
class DemoContentSeeder extends Seeder
{
public function run(): void
{
Project::factory()
->count(50)
->sequence(fn ($seq) => [
'name' => ucwords(WordGenerator::generate(2)).' '.$seq->index,
])
->create();
}
}
The sequence trick (appending the index) solves the collision problem you'll hit with short phrases.
Rather than calling the static API across the codebase, bind a thin wrapper so tests can fake it and so you control the word list in one place:
// app/Support/WordSmith.php
final class WordSmith
{
public function phrase(int $words = 2): string
{
return WordGenerator::generate($words);
}
public function identifier(string $prefix = ''): string
{
return $prefix.Str::studly($this->phrase(2));
}
}
Register it as a singleton (or scoped) in a service provider. In tests, $this->instance(WordSmith::class, new FakeWordSmith()) gives you fixed strings and kills flaky assertions.
Where you'd otherwise use Str::random(8) and get xK9fLq2a, this package gives bright-harbor-4821. Pattern:
$code = Str::slug(WordGenerator::generate(2)).'-'.random_int(1000, 9999);
Do this for invite codes, sandbox tenant names, download filenames, and test account labels — anywhere a support engineer will have to read the value aloud.
For meta_description, comment bodies, or changelog stubs, generate a longer phrase and sentence-case it:
$sentence = Str::ucfirst(WordGenerator::generate(10)).'.';
Keep it inside factories/seeders only. Never let generated words reach a real user-facing page.
If your domain has a vocabulary (product categories, city names, feature names), supply a custom list rather than post-processing the bundled one. Pass the list explicitly or point the config at a file — prefer base_path()/resource_path() so the path survives deploys, not a path relative to the vendor directory.
// config/word-generator.php
'lists' => [
'default' => resource_path('wordlists/common.txt'),
'products' => resource_path('wordlists/products.txt'),
],
Then keep one named list per domain concept rather than one giant merged file — it makes generated data semantically coherent.
Not random enough for secrets. Do not use this for passwords, API tokens, reset codes, or anything security-sensitive. It draws from a small finite vocabulary with a non-cryptographic source; use Str::random() / random_bytes() for those.
Collisions are guaranteed at scale. With a small word list, generate(2) produces duplicates within a few hundred rows. Always append a numeric suffix, a Str::random(4), or combine with the primary key — and add a unique() constraint in the schema so the failure is loud rather than silent.
Uniqueness in factories. ->unique() on the Faker-style chained call won't help you here; wrap in a loop with a bounded retry, or use sequence()/the model's ID as the disambiguator.
Casing and formatting. Generated words are typically lowercase. If a column expects title case or a slug, apply ucwords() / Str::slug() deliberately. If the package exposes output-format options, prefer them over string munging — but verify the behaviour, because format flags are the most likely place for the API to differ from what you assume.
Config caching. After adding custom word list paths, run php artisan config:cache fresh in production. A stale cached config pointing at a path that doesn't exist on the deploy target is the classic silent failure — the fallback to the bundled list means your generated "product names" quietly become generic words.
Performance in loops. Loading a word list on every call inside a 10,000-row seeder is wasteful. Resolve the generator once (container singleton or a local variable) and reuse it. If the API is static-only, at minimum avoid re-reading the list inside nested loops.
Determinism for tests. Random output makes assertions brittle. Two options: (a) inject a wrapper and fake it in tests, or (b) if the generator supports a seeded RNG, set a fixed seed in setUp() and assert on the exact string. Option (a) is far more maintainable.
Don't assert on generated content. Tests should assert on shape — assertMatchesRegularExpression('/^[a-z]+-[a-z]+$/', $slug) — not on the specific words.
Extension point to watch. The framework-agnostic core is the real API; the Laravel facade is a convenience. If you need behaviour the facade doesn't expose (custom RNG, weighted lists, locale switching), drop down to the underlying class and wrap it. Check the package's release notes before upgrading — with a low-traffic package, minor versions occasionally carry signature changes the changelog under-describes.
Profanity and locale. The bundled list is English and unaudited for brand-safety. For any client demo, skim the list or supply your own — a randomly generated rude word in a screenshot is not a fun afternoon.
Scope discipline. Treat this as a fixture and placeholder tool. If you find it in a request path serving real users, that's a code smell — replace it with real data or an explicit, curated list.
How can I help you explore Laravel packages today?