255) { throw new InvalidArgumentException('Default size must be between 1 and 255'); } if (empty($defaultAlphabet)) { throw new InvalidArgumentException('Default alphabet cannot be empty'); } $this->defaultSize = $defaultSize; $this->defaultAlphabet = $defaultAlphabet; } /** * Generate a NanoId with default settings */ public function generate(): NanoId { return $this->generateWithCustom($this->defaultSize, $this->defaultAlphabet); } /** * Generate a NanoId with custom size */ public function generateWithSize(int $size): NanoId { return $this->generateWithCustom($size, $this->defaultAlphabet); } /** * Generate a NanoId with custom alphabet */ public function generateWithAlphabet(string $alphabet): NanoId { return $this->generateWithCustom($this->defaultSize, $alphabet); } /** * Generate a NanoId with custom size and alphabet */ public function generateCustom(int $size, string $alphabet): NanoId { return $this->generateWithCustom($size, $alphabet); } /** * Core NanoId generation logic */ private function generateWithCustom(int $size, string $alphabet): NanoId { if ($size <= 0 || $size > 255) { throw new InvalidArgumentException('Size must be between 1 and 255'); } if (empty($alphabet)) { throw new InvalidArgumentException('Alphabet cannot be empty'); } $alphabetLength = strlen($alphabet); if ($alphabetLength > 255) { throw new InvalidArgumentException('Alphabet cannot exceed 255 characters'); } $id = ''; $mask = (2 << (int)(log($alphabetLength - 1) / M_LN2)) - 1; $step = (int)ceil(1.6 * $mask * $size / $alphabetLength); while (strlen($id) < $size) { $bytes = $this->randomGenerator->bytes($step); for ($i = 0; $i < $step && strlen($id) < $size; $i++) { $byte = ord($bytes[$i]) & $mask; if ($byte < $alphabetLength) { $id .= $alphabet[$byte]; } } } return NanoId::fromString($id); } /** * Generate a safe NanoId (no lookalikes) */ public function generateSafe(int $size = NanoId::DEFAULT_SIZE): NanoId { return $this->generateWithCustom($size, NanoId::SAFE_ALPHABET); } /** * Generate a numeric NanoId */ public function generateNumeric(int $size = 12): NanoId { return $this->generateWithCustom($size, NanoId::NUMBERS); } /** * Generate a lowercase alphanumeric NanoId */ public function generateLowercase(int $size = NanoId::DEFAULT_SIZE): NanoId { return $this->generateWithCustom($size, NanoId::LOWERCASE_ALPHANUMERIC); } /** * Generate an uppercase alphanumeric NanoId */ public function generateUppercase(int $size = NanoId::DEFAULT_SIZE): NanoId { return $this->generateWithCustom($size, NanoId::UPPERCASE_ALPHANUMERIC); } /** * Generate a NanoId for a specific entity type with prefix */ public function generateForEntity(string $entityType, int $size = 16): NanoId { $prefix = match($entityType) { 'user' => 'usr_', 'order' => 'ord_', 'product' => 'prd_', 'session' => 'ses_', 'token' => 'tok_', 'transaction' => 'txn_', 'invoice' => 'inv_', 'customer' => 'cus_', 'payment' => 'pay_', 'subscription' => 'sub_', default => strtolower(substr($entityType, 0, 3)) . '_' }; $id = $this->generateWithCustom($size, NanoId::DEFAULT_ALPHABET); return $id->withPrefix($prefix); } /** * Generate a time-prefixed NanoId (sortable by creation time) */ public function generateTimePrefixed(int $idSize = 12): NanoId { // Use base36 timestamp for compactness $timestamp = base_convert((string)time(), 10, 36); $id = $this->generateWithCustom($idSize, NanoId::LOWERCASE_ALPHANUMERIC); return $id->withPrefix($timestamp . '_'); } /** * Generate a batch of unique NanoIds */ public function generateBatch(int $count, int $size = NanoId::DEFAULT_SIZE): array { if ($count <= 0) { throw new InvalidArgumentException('Count must be positive'); } if ($count > 10000) { throw new InvalidArgumentException('Batch size cannot exceed 10000'); } $ids = []; $generated = []; while (count($ids) < $count) { $id = $this->generateWithCustom($size, $this->defaultAlphabet); $value = $id->toString(); // Ensure uniqueness within batch if (! isset($generated[$value])) { $ids[] = $id; $generated[$value] = true; } } return $ids; } /** * Validate if a string is a valid NanoId for the current configuration */ public function isValid(string $value): bool { if (empty($value) || strlen($value) > 255) { return false; } try { $nanoId = NanoId::fromString($value); return $nanoId->matchesAlphabet($this->defaultAlphabet); } catch (InvalidArgumentException) { return false; } } /** * Create a custom generator with specific settings */ public static function create(RandomGenerator $randomGenerator, int $size = NanoId::DEFAULT_SIZE, string $alphabet = NanoId::DEFAULT_ALPHABET): self { return new self($randomGenerator, $size, $alphabet); } /** * Create a generator for safe IDs (no lookalikes) */ public static function createSafe(RandomGenerator $randomGenerator, int $size = NanoId::DEFAULT_SIZE): self { return new self($randomGenerator, $size, NanoId::SAFE_ALPHABET); } /** * Create a generator for numeric IDs */ public static function createNumeric(RandomGenerator $randomGenerator, int $size = 12): self { return new self($randomGenerator, $size, NanoId::NUMBERS); } }