123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- <?php
- /*
- * This file is part of the godruoyi/php-snowflake.
- *
- * (c) Godruoyi <g@godruoyi.com>
- *
- * This source file is subject to the MIT license that is bundled.
- */
- namespace Godruoyi\Snowflake;
- class Snowflake
- {
- const MAX_TIMESTAMP_LENGTH = 41;
- const MAX_DATACENTER_LENGTH = 5;
- const MAX_WORKID_LENGTH = 5;
- const MAX_SEQUENCE_LENGTH = 12;
- const MAX_FIRST_LENGTH = 1;
- /**
- * The data center id.
- *
- * @var int
- */
- protected $datacenter;
- /**
- * The worker id.
- *
- * @var int
- */
- protected $workerid;
- /**
- * The Sequence Resolver instance.
- *
- * @var \Godruoyi\Snowflake\SequenceResolver|null
- */
- protected $sequence;
- /**
- * The start timestamp.
- *
- * @var int
- */
- protected $startTime;
- /**
- * Default sequence resolver.
- *
- * @var \Godruoyi\Snowflake\SequenceResolver|null
- */
- protected $defaultSequenceResolver;
- /**
- * Build Snowflake Instance.
- *
- * @param int $datacenter
- * @param int $workerid
- */
- public function __construct(int $datacenter = null, int $workerid = null)
- {
- $maxDataCenter = -1 ^ (-1 << self::MAX_DATACENTER_LENGTH);
- $maxWorkId = -1 ^ (-1 << self::MAX_WORKID_LENGTH);
- // If not set datacenter or workid, we will set a default value to use.
- $this->datacenter = $datacenter > $maxDataCenter || $datacenter < 0 ? mt_rand(0, 31) : $datacenter;
- $this->workerid = $workerid > $maxWorkId || $workerid < 0 ? mt_rand(0, 31) : $workerid;
- }
- /**
- * Get snowflake id.
- *
- * @return int
- */
- public function id()
- {
- $currentTime = $this->getCurrentMicrotime();
- while (($sequence = $this->callResolver($currentTime)) > (-1 ^ (-1 << self::MAX_SEQUENCE_LENGTH))) {
- usleep(1);
- $currentTime = $this->getCurrentMicrotime();
- }
- $workerLeftMoveLength = self::MAX_SEQUENCE_LENGTH;
- $datacenterLeftMoveLength = self::MAX_WORKID_LENGTH + $workerLeftMoveLength;
- $timestampLeftMoveLength = self::MAX_DATACENTER_LENGTH + $datacenterLeftMoveLength;
- return (string) ((($currentTime - $this->getStartTimeStamp()) << $timestampLeftMoveLength)
- | ($this->datacenter << $datacenterLeftMoveLength)
- | ($this->workerid << $workerLeftMoveLength)
- | ($sequence));
- }
- /**
- * Parse snowflake id.
- *
- * @param string $id
- *
- * @return array
- */
- public function parseId(string $id, $transform = false): array
- {
- $id = decbin($id);
- $data = [
- 'timestamp' => substr($id, 0, -22),
- 'sequence' => substr($id, -12),
- 'workerid' => substr($id, -17, 5),
- 'datacenter' => substr($id, -22, 5),
- ];
- return $transform ? array_map(function ($value) {
- return bindec($value);
- }, $data) : $data;
- }
- /**
- * Get current microtime timestamp.
- *
- * @return int
- */
- public function getCurrentMicrotime()
- {
- return floor(microtime(true) * 1000) | 0;
- }
- /**
- * Set start time (millisecond).
- *
- * @param int $startTime
- */
- public function setStartTimeStamp(int $startTime)
- {
- $missTime = $this->getCurrentMicrotime() - $startTime;
- if ($missTime < 0 || $missTime > ($maxTimeDiff = ((1 << self::MAX_TIMESTAMP_LENGTH) - 1))) {
- throw new \Exception('The starttime cannot be greater than current time and the maximum time difference is '.$maxTimeDiff);
- }
- $this->startTime = $startTime;
- return $this;
- }
- /**
- * Get start timestamp (millisecond), If not set default to 2019-08-08 08:08:08.
- *
- * @return int
- */
- public function getStartTimeStamp()
- {
- if ($this->startTime > 0) {
- return $this->startTime;
- }
- // We set a default start time if you not set.
- $defaultTime = '2019-08-08 08:08:08';
- return strtotime($defaultTime) * 1000;
- }
- /**
- * Set Sequence Resolver.
- *
- * @param SequenceResolver|callable $sequence
- */
- public function setSequenceResolver($sequence)
- {
- $this->sequence = $sequence;
- return $this;
- }
- /**
- * Get Sequence Resolver.
- *
- * @return \Godruoyi\Snowflake\SequenceResolver|callable|null
- */
- public function getSequenceResolver()
- {
- return $this->sequence;
- }
- /**
- * Get Default Sequence Resolver.
- *
- * @return \Godruoyi\Snowflake\SequenceResolver
- */
- public function getDefaultSequenceResolver(): SequenceResolver
- {
- return $this->defaultSequenceResolver ?: $this->defaultSequenceResolver = new RandomSequenceResolver();
- }
- /**
- * Call resolver.
- *
- * @param callable|\Godruoyi\Snowflake\SequenceResolver $resolver
- * @param int $maxSequence
- *
- * @return int
- */
- protected function callResolver($currentTime)
- {
- $resolver = $this->getSequenceResolver();
- if (is_callable($resolver)) {
- return $resolver($currentTime);
- }
- return is_null($resolver) || !($resolver instanceof SequenceResolver)
- ? $this->getDefaultSequenceResolver()->sequence($currentTime)
- : $resolver->sequence($currentTime);
- }
- }
|