Skip to content

Commit

Permalink
docs: update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
calebdw committed Jul 19, 2024
1 parent 60323ec commit 7e08010
Showing 1 changed file with 120 additions and 79 deletions.
199 changes: 120 additions & 79 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,138 +1,106 @@
# Laraflake

<p align="center">
<img src="/art/laraflake.webp" alt="Laraflake" style="width:40%;">
<img src="/art/laraflake.webp" alt="Laraflake" style="width:35%;">
</p>
<p align="center">Generate X/Twitter <a href="https://en.wikipedia.org/wiki/Snowflake_ID">Snowflake identifiers</a> in Laravel.</p>

This package enables a Laravel application to create [Snowflake IDs](https://en.wikipedia.org/wiki/Snowflake_ID).
It is a very thin wrapper around the excellent [godruoyi/php-snowflake](https://github.com/godruoyi/php-snowflake) library.

## What are Snowflakes?

Snowflakes are a form of unique identifier devised by X/Twitter and are used by many companies, including Instagram and Discord, to generate unique IDs for their entities.

Some of the benefits of using Snowflakes (over alternatives such as UUID/ULID) include:

- They consist entirely of integers.
- They use less space (16 characters, so it fits in a `BIGINT`).
- Indexing of integers is much faster than indexing a string.
- Keys begin with a timestamp, so are sortable.
- Keys end with a random number, so guessing table size is not possible.
- Databases handle integers more efficiently than strings.
- Generation of new keys is faster (less than 1 ms).
- **Timestamp Component:** Extract creation time directly from the ID.
- **Uniqueness Across Distributed Systems:** Ensures unique IDs without coordination.
- **Orderability:** Roughly ordered by creation time for easy sorting.
- **Compactness:** 64-bit size, more compact than 128-bit UUIDs.
- **Performance:** Faster and less resource-intensive generation.
- **Configurability:** Flexible bit allocation for specific needs.
- **Storage Efficiency:** More efficient storage compared to larger identifiers.
- **Database Indexing:** Faster indexing and query performance.
- **Human Readability:** More compact and readable than longer identifiers.

## Installation

Pull in the package using Composer:
First pull in the package using Composer:

```bash
composer require calebdw/laraflake
```

## Configuration

Snowflake includes a configuration file with several settings that you can use to initialize the Snowflake service.
You should begin by publishing this configuration file:
And then publish the package's configuration file:

```bash
php artisan vendor:publish
php artisan vendor:publish --provider="CalebDW\Laraflake\ServiceProvider"
```

## Configuration

### Snowflake 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.

The default epoch is `2024-01-01`, but in most cases you should set this value to the current date using a format of `YYYY-MM-DD`.
In most cases you should set this value to the current date using a format of `YYYY-MM-DD`.

> **Note**:
> Do not set the timestamp to a date in the future, as that won't achieve anything.
> You should also avoid using a date far in the past (such as the Unix epoch `1970-01-01`), as that may reduce the number of years for which you can generate timestamps.
> Future dates will throw an error and you should avoid using a date far in the past (such as the Unix epoch `1970-01-01`)
as that may reduce the number of years for which you can generate timestamps.

### Data Center & Worker IDs

If using a distributed architectural setup, you'll need to set the data center and worker IDs that the application should use when generating Snowflakes.
These are both set to `0` by default, as that is a good starting point, but you are free to increase these numbers as you add more workers and data centers.

The maximums for each of these configuration values is `31`. This gives you up to 32 workers per data center, and 32 data centers in total.
Therefore, you can have up `1024` workers each generating unique Snowflakes.

### Sequence resolver

In order to handle the generation of unique keys within the same millisecond, the service uses a sequence resolver.
There are several to choose from, however they each have dependencies, such as Redis.
You are free to use any of them, however the default option is a good choice, as it **doesn't** have any dependencies.
If using distributed systems, you'll need to 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).

## Usage

> **WARNING**: Do not create new instances of the Snowflake service, as doing so risks generating matching keys / introducing collisions.
> Instead, always resolve the Snowflake singleton out of the container. You can also use the global helper method (see below).
> **WARNING**: Do not create new instances of the Snowflake generator (as this could cause collisions), always use the Snowflake singleton from the container.

You can generate a Snowflake by resolving the service out of the container and calling its `id` method:
Since this is a little cumbersome, the package also registers a global `snowflake()` helper method that you can use anywhere.
You can generate a Snowflake by resolving the singleton from the container and calling its `id` method:

```php
<?php
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` macro for convenience:

declare(strict_types=1);
```php
use CalebDW\Laraflake\Facades\Snowflake;
use Illuminate\Support\Str;

resolve('snowflake')->id(); // (string) "5585066784854016"
snowflake()->id(); // (string) "5585066784854016"
Snowflake::id(); // (string) "5585066784854016"
snowflake()->id(); // (string) "5585066784854016"
Snowflake::id(); // (string) "5585066784854016"
Str::snowflakeId(); // (string) "5585066784854016"
```

## Databases

If you want to use Snowflakes in your database e.g. for primary and foreign keys, then you'll need to perform a couple of steps.

First, modify your migrations so that they use the Snowflake migration methods e.g.

```php
<?php
### Eloquent Integration

declare(strict_types=1);
#### Migrations

// Before
$table->id();
$table->foreignId('user_id');
$table->foreignIdFor(User::class);

// After
$table->snowflake()->primary();
$table->foreignSnowflake('user_id');
$table->foreignSnowflakeFor(User::class);
```
This package provides a set of migration macros to make it easier to work with Snowflakes in your database schema.

Here's an example:

```php
<?php

return new class extends Migration
{
public function up()
public function up(): void
{
Schema::create('posts', function(Blueprint $table) {
Schema::create('comments', function(Blueprint $table) {
$table->snowflake()->primary();
$table->foreignSnowflake('user_id')->constrained()->cascadeOnDelete();
$table->string('title', 100);
$table->timestamps();
$table->foreignSnowflakeFor(Post::class)->constrained();
});
}
}
```

Next, if you're using Eloquent, add the package's `HasSnowflakes` trait to your Eloquent models:

```php
<?php
#### Models

declare(strict_types=1);
Next, add the package's `HasSnowflakes` trait to your Eloquent models:

```php
namespace App\Models;

use CalebDW\Laraflake\Concerns\HasSnowflakes;
Expand All @@ -143,15 +111,35 @@ class Post extends Model
}
```

Finally, configure the model's `$casts` array to use the package's `AsSnowflake` for all Snowflake attributes.
This cast automatically handles conversion from the database integer to a string representation in the application.
It also ensures that languages which do not support 64-bit integers (such as JavaScript), will not truncate the Snowflake.
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).

By default, the trait assumes that the model's primary key is a Snowflake.
If you have other unique columns that should be treated as Snowflakes, you can override the `uniqueIds` method to specify them:

```php
<?php

declare(strict_types=1);
namespace App\Models;

use CalebDW\Laraflake\Concerns\HasSnowflakes;

class Post extends Model
{
use HasSnowflakes;

/** @inheritDoc */
public function uniqueIds(): array
{
return [$this->getKeyName(), 'slug'];
}
}
```

If necessary, you can explicitly cast the model's Snowflake columns using the `AsSnowflake` cast:

```php
namespace App\Models;

use CalebDW\Laraflake\Casts\AsSnowflake;
Expand All @@ -164,11 +152,63 @@ class Post extends Model
protected $casts = [
'id' => AsSnowflake::class,
'user_id' => AsSnowflake::class,
'title' => 'string',
];
}
```


### Validation

If you need to validate Snowflakes in your application, you can use the `Snowflake` rule or the `Rule` macro provided by this package:

```php
use CalebDW\Laraflake\Rules\Snowflake;
use Illuminate\Validation\Rule;

$request->validate([
'id' => ['required', new Snowflake()],
'user_id' => ['required', Rule::snowflake()],
]);
```

You can also just use the `Str` macro to check if a value is a valid Snowflake:

```php
use Illuminate\Support\Str;

Str::isSnowflake('5585066784854016'); // (bool) true
```

### Sequence Resolver

The sequence resolver is responsible for generating the sequence component of the Snowflake
to ensure that numbers generated on the same machine within the same millisecond are unique.

By default, if the application has a cache, then it uses the `LaravelSequenceResolver`
which uses the Laravel cache to store the last sequence number.

If the application does not have a cache, then it uses the `RandomSequenceResolver` which
has no dependencies **but is not concurrency-safe**.

You can override the sequence resolver by binding your own implementation in a service provider:

```php
use Godruoyi\Snowflake\SequenceResolver;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(SequenceResolver::class, function() {
return new MySequenceResolver();
});
}
}
```

Please see [godruoyi/php-snowflake](https://github.com/godruoyi/php-snowflake) for more information on the available sequence resolvers and their dependencies.

## Contributing

Thank you for considering contributing! You can read the contribution guide [here](CONTRIBUTING.md).
Expand All @@ -180,3 +220,4 @@ Laraflake is open-sourced software licensed under the [MIT license](LICENSE).
## Acknowledgements

Derived from [caneara/snowflake](https://github.com/caneara/snowflake) which is no longer maintained.
The actual Snowflake generation is handled by the excellent [godruoyi/php-snowflake](https://github.com/godruoyi/php-snowflake) library.

0 comments on commit 7e08010

Please sign in to comment.