diff --git a/src/FeatureSet.php b/src/FeatureSet.php index 04b5e8d..1fd1157 100644 --- a/src/FeatureSet.php +++ b/src/FeatureSet.php @@ -28,6 +28,7 @@ use Ramsey\Uuid\Codec\StringCodec; use Ramsey\Uuid\Codec\GuidStringCodec; use Ramsey\Uuid\Builder\DegradedUuidBuilder; use Ramsey\Uuid\Generator\RandomGeneratorFactory; +use Ramsey\Uuid\Generator\TimeGeneratorFactory; /** * Detects and exposes available features in current environment (32 or 64 bit, available dependencies...) @@ -54,6 +55,8 @@ class FeatureSet private $randomGenerator; + private $timeGenerator; + private $timeConverter; private $timeProvider; @@ -73,6 +76,7 @@ class FeatureSet $this->codec = $this->buildCodec($useGuids); $this->nodeProvider = $this->buildNodeProvider(); $this->randomGenerator = $this->buildRandomGenerator(); + $this->timeGenerator = $this->buildTimeGenerator(); $this->timeConverter = $this->buildTimeConverter(); $this->timeProvider = new SystemTimeProvider(); } @@ -102,6 +106,11 @@ class FeatureSet return $this->randomGenerator; } + public function getTimeGenerator() + { + return $this->timeGenerator; + } + public function getTimeConverter() { return $this->timeConverter; @@ -147,6 +156,11 @@ class FeatureSet return (new RandomGeneratorFactory())->getGenerator(); } + protected function buildTimeGenerator() + { + return (new TimeGeneratorFactory())->getGenerator(); + } + protected function buildTimeConverter() { if ($this->is64BitSystem()) { diff --git a/src/Generator/DefaultTimeGenerator.php b/src/Generator/DefaultTimeGenerator.php new file mode 100644 index 0000000..2f1da7f --- /dev/null +++ b/src/Generator/DefaultTimeGenerator.php @@ -0,0 +1,70 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +use Ramsey\Uuid\UuidFactory; + +class DefaultTimeGenerator implements TimeGeneratorInterface +{ + public function generate(UuidFactory $factory, $node = null, $clockSeq = null) + { + $node = $this->getValidNode($node, $factory); + + if ($clockSeq === null) { + // Not using "stable storage"; see RFC 4122, Section 4.2.1.1 + $clockSeq = mt_rand(0, 1 << 14); + } + + // Create a 60-bit time value as a count of 100-nanosecond intervals + // since 00:00:00.00, 15 October 1582 + $timeOfDay = $factory->getTimeProvider()->currentTime(); + $uuidTime = $factory->getTimeConverter()->calculateTime($timeOfDay['sec'], $timeOfDay['usec']); + + $timeHi = $factory->applyVersion($uuidTime['hi'], 1); + $clockSeqHi = $factory->applyVariant($clockSeq >> 8); + + $hex = vsprintf( + '%08s%04s%04s%02s%02s%012s', + array( + $uuidTime['low'], + $uuidTime['mid'], + sprintf('%04x', $timeHi), + sprintf('%02x', $clockSeqHi), + sprintf('%02x', $clockSeq & 0xff), + $node, + ) + ); + + return hex2bin($hex); + } + + protected function getValidNode($node, UuidFactory $factory) + { + if ($node === null) { + $node = $factory->getNodeProvider()->getNode(); + } + + // Convert the node to hex, if it is still an integer + if (is_int($node)) { + $node = sprintf('%012x', $node); + } + + if (! ctype_xdigit($node) || strlen($node) > 12) { + throw new \InvalidArgumentException('Invalid node value'); + } + + return strtolower(sprintf('%012s', $node)); + } +} diff --git a/src/Generator/TimeGeneratorFactory.php b/src/Generator/TimeGeneratorFactory.php new file mode 100644 index 0000000..5b885d3 --- /dev/null +++ b/src/Generator/TimeGeneratorFactory.php @@ -0,0 +1,23 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +class TimeGeneratorFactory +{ + public static function getGenerator() + { + return new DefaultTimeGenerator(); + } +} diff --git a/src/Generator/TimeGeneratorInterface.php b/src/Generator/TimeGeneratorInterface.php new file mode 100644 index 0000000..e912ca1 --- /dev/null +++ b/src/Generator/TimeGeneratorInterface.php @@ -0,0 +1,37 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link https://benramsey.com/projects/ramsey-uuid/ Documentation + * @link https://packagist.org/packages/ramsey/uuid Packagist + * @link https://github.com/ramsey/uuid GitHub + */ + +namespace Ramsey\Uuid\Generator; + +use Ramsey\Uuid\UuidFactory; + +interface TimeGeneratorInterface +{ + /** + * Generate a version 1 UUID from a host ID, sequence number, and the current time. + * + * If $node is not given, we will attempt to obtain the local hardware + * address. If $clockSeq is given, it is used as the sequence number; + * otherwise a random 14-bit sequence number is chosen. + * + * @param UuidFactory $factory + * @param int|string $node A 48-bit number representing the hardware address + * This number may be represented as an integer or a hexadecimal string. + * @param int $clockSeq A 14-bit number used to help avoid duplicates that + * could arise when the clock is set backwards in time or if the node ID + * changes. + * @return string A 16-byte binary string representing a UUID + */ + public function generate(UuidFactory $factory, $node = null, $clockSeq = null); +} diff --git a/src/UuidFactory.php b/src/UuidFactory.php index b7de729..0720dfc 100644 --- a/src/UuidFactory.php +++ b/src/UuidFactory.php @@ -20,6 +20,7 @@ use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Provider\TimeProviderInterface; use Ramsey\Uuid\Generator\RandomGeneratorInterface; +use Ramsey\Uuid\Generator\TimeGeneratorInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Builder\UuidBuilderInterface; @@ -49,6 +50,11 @@ class UuidFactory implements UuidFactoryInterface */ private $randomGenerator = null; + /** + * @var TimeGeneratorInterface + */ + private $timeGenerator = null; + /** * * @var TimeConverterInterface @@ -79,6 +85,7 @@ class UuidFactory implements UuidFactoryInterface $this->nodeProvider = $features->getNodeProvider(); $this->numberConverter = $features->getNumberConverter(); $this->randomGenerator = $features->getRandomGenerator(); + $this->timeGenerator = $features->getTimeGenerator(); $this->timeConverter = $features->getTimeConverter(); $this->timeProvider = $features->getTimeProvider(); $this->uuidBuilder = $features->getBuilder(); @@ -89,11 +96,21 @@ class UuidFactory implements UuidFactoryInterface return $this->codec; } + public function getNodeProvider() + { + return $this->nodeProvider; + } + public function getRandomGenerator() { return $this->randomGenerator; } + public function getTimeGenerator() + { + return $this->timeGenerator; + } + public function getNumberConverter() { return $this->numberConverter; @@ -109,6 +126,11 @@ class UuidFactory implements UuidFactoryInterface $this->timeConverter = $converter; } + public function getTimeProvider() + { + return $this->timeProvider; + } + public function setTimeProvider(TimeProviderInterface $provider) { $this->timeProvider = $provider; @@ -119,6 +141,11 @@ class UuidFactory implements UuidFactoryInterface $this->randomGenerator = $generator; } + public function setTimeGenerator(TimeGeneratorInterface $generator) + { + $this->timeGenerator = $generator; + } + public function setNodeProvider(NodeProviderInterface $provider) { $this->nodeProvider = $provider; @@ -184,31 +211,10 @@ class UuidFactory implements UuidFactoryInterface */ public function uuid1($node = null, $clockSeq = null) { - $node = $this->getValidNode($node); + $bytes = $this->timeGenerator->generate($this, $node, $clockSeq); + $hex = bin2hex($bytes); - if ($clockSeq === null) { - // Not using "stable storage"; see RFC 4122, Section 4.2.1.1 - $clockSeq = mt_rand(0, 1 << 14); - } - - // Create a 60-bit time value as a count of 100-nanosecond intervals - // since 00:00:00.00, 15 October 1582 - $timeOfDay = $this->timeProvider->currentTime(); - $uuidTime = $this->timeConverter->calculateTime($timeOfDay['sec'], $timeOfDay['usec']); - - $timeHi = $this->applyVersion($uuidTime['hi'], 1); - $clockSeqHi = $this->applyVariant($clockSeq >> 8); - - $fields = array( - 'time_low' => $uuidTime['low'], - 'time_mid' => $uuidTime['mid'], - 'time_hi_and_version' => sprintf('%04x', $timeHi), - 'clock_seq_hi_and_reserved' => sprintf('%02x', $clockSeqHi), - 'clock_seq_low' => sprintf('%02x', $clockSeq & 0xff), - 'node' => $node, - ); - - return $this->uuid($fields); + return $this->uuidFromHashedName($hex, 1); } @@ -260,7 +266,7 @@ class UuidFactory implements UuidFactoryInterface return $this->uuidBuilder->build($this->codec, $fields); } - protected function applyVariant($clockSeqHi) + public function applyVariant($clockSeqHi) { // Set the variant to RFC 4122 $clockSeqHi = $clockSeqHi & 0x3f; @@ -274,7 +280,7 @@ class UuidFactory implements UuidFactoryInterface * @param string $timeHi * @param integer $version */ - protected function applyVersion($timeHi, $version) + public function applyVersion($timeHi, $version) { $timeHi = hexdec($timeHi) & 0x0fff; $timeHi &= ~(0xf000); @@ -323,22 +329,4 @@ class UuidFactory implements UuidFactoryInterface return $this->uuid($fields); } - - protected function getValidNode($node) - { - if ($node === null) { - $node = $this->nodeProvider->getNode(); - } - - // Convert the node to hex, if it is still an integer - if (is_int($node)) { - $node = sprintf('%012x', $node); - } - - if (! ctype_xdigit($node) || strlen($node) > 12) { - throw new \InvalidArgumentException('Invalid node value'); - } - - return strtolower(sprintf('%012s', $node)); - } }