Skip to content

Commit

Permalink
Merge pull request #2 from calebdw/sonyflake
Browse files Browse the repository at this point in the history
feat: add sonyflake support
  • Loading branch information
calebdw authored Aug 2, 2024
2 parents 22ca389 + b965abd commit 451610d
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 12 deletions.
31 changes: 27 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,22 @@ php artisan vendor:publish --provider="CalebDW\Laraflake\ServiceProvider"

## Configuration

### Snowflake Epoch
### Snowflake Type

The Snowflake type determines the class used to generate Snowflakes.

The default Snowflake type is `Godruoyi\Snowflake\Snowflake` which uses 41 bits for the epoch, 5 bits for the data center ID, 5 bits for the worker ID, and 12 bits for the sequence.
This allows for up to `1024` workers and `4096` unique IDs per worker per millisecond.

You can change the Snowflake type to `Godruoyi\Snowflake\Sonyflake` which uses 39 bits for the epoch, 16 bits for the machine ID, and 8 bits for the sequence.
This allows for up to `65535` machines and `256` unique IDs per worker per *10 milliseconds*.

### Epoch

The timestamp encoded in the Snowflake is the difference between the time of creation and a given starting epoch/timestamp.
Snowflakes use 41 bits and can generate IDs for up to 69 years past the given epoch.
Sonyflakes use 39 bits and can generate IDs for up to 174 years past the given epoch.

The 41-bit timestamp encoded in the Snowflake is the difference between the time of creation and a given starting epoch/timestamp.
Snowflakes can be generated for up to 69 years past the given epoch.
In most cases you should set this value to the current date using a format of `YYYY-MM-DD`.

> **Note**:
Expand All @@ -66,9 +78,18 @@ as that may reduce the number of years for which you can generate timestamps.

### Data Center & Worker IDs

If using distributed systems, you'll need to set the data center and worker IDs that the application should use when generating Snowflakes.
> **Note**: This is only used for the `Snowflake` type.
You can set the data center and worker IDs that the application should use when generating Snowflakes.
These are used to ensure that each worker generates unique Snowflakes and can range from `0` to `31` (up to `1024` unique workers).

### Machine ID

> **Note**: This is only used for the `Sonyflake` type.
You can set the machine ID that the application should use when generating Sonyflakes.
This is used to ensure that each machine generates unique Sonyflakes and can range from `0` to `65535`.

## Usage

> **WARNING**: Do not create new instances of the Snowflake generator (as this could cause collisions), always use the Snowflake singleton from the container.
Expand All @@ -81,6 +102,7 @@ use Godruoyi\Snowflake\Snowflake;
resolve('snowflake')->id(); // (string) "5585066784854016"
resolve(Snowflake::class)->id(); // (string) "5585066784854016"
```

This package also provides a `snowflake` helper function, a `Snowflake` facade, and a `Str::snowflakeId` macro for convenience:

```php
Expand Down Expand Up @@ -130,6 +152,7 @@ class Post extends Model
```

The trait provides several features for the model's Snowflake columns:

- the generation of Snowflakes for new records
- route model binding
- automatic casting from database integers to strings which prevents truncation in languages that do not support 64-bit integers (such as JavaScript).
Expand Down
25 changes: 24 additions & 1 deletion config/laraflake.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@

declare(strict_types=1);

use Godruoyi\Snowflake\Snowflake;

return [
/*
|--------------------------------------------------------------------------
| Snowflake Type
|--------------------------------------------------------------------------
|
| The class to use by Laraflake when generating unique identifiers.
| The default is the Snowflake class, but you can also use the Sonyflake class.
*/
'snowflake_type' => Snowflake::class,

/*
|--------------------------------------------------------------------------
| Snowflake Epoch
Expand All @@ -17,7 +29,7 @@

/*
|--------------------------------------------------------------------------
| Data Center & Worker IDs
| Snowflake Data Center & Worker IDs
|--------------------------------------------------------------------------
|
| These values represents the data center and worker ids that should be used
Expand All @@ -26,4 +38,15 @@
*/
'datacenter_id' => env('LARAFLAKE_DATACENTER_ID', 0),
'worker_id' => env('LARAFLAKE_WORKER_ID', 0),

/*
|--------------------------------------------------------------------------
| Sonyflake Machine ID
|--------------------------------------------------------------------------
|
| This value represents the machine id that should be used by Sonyflake when
| generating unique identifiers. The value must be 0--65535 (2^16 - 1).
|
*/
'machine_id' => env('LARAFLAKE_MACHINE_ID', 0),
];
37 changes: 30 additions & 7 deletions src/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,25 @@
use Godruoyi\Snowflake\RedisSequenceResolver;
use Godruoyi\Snowflake\SequenceResolver;
use Godruoyi\Snowflake\Snowflake;
use Godruoyi\Snowflake\Sonyflake;
use Godruoyi\Snowflake\SwooleSequenceResolver;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Foundation\Console\AboutCommand;
use Illuminate\Support\ServiceProvider as IlluminateServiceProvider;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;

use InvalidArgumentException;

/**
* @phpstan-type LaraflakeConfig array{
* datacenter_id: int,
* worker_id: int,
* epoch: string,
* machine_id: int,
* snowflake_type: class-string<Snowflake>,
* }
*/
class ServiceProvider extends IlluminateServiceProvider
{
/** @inheritDoc */
Expand All @@ -42,13 +53,22 @@ public function boot(): void
$this->registerMixins();

AboutCommand::add('Laraflake', function () {
/** @var array{datacenter_id: int, worker_id: int, epoch: string} $config */
/** @var LaraflakeConfig $config */
$config = config('laraflake');

return [
'Snowflake Type' => $config['snowflake_type'],
...match ($config['snowflake_type']) {
Snowflake::class => [
'Datacenter ID' => $config['datacenter_id'],
'Worker ID' => $config['worker_id'],
],
Sonyflake::class => [
'Machine ID' => $config['machine_id'],
],
default => [],
},
'Epoch' => $config['epoch'],
'Datacenter ID' => $config['datacenter_id'],
'Worker ID' => $config['worker_id'],
'Sequence Resolver' => $this->getPrettyResolver(),
'Version' => InstalledVersions::getPrettyVersion('calebdw/laraflake'),
];
Expand All @@ -67,11 +87,14 @@ protected function registerMixins(): void
protected function registerSnowflake(): void
{
$this->app->singleton(Snowflake::class, function ($app) {
/** @var array{datacenter_id: int, worker_id: int, epoch: string} $config */
/** @var LaraflakeConfig $config */
$config = config('laraflake');

return (new Snowflake($config['datacenter_id'], $config['worker_id']))
->setStartTimeStamp(strtotime($config['epoch']) * 1000)
return (match ($config['snowflake_type']) {
Snowflake::class => new Snowflake($config['datacenter_id'], $config['worker_id']),
Sonyflake::class => new Sonyflake($config['machine_id']),
default => throw new InvalidArgumentException("Invalid Snowflake type: {$config['snowflake_type']}"),
})->setStartTimeStamp(strtotime($config['epoch']) * 1000)
->setSequenceResolver($app->make(SequenceResolver::class));
});
$this->app->alias(Snowflake::class, 'laraflake');
Expand Down
24 changes: 24 additions & 0 deletions tests/Feature/ServiceProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use Godruoyi\Snowflake\LaravelSequenceResolver;
use Godruoyi\Snowflake\RandomSequenceResolver;
use Godruoyi\Snowflake\SequenceResolver;
use Godruoyi\Snowflake\Snowflake;
use Godruoyi\Snowflake\Sonyflake;
use Illuminate\Contracts\Cache\Repository;

it('binds random sequence resolver when there is not a cache', function () {
Expand All @@ -28,3 +30,25 @@
->assertSuccessful()
->expectsOutputToContain('Laraflake');
});

it('supports Sonyflakes', function () {
config()->set('laraflake.snowflake_type', Sonyflake::class);

expect(app()->make(Snowflake::class))
->toBeInstanceOf(Sonyflake::class);

test()->artisan('about')
->assertSuccessful()
->expectsOutputToContain('Sonyflake');
});

it('throws exception for invalid snowflake type', function () {
config()->set('laraflake.snowflake_type', 'invalid');

test()->artisan('about')
->assertSuccessful()
->doesntExpectOutputToContain('Sonyflake');

expect(app()->make(Snowflake::class))
->toBeInstanceOf(Sonyflake::class);
})->throws(InvalidArgumentException::class);

0 comments on commit 451610d

Please sign in to comment.