Skip to content

Phase 09: Key Generator

Implement a configurable key generator that produces keys in any format (e.g., XXXX-XXXX-XXXX-XXXX), with support for sequential generation, random generation, and batch creation.

  • src/Models/Generator.php
  • src/Services/GeneratorService.php
  • src/Repositories/GeneratorRepository.php

A Generator record defines the format template for a batch of keys:

$generator->pattern // e.g. "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
$generator->character_set // e.g. "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
$generator->separator // e.g. "-"
$generator->prefix // e.g. "ACME-"
$generator->max_activations // default seats for keys from this generator
$generator->valid_for_days // default expiry window
$generator->product_id // WooCommerce product this generator is linked to

Why exclude I, O, 1, 0? The default character set removes characters that are visually ambiguous when a customer reads a key from an email. This reduces support requests.

1. Load the Generator record.
2. Walk the pattern left-to-right:
- 'X' → pick random character from character_set (random_int for CSPRNG).
- Other characters → use as literal separator.
3. Prepend prefix if set.
4. Check uniqueness against the database (license_key_hash lookup).
5. Retry up to 10 times on collision before returning WP_Error.
$key = $generator_svc->generate($generator_id); // single key string
$keys = $generator_svc->generate_batch($generator_id, 50); // array of 50

GeneratorService::generate_batch() accepts an optional ?callable $create_fn parameter:

$svc->generate_batch($gen_id, 50, fn($key) => $license_svc->create($key, $attrs));

When provided, the callable is invoked to persist each key rather than GeneratorService calling LicenseService directly. This avoids the GeneratorService → LicenseService → GeneratorService circular dependency. The REST controller supplies the callable.

POST /wplm/v1/generators/{id}/generate

{ "count": 50 }

Response: array of created license objects (keys excluded by default, visible to write-scoped API keys only).