c4ys/doctrine-snowflakes-bundle
Installation:
composer require c4ys/doctrine-snowflakes-bundle
Add to config/bundles.php (Symfony):
return [
// ...
KaiGrassnick\DoctrineSnowflakesBundle\DoctrineSnowflakesBundle::class => ['all' => true],
];
Entity Configuration:
Add the @ORM\CustomIdGenerator annotation to your entity’s ID field:
use KaiGrassnick\DoctrineSnowflakesBundle\Generator\SnowflakeGenerator;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
*/
class MyEntity {
/**
* @ORM\Id()
* @ORM\GeneratedValue(strategy="CUSTOM")
* @ORM\Column(type="bigint")
* @ORM\CustomIdGenerator(class=SnowflakeGenerator::class)
*/
private string $id;
}
First Use Case:
Run migrations (php bin/console doctrine:migrations:diff) and generate IDs:
$entity = new MyEntity();
$entityManager->persist($entity); // Auto-generates a snowflake ID
$entityManager->flush();
Snowflake IDs in Services: Use the generated IDs in business logic (e.g., API responses, caching keys):
public function getEntityData(MyEntity $entity): array {
return [
'id' => $entity->getId(), // Snowflake ID (e.g., "1234567890123456789")
'name' => $entity->getName(),
];
}
Bulk Operations: Snowflakes are deterministic per machine/process. For distributed systems:
worker_id in config/packages/doctrine_snowflakes.yaml:
kai_grassnick_doctrine_snowflakes:
worker_id: %env(int:WORKER_ID, 1)%
Migrations:
strategy="CUSTOM" for all new entities needing snowflakes.snowflake_id column (if needed) and backfill via a custom migration:
public function up(SchemaManagerInterface $sm, \Closure $rollback) {
$this->addSql('ALTER TABLE my_entity ADD snowflake_id BIGINT NOT NULL');
$this->addSql('UPDATE my_entity SET snowflake_id = :snowflake', [
'snowflake' => (new SnowflakeGenerator())->generate(),
]);
}
Testing: Mock the generator in unit tests:
$generator = $this->createMock(SnowflakeGenerator::class);
$generator->method('generate')->willReturn('1234567890123456789');
$entityManager->getConnection()->getConfiguration()->addCustomStringFunction(
'SNOWFLAKE',
$generator
);
Clock Skew: Snowflakes rely on a timestamp. If your server’s clock drifts (e.g., NTP misconfiguration), IDs may violate uniqueness or sorting. Mitigate with:
Worker ID Collisions:
worker_id is 1. In distributed environments, ensure each worker has a unique worker_id (e.g., via Docker/K8s labels or environment variables).flush().Database Constraints:
BIGINT as a primary key if the table is large. Use UNSIGNED BIGINT instead:
@ORM\Column(type="bigint", options={"unsigned": true})
Doctrine Cache: Clear the metadata cache after adding the bundle:
php bin/console cache:clear
ID Generation Issues:
$generator = new SnowflakeGenerator();
var_dump($generator->generate()); // Debug raw output
kai_grassnick_doctrine_snowflakes:
debug: true # Logs generation details
Performance:
Custom Snowflake Logic: Extend the generator:
class CustomSnowflakeGenerator extends SnowflakeGenerator {
protected function getWorkerId(): int {
return hash('crc32', $_SERVER['HOSTNAME']) % 1024; // Example: Hash hostname
}
}
Update the entity annotation:
@ORM\CustomIdGenerator(class="App\Generator\CustomSnowflakeGenerator")
Hybrid IDs: Combine snowflakes with UUIDs for external systems:
public function getExternalId(): string {
return 'SNOW_' . $this->id . '_UUID_' . $this->uuid;
}
Event Listeners: Trigger actions on snowflake generation:
$entityManager->getEventManager()->addEventListener(
\Doctrine\ORM\Events::prePersist,
function (LifecycleEventArgs $args) {
$entity = $args->getObject();
if ($entity instanceof MyEntity && empty($entity->getId())) {
// Custom logic post-generation
}
}
);
datacenter_id: 1 (10-bit, max 1024).worker_id: 1 (10-bit, max 1024).epoch: 2020-01-01 00:00:00 (adjust if needed).
Override in config/packages/doctrine_snowflakes.yaml:kai_grassnick_doctrine_snowflakes:
epoch: "2010-01-01 00:00:00"
datacenter_id: %env(int:DATACENTER_ID, 1)%
How can I help you explore Laravel packages today?