pikaid / pikaid-php
Small · Sortable · Secure IDs
Requires
- php: >=7.3
Requires (Dev)
- php: >=8.3
- hidehalo/nanoid-php: ^2.0
- phpunit/phpunit: ^12.1.5
- ramsey/uuid: ^4.7.6
- robinvdvleuten/ulid: ^5.0.0
Suggests
- ext-bcmath: Faster than pure-PHP fallback if GMP is unavailable
- ext-gmp: Fastest Base36 conversions (recommended for best performance)
README
Pikaid (PHP)
Small · Sortable · Secure ID generator
This is the official PHP implementation. Fully compliant with v1.0.1.
Pikaid is a 26-character, lowercase Base36 identifier, composed of:
- 7-character timestamp (seconds since epoch)
- 19-character cryptographically secure randomness
It’s a modern, compact alternative to UUID and ULID:
- Lexicographically sortable
- Collision-resistant
- Compact binary form (
BINARY(17)
)
📚 Specifications & Benchmarks
See the full technical specs and benchmarks at pikaid/pikaid-specs
⚙️ Requirements
- PHP 7.4+ (PHP 8 recommended)
ext-gmp
(recommended for best performance) orext-bcmath
(optional)- If neither extension is installed, a pure-PHP fallback is used.
📦 Installation
Install via Composer:
composer require pikaid/pikaid-php
Include Composer's autoloader in your project:
require 'vendor/autoload.php';
🚀 Basic Usage
<?php use Pikaid\Pikaid; // Generate a new Pikaid string $id = Pikaid::generate(); echo "ID: $id\n"; // e.g. 0swct4q01ch83h91onl6l47vb6 // Validate if (Pikaid::isValid($id)) { echo "Valid ID!\n"; } // Parse components $data = Pikaid::parse($id); echo "Timestamp: " . $data['timestamp']->format('Y-m-d H:i:s') . " UTC\n"; echo "Randomness (hex): {$data['randomness']}\n";
🧩 API Reference
Pikaid::generate(): string
Generate a new 26-character string ID.
- Layout:
[7 chars timestamp][19 chars randomness]
- Sortable by second.
$id = Pikaid::generate();
Pikaid::generateBinary(): string
Generate the binary form directly (BINARY(17)
).
- Layout:
[5 bytes timestamp (uint40, big-endian)][12 bytes entropy]
.
$bin = Pikaid::generateBinary();
Pikaid::toBinary(string $id): string
Convert a string ID to its binary (17 bytes) representation.
Throws InvalidArgumentException
if the input is not a valid 26-character Pikaid or if the timestamp is out of range.
$bin = Pikaid::toBinary($id);
Pikaid::fromBinary(string $bin): string
Convert binary (17 bytes) back into a 26-char string.
Throws InvalidArgumentException
if the binary length isn’t exactly 17 bytes.
$id = Pikaid::fromBinary($bin);
Pikaid::isValid(string $id): bool
Check if the given string is a valid Pikaid.
- Must be 26 chars long
- Must match regex:
/^[0-9a-z]{26}$/
if (Pikaid::isValid($id)) { echo "Valid format!"; }
Pikaid::parse(string $id): array
Parse a string ID into its components:
timestamp
→DateTimeImmutable
(UTC)randomness
→ lowercase hex string (24 chars = 12 bytes)
Throws InvalidArgumentException
on invalid input.
$info = Pikaid::parse($id); /* [ 'timestamp' => DateTimeImmutable(...), 'randomness' => 'a1b2c3d4e5f6a7b8c9d0e1f2' ] */
Pikaid::fromDateTime(DateTimeInterface $t): string
Generate a string ID for a specific timestamp (seconds precision).
$id = Pikaid::fromDateTime(new DateTimeImmutable('@1234567890'));
🔄 Order Guarantee
- String and binary representations sort lexicographically by second:
$id1 = Pikaid::generate(); sleep(1); $id2 = Pikaid::generate(); assert($id1 < $id2); // always true
🛢 Storage Recommendations
Pikaid is designed to be compact and index-friendly, with a predictable layout:
Representation | Size | Sortable | Recommended for |
---|---|---|---|
BINARY(17) | 17B | Yes | High-performance storage and indexing |
CHAR(26) | 26B | Yes | Readability in SQL, debugging, or logs |
🔹 Option 1: Store as BINARY(17)
(Recommended)
Binary form stores exactly 17 bytes:
[5 bytes timestamp (uint40, big-endian)][12 bytes entropy]
Table Definition
CREATE TABLE pika_events ( id BINARY(17) NOT NULL, -- Primary key payload JSON NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- Optional: Extract timestamp for range queries and indexing ts_seconds BIGINT UNSIGNED AS ( ORD(SUBSTR(id, 1, 1)) * 4294967296 + CONV(HEX(SUBSTR(id, 2, 4)), 16, 10) ) STORED, PRIMARY KEY (id), KEY idx_ts_seconds (ts_seconds), KEY idx_created_at (created_at) ) ENGINE=InnoDB;
Insert Example (PHP)
use Pikaid\Pikaid; // Generate binary ID and store it $binId = Pikaid::generateBinary(); $stmt = $pdo->prepare('INSERT INTO pika_events (id, payload) VALUES (?, ?)'); $stmt->execute([$binId, json_encode(['event' => 'signup'])]);
Select Example (PHP)
$stmt = $pdo->query('SELECT id, ts_seconds FROM pika_events ORDER BY id ASC'); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) { $stringId = Pikaid::fromBinary($row['id']); // Convert back to string echo $stringId . ' @ ' . gmdate('c', (int)$row['ts_seconds']) . PHP_EOL; }
Benefits
- Small index size = better performance
- Binary comparison matches chronological order
- Perfect for
PRIMARY KEY
or clustered indexes
🔹 Option 2: Store as CHAR(26)
(String)
If you need IDs to remain human-readable directly in SQL queries, store the string form.
Table Definition
CREATE TABLE pika_events_str ( id CHAR(26) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, -- Primary key payload JSON NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- Optional: Extract timestamp for range queries and indexing ts_seconds BIGINT UNSIGNED AS (CONV(SUBSTR(id, 1, 7), 36, 10)) STORED, PRIMARY KEY (id), KEY idx_ts_seconds (ts_seconds), KEY idx_created_at (created_at) ) ENGINE=InnoDB;
Insert Example (PHP)
use Pikaid\Pikaid; $id = Pikaid::generate(); $stmt = $pdo->prepare('INSERT INTO pika_events_str (id, payload) VALUES (?, ?)'); $stmt->execute([$id, json_encode(['event' => 'login'])]);
Select Example (PHP)
$stmt = $pdo->query('SELECT id, ts_seconds FROM pika_events_str ORDER BY id ASC'); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) { echo $row['id'] . ' @ ' . gmdate('c', (int)$row['ts_seconds']) . PHP_EOL; }
Notes
- Slightly larger storage and index compared to
BINARY(17)
- Ideal for debugging or manual inspection of IDs
🔄 Order Guarantee
Both BINARY(17)
and CHAR(26)
maintain natural chronological order:
$id1 = Pikaid::generate(); sleep(1); $id2 = Pikaid::generate(); assert($id1 < $id2); // String order is chronological assert(strcmp(Pikaid::toBinary($id1), Pikaid::toBinary($id2)) < 0); // Binary order too
📜 License
Pikaid is released under the MIT License.