Skip to content

Commit bf24d19

Browse files
committed
Refactor other Snowflakes
1 parent 5130bbb commit bf24d19

13 files changed

+181
-252
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ composer require cycle/entity-behavior-identifier
1818

1919
## Snowflake Examples
2020

21-
**Generic:** A flexible Snowflake format that can use a node identifier and any epoch offset, suitable for various applications requiring unique identifiers. Default values for `node` and `epochOffset` can be defined globally via class `Cycle\ORM\Entity\Behavior\Identifier\Defaults\SnowflakeGeneric`.
21+
### Generic
22+
A flexible Snowflake format that can use a node identifier and any epoch offset, suitable for various applications requiring unique identifiers. Default values for `node` and `epochOffset` can be defined globally via the `\Cycle\ORM\Entity\Behavior\Identifier\Listener\SnowflakeGeneric::setDefaults()` method.
2223

2324
```php
2425
use Cycle\Annotated\Annotation\Column;
@@ -71,7 +72,8 @@ class User
7172
}
7273
```
7374

74-
**Mastodon:** Snowflake identifier for Mastodon’s decentralized social network, generated within a database to ensure uniqueness and approximate order within 1ms. Can include a table name for distinct sequences per table; IDs are unique on a single database but not guaranteed across multiple machines. Default values for `tableName` can be defined globally via class `Cycle\ORM\Entity\Behavior\Identifier\Defaults\SnowflakeMastodon`.
75+
### Mastodon
76+
Snowflake identifier for Mastodon's decentralized social network, generated within a database to ensure uniqueness and approximate order within 1ms. Can include a table name for distinct sequences per table; IDs are unique on a single database but not guaranteed across multiple machines. Default values for `tableName` can be defined globally via the `\Cycle\ORM\Entity\Behavior\Identifier\Listener\SnowflakeMastodon::setDefaults()` method.
7577

7678
```php
7779
use Cycle\Annotated\Annotation\Column;
@@ -88,7 +90,8 @@ class User
8890
}
8991
```
9092

91-
**Twitter:** Snowflake identifier for Twitter (X), beginning from `2010-11-04`. Can incorporate a machine ID to generate distinct Snowflakes. Default values for `machineId` can be defined globally via class `Cycle\ORM\Entity\Behavior\Identifier\Defaults\SnowflakeTwitter`.
93+
### Twitter
94+
Snowflake identifier for Twitter (X), beginning from `2010-11-04`. Can incorporate a machine ID to generate distinct Snowflakes. Default values for `machineId` can be defined globally via the `\Cycle\ORM\Entity\Behavior\Identifier\Listener\SnowflakeTwitter::setDefaults()` method.
9295

9396
```php
9497
use Cycle\Annotated\Annotation\Column;

src/Defaults/SnowflakeGeneric.php

Lines changed: 0 additions & 33 deletions
This file was deleted.

src/Defaults/SnowflakeMastodon.php

Lines changed: 0 additions & 29 deletions
This file was deleted.

src/Defaults/SnowflakeTwitter.php

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/Listener/SnowflakeGeneric.php

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,63 @@
44

55
namespace Cycle\ORM\Entity\Behavior\Identifier\Listener;
66

7-
use Cycle\ORM\Entity\Behavior\Attribute\Listen;
8-
use Cycle\ORM\Entity\Behavior\Event\Mapper\Command\OnCreate;
7+
use Ramsey\Identifier\Snowflake;
98
use Ramsey\Identifier\Snowflake\Epoch;
109
use Ramsey\Identifier\Snowflake\GenericSnowflakeFactory;
1110

12-
final class SnowflakeGeneric
11+
/**
12+
* Generates generic Snowflake identifiers for entities.
13+
* You can set default node and epoch offset using the {@see setDefaults()} method.
14+
*/
15+
final class SnowflakeGeneric extends \Cycle\ORM\Entity\Behavior\Identifier\Listener\Snowflake
1316
{
17+
/** @var int<0, 1023> */
18+
private static int $node = 0;
19+
20+
/** @var Epoch|int */
21+
private static Epoch|int $epochOffset = 0;
22+
23+
private GenericSnowflakeFactory $factory;
24+
25+
/**
26+
* @param non-empty-string $field The name of the field to store the Snowflake identifier
27+
* @param bool $nullable Indicates whether the Snowflake identifier can be null
28+
* @param int<0, 1023>|null $node A node identifier to use when creating Snowflakes
29+
* @param Epoch|int|null $epochOffset The offset from the Unix Epoch in milliseconds
30+
*/
1431
public function __construct(
15-
private string $field = 'snowflake',
16-
private int $node = 0,
17-
private Epoch|int $epochOffset = 0,
18-
private bool $nullable = false,
19-
) {}
20-
21-
#[Listen(OnCreate::class)]
22-
public function __invoke(OnCreate $event): void
32+
string $field,
33+
bool $nullable = false,
34+
?int $node = null,
35+
Epoch|int|null $epochOffset = null,
36+
) {
37+
$node ??= self::$node;
38+
$epochOffset ??= self::$epochOffset;
39+
$this->factory = new GenericSnowflakeFactory($node, $epochOffset);
40+
parent::__construct($field, $nullable);
41+
}
42+
43+
/**
44+
* Set default node and epoch offset for Snowflake generation.
45+
*
46+
* @param null|int<0, 1023> $node The node ID to set. Null to use the default (0).
47+
* @param Epoch|int|null $epochOffset The epoch offset to set. Null to use the default (0).
48+
*/
49+
public static function setDefaults(?int $node, Epoch|int|null $epochOffset): void
2350
{
24-
if ($this->nullable || isset($event->state->getData()[$this->field])) {
25-
return;
51+
if ($node !== null && ($node < 0 || $node > 1023)) {
52+
throw new \InvalidArgumentException('Node ID must be between 0 and 1023.');
2653
}
2754

28-
$identifier = (new GenericSnowflakeFactory($this->node, $this->epochOffset))->create();
55+
self::$node = (int) $node;
56+
if ($epochOffset !== null) {
57+
self::$epochOffset = $epochOffset;
58+
}
59+
}
2960

30-
$event->state->register($this->field, $identifier);
61+
#[\Override]
62+
protected function createValue(): Snowflake
63+
{
64+
return $this->factory->create();
3165
}
3266
}

src/Listener/SnowflakeMastodon.php

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,48 @@
44

55
namespace Cycle\ORM\Entity\Behavior\Identifier\Listener;
66

7-
use Cycle\ORM\Entity\Behavior\Attribute\Listen;
8-
use Cycle\ORM\Entity\Behavior\Event\Mapper\Command\OnCreate;
7+
use Ramsey\Identifier\Snowflake\MastodonSnowflake;
98
use Ramsey\Identifier\Snowflake\MastodonSnowflakeFactory;
109

11-
final class SnowflakeMastodon
10+
/**
11+
* Generates Mastodon Snowflake identifiers for entities.
12+
* You can set default table name using the {@see setDefaults()} method.
13+
*/
14+
final class SnowflakeMastodon extends Snowflake
1215
{
16+
/** @var non-empty-string|null */
17+
private static ?string $tableName = null;
18+
19+
private MastodonSnowflakeFactory $factory;
20+
1321
/**
14-
* @param non-empty-string|null $tableName
22+
* @param non-empty-string $field The name of the field to store the Snowflake identifier
23+
* @param bool $nullable Indicates whether the Snowflake identifier can be null
24+
* @param non-empty-string|null $tableName Database table name ensuring different tables derive separate sequence bases
1525
*/
1626
public function __construct(
17-
private string $field = 'snowflake',
18-
private ?string $tableName = null,
19-
private bool $nullable = false,
20-
) {}
27+
string $field,
28+
bool $nullable = false,
29+
?string $tableName = null,
30+
) {
31+
$tableName ??= self::$tableName;
32+
$this->factory = new MastodonSnowflakeFactory($tableName);
33+
parent::__construct($field, $nullable);
34+
}
2135

22-
#[Listen(OnCreate::class)]
23-
public function __invoke(OnCreate $event): void
36+
/**
37+
* Set default table name for Snowflake generation.
38+
*
39+
* @param non-empty-string|null $tableName The table name to set. Null to use the default (null).
40+
*/
41+
public static function setDefaults(?string $tableName): void
2442
{
25-
if ($this->nullable || isset($event->state->getData()[$this->field])) {
26-
return;
27-
}
28-
29-
$identifier = (new MastodonSnowflakeFactory($this->tableName))->create();
43+
self::$tableName = $tableName;
44+
}
3045

31-
$event->state->register($this->field, $identifier);
46+
#[\Override]
47+
protected function createValue(): MastodonSnowflake
48+
{
49+
return $this->factory->create();
3250
}
3351
}

src/Listener/SnowflakeTwitter.php

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,52 @@
44

55
namespace Cycle\ORM\Entity\Behavior\Identifier\Listener;
66

7-
use Cycle\ORM\Entity\Behavior\Attribute\Listen;
8-
use Cycle\ORM\Entity\Behavior\Event\Mapper\Command\OnCreate;
7+
use Ramsey\Identifier\Snowflake\TwitterSnowflake;
98
use Ramsey\Identifier\Snowflake\TwitterSnowflakeFactory;
109

11-
final class SnowflakeTwitter
10+
/**
11+
* Generates Twitter Snowflake identifiers for entities.
12+
* You can set default machine ID using the {@see setDefaults()} method.
13+
*/
14+
final class SnowflakeTwitter extends Snowflake
1215
{
16+
/** @var int<0, 1023> */
17+
private static int $machineId = 0;
18+
19+
private TwitterSnowflakeFactory $factory;
20+
21+
/**
22+
* @param non-empty-string $field The name of the field to store the Snowflake identifier
23+
* @param bool $nullable Indicates whether the Snowflake identifier can be null
24+
* @param int<0, 1023>|null $machineId A machine identifier to use when creating Snowflakes
25+
*/
1326
public function __construct(
14-
private string $field = 'snowflake',
15-
private int $machineId = 0,
16-
private bool $nullable = false,
17-
) {}
27+
string $field,
28+
bool $nullable = false,
29+
?int $machineId = null,
30+
) {
31+
$machineId ??= self::$machineId;
32+
$this->factory = new TwitterSnowflakeFactory($machineId);
33+
parent::__construct($field, $nullable);
34+
}
1835

19-
#[Listen(OnCreate::class)]
20-
public function __invoke(OnCreate $event): void
36+
/**
37+
* Set default machine ID for Snowflake generation.
38+
*
39+
* @param null|int<0, 1023> $machineId The machine ID to set. Null to use the default (0).
40+
*/
41+
public static function setDefaults(?int $machineId): void
2142
{
22-
if ($this->nullable || isset($event->state->getData()[$this->field])) {
23-
return;
43+
if ($machineId !== null && ($machineId < 0 || $machineId > 1023)) {
44+
throw new \InvalidArgumentException('Machine ID must be between 0 and 1023.');
2445
}
2546

26-
$identifier = (new TwitterSnowflakeFactory($this->machineId))->create();
47+
self::$machineId = (int) $machineId;
48+
}
2749

28-
$event->state->register($this->field, $identifier);
50+
#[\Override]
51+
protected function createValue(): TwitterSnowflake
52+
{
53+
return $this->factory->create();
2954
}
3055
}

src/SnowflakeGeneric.php

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@
55
namespace Cycle\ORM\Entity\Behavior\Identifier;
66

77
use Cycle\ORM\Entity\Behavior\Identifier\Snowflake as BaseSnowflake;
8-
use Cycle\ORM\Entity\Behavior\Identifier\Defaults\SnowflakeGeneric as Defaults;
98
use Cycle\ORM\Entity\Behavior\Identifier\Listener\SnowflakeGeneric as Listener;
109
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
11-
use JetBrains\PhpStorm\ArrayShape;
1210
use Ramsey\Identifier\Snowflake\Epoch;
1311
use Ramsey\Identifier\Snowflake\GenericSnowflakeFactory;
1412
use Ramsey\Identifier\SnowflakeFactory;
@@ -24,30 +22,25 @@
2422
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE), NamedArgumentConstructor]
2523
final class SnowflakeGeneric extends BaseSnowflake
2624
{
27-
private int $node;
28-
private Epoch|int $epochOffset;
29-
3025
/**
3126
* @param non-empty-string $field Snowflake property name
32-
* @param string|null $column Snowflake column name
33-
* @param int|null $node A node identifier to use when creating Snowflakes
27+
* @param non-empty-string|null $column Snowflake column name
28+
* @param int<0, 1023>|null $node A node identifier to use when creating Snowflakes
3429
* @param Epoch|int|null $epochOffset The offset from the Unix Epoch in milliseconds
3530
* @param bool $nullable Indicates whether to generate a new Snowflake or not
3631
*
3732
* @see \Ramsey\Identifier\Snowflake\GenericSnowflakeFactory::create()
3833
*/
3934
public function __construct(
40-
string $field = 'snowflake',
35+
string $field,
4136
?string $column = null,
42-
?int $node = null,
43-
Epoch|int|null $epochOffset = null,
37+
private readonly ?int $node = null,
38+
private readonly Epoch|int|null $epochOffset = null,
4439
bool $nullable = false,
4540
) {
4641
$this->field = $field;
4742
$this->column = $column;
4843
$this->nullable = $nullable;
49-
$this->node = $node === null ? Defaults::getNode() : $node;
50-
$this->epochOffset = $epochOffset === null ? Defaults::getEpochOffset() : $epochOffset;
5144
}
5245

5346
#[\Override]
@@ -56,12 +49,14 @@ protected function getListenerClass(): string
5649
return Listener::class;
5750
}
5851

59-
#[ArrayShape([
60-
'field' => 'string',
61-
'node' => 'Epoch|int',
62-
'epochOffset' => 'int',
63-
'nullable' => 'bool',
64-
])]
52+
/**
53+
* @return array{
54+
* field: non-empty-string,
55+
* node: null|int<0, 1023>,
56+
* epochOffset: Epoch|int|null,
57+
* nullable: bool
58+
* }
59+
*/
6560
#[\Override]
6661
protected function getListenerArgs(): array
6762
{

0 commit comments

Comments
 (0)