Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/security-standards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ jobs:
actions: read
contents: read
with:
php_versions: '["8.3","8.4","8.5"]'
php_versions: '["8.2","8.3","8.4","8.5"]'
dependency_versions: '["prefer-lowest","prefer-stable"]'
php_extensions: "bcmath"
coverage: "xdebug"
composer_flags: ""
phpstan_memory_limit: "1G"
psalm_threads: "1"
run_analysis: true
run_svg_report: true
artifact_retention_days: 61
2 changes: 1 addition & 1 deletion captainhook.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"options": []
},
{
"action": "composer ic:tests",
"action": "composer ic:ci",
"options": []
}
]
Expand Down
34 changes: 34 additions & 0 deletions src/Configuration/ResolvesCustomEpoch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Infocyph\UID\Configuration;

use DateTimeInterface;

trait ResolvesCustomEpoch
{
public function resolveCustomEpochMs(): ?int
{
return self::resolveEpochValue($this->customEpoch);
}

private static function resolveEpochValue(DateTimeInterface|int|string|null $customEpoch): ?int
{
if ($customEpoch === null) {
return null;
}

if ($customEpoch instanceof DateTimeInterface) {
return (int) $customEpoch->format('Uv');
}

if (is_int($customEpoch)) {
return $customEpoch;
}

$epoch = strtotime($customEpoch);

return $epoch === false ? null : $epoch * 1000;
}
}
21 changes: 2 additions & 19 deletions src/Configuration/SnowflakeConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

final readonly class SnowflakeConfig
{
use ResolvesCustomEpoch;

private ?Closure $nodeResolver;

/**
Expand All @@ -30,25 +32,6 @@ public function __construct(
$this->nodeResolver = $nodeResolver ? $nodeResolver(...) : null;
}

public function resolveCustomEpochMs(): ?int
{
if ($this->customEpoch === null) {
return null;
}

if ($this->customEpoch instanceof DateTimeInterface) {
return (int) $this->customEpoch->format('Uv');
}

if (is_int($this->customEpoch)) {
return $this->customEpoch;
}

$epoch = strtotime($this->customEpoch);

return $epoch === false ? null : $epoch * 1000;
}

/**
* @return array{0:int,1:int}
*/
Expand Down
21 changes: 2 additions & 19 deletions src/Configuration/SonyflakeConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

final readonly class SonyflakeConfig
{
use ResolvesCustomEpoch;

private ?Closure $machineIdResolver;

/**
Expand All @@ -28,25 +30,6 @@ public function __construct(
$this->machineIdResolver = $machineIdResolver ? $machineIdResolver(...) : null;
}

public function resolveCustomEpochMs(): ?int
{
if ($this->customEpoch === null) {
return null;
}

if ($this->customEpoch instanceof DateTimeInterface) {
return (int) $this->customEpoch->format('Uv');
}

if (is_int($this->customEpoch)) {
return $this->customEpoch;
}

$epoch = strtotime($this->customEpoch);

return $epoch === false ? null : $epoch * 1000;
}

public function resolveMachineId(): int
{
if ($this->machineIdResolver === null) {
Expand Down
18 changes: 2 additions & 16 deletions src/IdComparator.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Infocyph\UID;

use Infocyph\UID\Contracts\IdValueInterface;
use Infocyph\UID\Support\UnsignedDecimal;

final class IdComparator
{
Expand All @@ -17,7 +18,7 @@ public static function compare(IdValueInterface|string $left, IdValueInterface|s
$rightString = $right instanceof IdValueInterface ? $right->toString() : $right;

if (preg_match('/^\d+$/', $leftString) && preg_match('/^\d+$/', $rightString)) {
return self::compareUnsignedDecimals($leftString, $rightString);
return UnsignedDecimal::compare($leftString, $rightString);
}

return strcmp($leftString, $rightString);
Expand All @@ -35,19 +36,4 @@ public static function sort(array $ids): array

return $ids;
}

private static function compareUnsignedDecimals(string $left, string $right): int
{
$left = ltrim($left, '0');
$right = ltrim($right, '0');
$left = $left === '' ? '0' : $left;
$right = $right === '' ? '0' : $right;

$lengthComparison = strlen($left) <=> strlen($right);
if ($lengthComparison !== 0) {
return $lengthComparison;
}

return strcmp($left, $right);
}
}
7 changes: 2 additions & 5 deletions src/KSUID.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Exception;
use Infocyph\UID\Contracts\IdAlgorithmInterface;
use Infocyph\UID\Support\BaseEncoder;
use Infocyph\UID\Support\BinaryUnpack;

final class KSUID implements IdAlgorithmInterface
{
Expand Down Expand Up @@ -60,11 +61,7 @@ public static function parse(string $ksuid): array
}

$bytes = self::toBytes($ksuid);
$unpackedTimestamp = unpack('N', substr($bytes, 0, 4));
($unpackedTimestamp !== false) || throw new Exception('Unable to parse KSUID timestamp');
$timestampValue = $unpackedTimestamp[1] ?? null;
is_int($timestampValue) || throw new Exception('Unable to parse KSUID timestamp');
$timestamp = $timestampValue + self::$epoch;
$timestamp = BinaryUnpack::u32(substr($bytes, 0, 4), 'Unable to parse KSUID timestamp') + self::$epoch;
$data['time'] = new DateTimeImmutable('@' . $timestamp);
$data['payload'] = bin2hex(substr($bytes, 4));

Expand Down
19 changes: 6 additions & 13 deletions src/Sequence/FilesystemSequenceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Infocyph\UID\Sequence;

use Infocyph\UID\Exceptions\FileLockException;
use Infocyph\UID\Support\FileLock;

final readonly class FilesystemSequenceProvider implements SequenceProviderInterface
{
Expand Down Expand Up @@ -40,21 +41,13 @@ public function next(string $type, int $machineId, int $timestamp): int
*/
private function acquireLock(string $fileLocation)
{
($handle = fopen($fileLocation, 'c+')) || throw new FileLockException(
return FileLock::acquire(
$fileLocation,
$this->waitTime,
$this->maxAttempts,
'Failed to open sequence file: ' . $fileLocation,
'Unable to acquire sequence lock: ' . $fileLocation,
);

for ($attempts = 0; $attempts < $this->maxAttempts; $attempts++) {
if (flock($handle, LOCK_EX | LOCK_NB)) {
return $handle;
}

usleep($this->waitTime);
}

fclose($handle);

throw new FileLockException('Unable to acquire sequence lock: ' . $fileLocation);
}

private function sequenceFileLocation(string $type, int $machineId): string
Expand Down
20 changes: 7 additions & 13 deletions src/Sequence/PsrSimpleCacheSequenceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Closure;
use Infocyph\UID\Exceptions\FileLockException;
use Infocyph\UID\Support\FileLock;
use Psr\SimpleCache\CacheInterface;
use Throwable;

Expand Down Expand Up @@ -79,21 +80,14 @@ public function next(string $type, int $machineId, int $timestamp): int
private function acquireLock(string $key)
{
$lockFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'uid-cache-lock-' . md5($key) . '.lck';
($handle = fopen($lockFile, 'c+')) || throw new FileLockException(

return FileLock::acquire(
$lockFile,
$this->waitTime,
$this->maxAttempts,
'Unable to open sequence cache lock file: ' . $lockFile,
'Unable to acquire sequence cache lock for key: ' . $key,
);

for ($attempt = 0; $attempt < $this->maxAttempts; $attempt++) {
if (flock($handle, LOCK_EX | LOCK_NB)) {
return $handle;
}

usleep($this->waitTime);
}

fclose($handle);

throw new FileLockException('Unable to acquire sequence cache lock for key: ' . $key);
}

private function key(string $type, int $machineId): string
Expand Down
Loading