evirma/code-pack
evirma/code-pack is a Laravel/PHP package that collects reusable helpers and scaffolding utilities in one pack, helping developers build consistent application components and speed up common development tasks.
1. Install the package
composer require evirma/code-pack
2. Publish the config and migrations
php artisan vendor:publish --provider="Evirma\CodePack\CodePackServiceProvider" --tag=config
php artisan vendor:publish --provider="Evirma\CodePack\CodePackServiceProvider" --tag=migrations
php artisan migrate
Check config/code-pack.php first — this is where length, alphabet, prefix, and storage options live. Adjust the alphabet before generating anything you plan to keep, since changing it later makes old and new batches non-uniform.
3. Generate your first batch
use Evirma\CodePack\Facades\CodePack;
$pack = CodePack::generate(500, [
'length' => 10,
'prefix' => 'PROMO-',
'alphabet' => 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789',
]);
$pack->codes; // collection of generated codes
Verify the exact facade name, method signatures, and return shape against the package README or
src/— the package is at 0 dependents, so the README is your most reliable reference, not blog posts.
First real use case: promotional voucher distribution. Generate a batch keyed to a campaign, hand the pack ID to your fulfillment/email job, and never regenerate the same batch.
Bind packs to domain objects. Treat a "pack" as an aggregate you attach metadata to (campaign ID, expiry, channel, batch owner). Store the returned pack ID on your Campaign/Promotion model rather than copying raw codes into your own table — you keep one source of truth and the package's uniqueness guarantees intact.
$pack = CodePack::generate($count, $format);
$campaign->update(['code_pack_id' => $pack->id]);
Generate in the background, redeem in the foreground. Generation is a bulk write; don't do it in a web request for large volumes. Wrap it in a queued job:
class GenerateVoucherPack implements ShouldQueue
{
public function handle(): void
{
$pack = CodePack::generate($this->count, $this->format);
$this->campaign->update(['code_pack_id' => $pack->id]);
VoucherPackReady::dispatch($this->campaign);
}
}
Redemption is your layer, not the package's. The package guarantees uniqueness at generation, not single-use consumption. Add your own redemptions table (code, user_id, redeemed_at) with a unique index on code and perform an atomic INSERT ... ON CONFLICT DO NOTHING (or insertOrIgnore) — that's your real "only one person can use this voucher" guarantee. Don't rely on SELECT then UPDATE.
Export pattern. Use $pack->codes and stream to CSV rather than loading into memory for huge batches:
$fh = fopen('php://output', 'w');
foreach ($pack->codes()->lazy(1000) as $code) {
fputcsv($fh, [$code]);
}
If the package only exposes an in-memory collection, chunk by iterating pack IDs and re-querying with chunkById.
Environment-specific alphabets. In tests, use a short alphabet/length so codes are readable in failures. In production, exclude ambiguous characters (0/O, 1/I/l) — the example alphabet above already does.
Facade vs. container. For unit tests, resolve the underlying service from the container (app(CodePack::class)) or swap the facade with CodePack::fake()/shouldReceive, so you're not hitting the DB during unrelated tests.
Uniqueness has a birthday-problem ceiling. With a 32-character alphabet at length 10 you get ~10^15 combinations, but if you're generating millions, collision retries can spike. Either increase length, or add a unique DB constraint on the codes table and let the package's collision handling retry — confirm in the source that it actually retries rather than throwing.
The unique index is non-negotiable. Even if the package validates uniqueness in PHP, concurrent generation workers can race. Ensure the migration includes a unique index on the code column; publish the migration and inspect it before running in production.
Config caching. After php artisan config:cache, any change to config/code-pack.php is ignored until you re-cache. If your length/prefix changes "don't take effect", this is almost always why — run php artisan config:clear.
Changing format mid-life breaks uniformity. Storing length/alphabet per pack (if supported) or freezing the config per environment is safer than tweaking the global config after launch. If the package doesn't persist per-pack format, keep a format column in your own campaign metadata.
Prefix handling. Prefixes are typically not part of the random entropy — a prefix plus fixed length means the random portion is length - strlen(prefix). Watch for off-by-one errors if you tune length after adding a long prefix.
Plain-PHP usage. The description claims non-Laravel support, but config publishing, migrations, and the service provider are Laravel-specific. In plain PHP you'll likely instantiate the generator directly and manage your own PDO/table — read src/ for the actual entry point rather than assuming the facade works.
Zero dependents = zero Stack Overflow answers. Budget time to read the source. Look at: the service provider (bindings), the generator class (collision loop, alphabet handling), and the migration (indexes). Those three files answer 90% of questions.
Extension points to look for: a configurable generator/code formatter contract (to swap in a custom checksum or Luhn digit), an event fired on pack creation (to hook notifications), and whether the storage repository is bound via the container so you can rebind it for a different DB or Redis store.
Debugging tip: if generation hangs or is slow, log the collision count per batch. A sudden jump usually means the alphabet/length combination is too small for the volume, not a bug in the package.
How can I help you explore Laravel packages today?