Skip to content

Commit 10d6a5c

Browse files
authored
Release/1.1.0 (#4)
* feat: Adds inspection of Ksuid components.
1 parent 64162da commit 10d6a5c

File tree

8 files changed

+135
-14
lines changed

8 files changed

+135
-14
lines changed

README.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,28 +29,46 @@ composer require tiny-blocks/ksuid
2929

3030
The library exposes a concrete implementation through the `Ksuid` class.
3131

32+
### Creating a Ksuid
33+
3234
With the `random` method, a new instance of type `Ksuid` is created from a timestamp (_current unix timestamp - EPOCH_)
3335
and a payload (_cryptographically secure pseudo-random bytes_).
3436

3537
```php
3638
$ksuid = Ksuid::random();
3739

38-
echo $ksuid->getValue(); # 2QvY47aUlV3cSyYcpo53FQxgSFg
39-
echo $ksuid->getPayload(); # bdf0a2329620aa70cebe4026ca9ff49c
40-
echo $ksuid->getTimestamp(); # 286235327
40+
echo $ksuid->getValue(); # 2QzPUGEaAKHhVcQYrqQodbiZat1
41+
echo $ksuid->getPayload(); # 464932c1194da98e752145d72b8f0aab
42+
echo $ksuid->getUnixTime(); # 1686353450
43+
echo $ksuid->getTimestamp(); # 286353450
4144
```
4245

4346
You can also choose from other factory models.
4447

4548
```php
4649

47-
Ksuid::from(payload: hex2bin("9850EEEC191BF4FF26F99315CE43B0C8"), timestamp: 286235327);
50+
Ksuid::from(payload: hex2bin('9850EEEC191BF4FF26F99315CE43B0C8'), timestamp: 286235327);
4851

4952
Ksuid::fromPayload(value: '0o5Fs0EELR0fUjHjbCnEtdUwQe3');
5053

5154
Ksuid::fromTimestamp(value: 286235327);
5255
```
5356

57+
### Inspecting a Ksuid
58+
59+
You can inspect the components used to create a `Ksuid`, using the `inspectFrom` method.
60+
61+
```php
62+
$ksuid = Ksuid::inspectFrom(ksuid: '2QzPUGEaAKHhVcQYrqQodbiZat1');
63+
64+
print_r($ksuid); # Array
65+
# (
66+
# [time] => 2023-06-09 23:30:50 +0000 GMT+0000
67+
# [payload] => 464932c1194da98e752145d72b8f0aab
68+
# [timestamp] => 286353450
69+
# )
70+
```
71+
5472
## License
5573

5674
Math is licensed under [MIT](/LICENSE).

composer.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"type": "library",
44
"license": "MIT",
55
"homepage": "https://github.com/tiny-blocks/ksuid",
6-
"description": "K-Sortable Unique Identifier for PHP.",
6+
"description": "K-Sortable Unique Identifier.",
77
"prefer-stable": true,
88
"minimum-stability": "stable",
99
"keywords": [
@@ -39,15 +39,18 @@
3939
}
4040
},
4141
"require": {
42-
"php": "^8.2",
43-
"tiny-blocks/encoder": "^1"
42+
"php": "^8.1||^8.2",
43+
"tiny-blocks/encoder": "^1.2"
4444
},
4545
"require-dev": {
4646
"infection/infection": "^0.26",
4747
"phpmd/phpmd": "^2.12",
4848
"phpunit/phpunit": "^9.5",
4949
"squizlabs/php_codesniffer": "^3.7"
5050
},
51+
"suggest": {
52+
"ext-gmp": "Enables faster math with arbitrary-precision integers using GMP."
53+
},
5154
"scripts": {
5255
"phpcs": "phpcs --standard=PSR12 --extensions=php ./src",
5356
"phpmd": "phpmd ./src text phpmd.xml --suffixes php --ignore-violations-on-exit",

infection.json.dist

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
"UnwrapSubstr": false,
2222
"UnwrapStrRepeat": false,
2323
"IncrementInteger": false,
24-
"DecrementInteger": false
24+
"DecrementInteger": false,
25+
"PublicVisibility": false
2526
},
2627
"phpUnit": {
2728
"configDir": "",
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace TinyBlocks\Ksuid\Internal\Exceptions;
4+
5+
use RuntimeException;
6+
7+
final class InvalidKsuidForInspection extends RuntimeException
8+
{
9+
public function __construct(private readonly string $ksuid)
10+
{
11+
$template = 'The KSUID <%s> is not valid for inspection.';
12+
parent::__construct(message: sprintf($template, $this->ksuid));
13+
}
14+
}

src/Internal/Exceptions/InvalidPayloadSize.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ final class InvalidPayloadSize extends RuntimeException
99
public function __construct(private readonly int $currentSize, private readonly int $payloadBytes)
1010
{
1111
$template = 'Current length is <%s> bytes. Payload size must be exactly <%s> bytes.';
12-
parent::__construct(sprintf($template, $this->currentSize, $this->payloadBytes));
12+
parent::__construct(message: sprintf($template, $this->currentSize, $this->payloadBytes));
1313
}
1414
}

src/Internal/Timestamp.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@
22

33
namespace TinyBlocks\Ksuid\Internal;
44

5+
use DateTime;
56
use TinyBlocks\Encoder\Base62;
67

78
final class Timestamp
89
{
910
public const EPOCH = 1400000000;
1011

11-
private function __construct(private readonly int $value)
12+
private readonly int $time;
13+
14+
private function __construct(private readonly int $value, private readonly int $epoch)
1215
{
16+
$this->time = $this->value - $this->epoch;
1317
}
1418

1519
public static function from(int $value): Timestamp
1620
{
17-
return new Timestamp(value: $value);
21+
return new Timestamp(value: $value, epoch: 0);
1822
}
1923

2024
public static function fromBytes(string $value): Timestamp
@@ -24,16 +28,26 @@ public static function fromBytes(string $value): Timestamp
2428
$timestamp = substr($timestamp, -4);
2529
$timestamp = (array)unpack("Nuint", $timestamp);
2630

27-
return new Timestamp(value: $timestamp["uint"]);
31+
return new Timestamp(value: $timestamp["uint"], epoch: 0);
2832
}
2933

3034
public static function fromAdjustedCurrentTime(): Timestamp
3135
{
32-
return new Timestamp(value: time() - self::EPOCH);
36+
return new Timestamp(value: time(), epoch: self::EPOCH);
37+
}
38+
39+
public static function format(int $timestamp): string
40+
{
41+
return (new DateTime("@$timestamp"))->format('Y-m-d H:i:s O T');
3342
}
3443

3544
public function getValue(): int
3645
{
37-
return $this->value;
46+
return $this->time;
47+
}
48+
49+
public function getUnixTime(): int
50+
{
51+
return $this->time + self::EPOCH;
3852
}
3953
}

src/Ksuid.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace TinyBlocks\Ksuid;
44

55
use TinyBlocks\Encoder\Base62;
6+
use TinyBlocks\Ksuid\Internal\Exceptions\InvalidKsuidForInspection;
67
use TinyBlocks\Ksuid\Internal\Payload;
78
use TinyBlocks\Ksuid\Internal\Timestamp;
89

@@ -34,6 +35,21 @@ public static function fromTimestamp(int $value): Ksuid
3435
return new Ksuid(payload: Payload::random(), timestamp: Timestamp::from(value: $value));
3536
}
3637

38+
public static function inspectFrom(string $ksuid): array
39+
{
40+
if (strlen($ksuid) !== self::ENCODED_SIZE) {
41+
throw new InvalidKsuidForInspection(ksuid: $ksuid);
42+
}
43+
44+
$ksuid = self::fromPayload(value: $ksuid);
45+
46+
return [
47+
'time' => Timestamp::format(timestamp: $ksuid->getUnixTime()),
48+
'payload' => $ksuid->getPayload(),
49+
'timestamp' => $ksuid->getTimestamp()
50+
];
51+
}
52+
3753
public function getValue(): string
3854
{
3955
$encoded = Base62::encode(value: $this->getBytes());
@@ -56,6 +72,11 @@ public function getPayload(): string
5672
return bin2hex($this->payload->getValue());
5773
}
5874

75+
public function getUnixTime(): int
76+
{
77+
return $this->timestamp->getUnixTime();
78+
}
79+
5980
public function getTimestamp(): int
6081
{
6182
return $this->timestamp->getValue();

tests/KsuidTest.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace TinyBlocks\Ksuid;
44

5+
use Exception;
56
use PHPUnit\Framework\TestCase;
7+
use TinyBlocks\Ksuid\Internal\Exceptions\InvalidKsuidForInspection;
68

79
class KsuidTest extends TestCase
810
{
@@ -59,4 +61,52 @@ public function testFromPayloadAndTimestamp(): void
5961
self::assertEquals(20, strlen($ksuid->getBytes()));
6062
self::assertEquals(Ksuid::ENCODED_SIZE, strlen($ksuid->getValue()));
6163
}
64+
65+
/**
66+
* @dataProvider providerForTestInspectFrom
67+
*/
68+
public function testInspectFrom(string $ksuid, array $expected): void
69+
{
70+
$actual = Ksuid::inspectFrom(ksuid: $ksuid);
71+
72+
self::assertEquals($expected, $actual);
73+
}
74+
75+
/**
76+
* @throws Exception
77+
*/
78+
public function testExceptionWhenInvalidKsuidForInspection(): void
79+
{
80+
/** @Given a invalid KSUID */
81+
$ksuid = random_bytes(5);
82+
$template = 'The KSUID <%s> is not valid for inspection.';
83+
84+
/** @Then an exception indicating that KSUID is invalid for inspection should occur */
85+
$this->expectException(InvalidKsuidForInspection::class);
86+
$this->expectExceptionMessage(sprintf($template, $ksuid));
87+
88+
Ksuid::inspectFrom(ksuid: $ksuid);
89+
}
90+
91+
public function providerForTestInspectFrom(): array
92+
{
93+
return [
94+
[
95+
'ksuid' => '2QzPUGEaAKHhVcQYrqQodbiZat1',
96+
'expected' => [
97+
'time' => '2023-06-09 23:30:50 +0000 GMT+0000',
98+
'payload' => '464932c1194da98e752145d72b8f0aab',
99+
'timestamp' => 286353450
100+
]
101+
],
102+
[
103+
'ksuid' => '0ujzPyRiIAffKhBux4PvQdDqMHY',
104+
'expected' => [
105+
'time' => '2017-10-10 04:46:20 +0000 GMT+0000',
106+
'payload' => '73fc1aa3b2446246d6e89fcd909e8fe8',
107+
'timestamp' => 107610780
108+
]
109+
]
110+
];
111+
}
62112
}

0 commit comments

Comments
 (0)