Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
208
src/Framework/Ksuid/KsuidGenerator.php
Normal file
208
src/Framework/Ksuid/KsuidGenerator.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Ksuid;
|
||||
|
||||
use App\Framework\Random\RandomGenerator;
|
||||
use DateTimeImmutable;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* KSUID Generator Service
|
||||
*
|
||||
* Generates K-Sortable Unique Identifiers with timestamp ordering.
|
||||
*/
|
||||
final readonly class KsuidGenerator
|
||||
{
|
||||
public function __construct(
|
||||
private RandomGenerator $randomGenerator
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new KSUID with current timestamp
|
||||
*/
|
||||
public function generate(): Ksuid
|
||||
{
|
||||
return $this->generateAt(time());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a KSUID at a specific Unix timestamp
|
||||
*/
|
||||
public function generateAt(int $timestamp): Ksuid
|
||||
{
|
||||
if ($timestamp < Ksuid::EPOCH) {
|
||||
throw new InvalidArgumentException('Timestamp cannot be before KSUID epoch (2014-05-13)');
|
||||
}
|
||||
|
||||
// Generate 16 random bytes for payload
|
||||
$payload = $this->randomGenerator->bytes(Ksuid::PAYLOAD_BYTES);
|
||||
|
||||
return Ksuid::fromTimestampAndPayload($timestamp, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a KSUID at a specific DateTime
|
||||
*/
|
||||
public function generateAtDateTime(DateTimeImmutable $dateTime): Ksuid
|
||||
{
|
||||
return $this->generateAt($dateTime->getTimestamp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a KSUID for the past (useful for testing)
|
||||
*/
|
||||
public function generateInPast(int $secondsAgo): Ksuid
|
||||
{
|
||||
$timestamp = time() - $secondsAgo;
|
||||
|
||||
return $this->generateAt($timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a batch of KSUIDs with the same timestamp
|
||||
*/
|
||||
public function generateBatch(int $count, ?int $timestamp = null): array
|
||||
{
|
||||
if ($count <= 0) {
|
||||
throw new InvalidArgumentException('Count must be positive');
|
||||
}
|
||||
|
||||
if ($count > 10000) {
|
||||
throw new InvalidArgumentException('Batch size cannot exceed 10000');
|
||||
}
|
||||
|
||||
$timestamp ??= time();
|
||||
$ksuids = [];
|
||||
$used = [];
|
||||
|
||||
while (count($ksuids) < $count) {
|
||||
$payload = $this->randomGenerator->bytes(Ksuid::PAYLOAD_BYTES);
|
||||
$payloadHex = bin2hex($payload);
|
||||
|
||||
// Ensure uniqueness within batch
|
||||
if (! isset($used[$payloadHex])) {
|
||||
$ksuids[] = Ksuid::fromTimestampAndPayload($timestamp, $payload);
|
||||
$used[$payloadHex] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $ksuids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a sequence of KSUIDs with incrementing timestamps
|
||||
*/
|
||||
public function generateSequence(int $count, int $intervalSeconds = 1): array
|
||||
{
|
||||
if ($count <= 0) {
|
||||
throw new InvalidArgumentException('Count must be positive');
|
||||
}
|
||||
|
||||
if ($count > 1000) {
|
||||
throw new InvalidArgumentException('Sequence size cannot exceed 1000');
|
||||
}
|
||||
|
||||
if ($intervalSeconds < 0) {
|
||||
throw new InvalidArgumentException('Interval must be non-negative');
|
||||
}
|
||||
|
||||
$ksuids = [];
|
||||
$timestamp = time();
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$ksuids[] = $this->generateAt($timestamp + ($i * $intervalSeconds));
|
||||
}
|
||||
|
||||
return $ksuids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a KSUID with specific prefix in payload (for testing/debugging)
|
||||
*/
|
||||
public function generateWithPrefix(string $prefix): Ksuid
|
||||
{
|
||||
$maxPrefixLength = Ksuid::PAYLOAD_BYTES - 1; // Reserve at least 1 byte for randomness
|
||||
|
||||
if (strlen($prefix) > $maxPrefixLength) {
|
||||
throw new InvalidArgumentException("Prefix cannot exceed {$maxPrefixLength} bytes");
|
||||
}
|
||||
|
||||
$remainingBytes = Ksuid::PAYLOAD_BYTES - strlen($prefix);
|
||||
$randomSuffix = $this->randomGenerator->bytes($remainingBytes);
|
||||
$payload = $prefix . $randomSuffix;
|
||||
|
||||
return Ksuid::fromTimestampAndPayload(time(), $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a KSUID string and validate it
|
||||
*/
|
||||
public function parse(string $ksuidString): Ksuid
|
||||
{
|
||||
return Ksuid::fromString($ksuidString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if a string is a valid KSUID
|
||||
*/
|
||||
public function isValid(string $value): bool
|
||||
{
|
||||
if (strlen($value) !== Ksuid::ENCODED_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Ksuid::fromString($value);
|
||||
|
||||
return true;
|
||||
} catch (InvalidArgumentException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum possible KSUID for a timestamp
|
||||
*/
|
||||
public function getMinForTimestamp(int $timestamp): Ksuid
|
||||
{
|
||||
$payload = str_repeat("\0", Ksuid::PAYLOAD_BYTES);
|
||||
|
||||
return Ksuid::fromTimestampAndPayload($timestamp, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum possible KSUID for a timestamp
|
||||
*/
|
||||
public function getMaxForTimestamp(int $timestamp): Ksuid
|
||||
{
|
||||
$payload = str_repeat("\xFF", Ksuid::PAYLOAD_BYTES);
|
||||
|
||||
return Ksuid::fromTimestampAndPayload($timestamp, $payload);
|
||||
}
|
||||
|
||||
/**@return array{min: \App\Framework\Ksuid\Ksuid, max: \App\Framework\Ksuid\Ksuid}
|
||||
* Generate KSUIDs for a time range (useful for queries)
|
||||
*/
|
||||
public function generateTimeRange(int $startTimestamp, int $endTimestamp): array
|
||||
{
|
||||
if ($startTimestamp >= $endTimestamp) {
|
||||
throw new InvalidArgumentException('Start timestamp must be before end timestamp');
|
||||
}
|
||||
|
||||
return [
|
||||
'min' => $this->getMinForTimestamp($startTimestamp),
|
||||
'max' => $this->getMaxForTimestamp($endTimestamp),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create factory method
|
||||
*/
|
||||
public static function create(RandomGenerator $randomGenerator): self
|
||||
{
|
||||
return new self($randomGenerator);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user