diff --git a/src/BinaryUtils.php b/src/BinaryUtils.php new file mode 100644 index 0000000..2597408 --- /dev/null +++ b/src/BinaryUtils.php @@ -0,0 +1,34 @@ +nodeProvider = $this->buildNodeProvider(); $this->randomGenerator = $this->buildRandomGenerator(); $this->timeConverter = $this->buildTimeConverter(); - $this->timeProvider = new SystemTimeProvider(); + $this->setTimeProvider(new SystemTimeProvider()); } public function getBuilder() @@ -102,6 +106,11 @@ class FeatureSet return $this->randomGenerator; } + public function getTimeGenerator() + { + return $this->timeGenerator; + } + public function getTimeConverter() { return $this->timeConverter; @@ -112,6 +121,12 @@ class FeatureSet return $this->timeProvider; } + public function setTimeProvider(TimeProviderInterface $timeProvider) + { + $this->timeProvider = $timeProvider; + $this->timeGenerator = $this->buildTimeGenerator(); + } + protected function buildCodec($useGuids = false) { if ($useGuids) { @@ -147,6 +162,11 @@ class FeatureSet return (new RandomGeneratorFactory())->getGenerator(); } + protected function buildTimeGenerator() + { + return (new TimeGeneratorFactory($this))->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..9664566 --- /dev/null +++ b/src/Generator/DefaultTimeGenerator.php @@ -0,0 +1,98 @@ + + * @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\BinaryUtils; +use Ramsey\Uuid\Converter\TimeConverterInterface; +use Ramsey\Uuid\Provider\NodeProviderInterface; +use Ramsey\Uuid\Provider\TimeProviderInterface; + +class DefaultTimeGenerator implements TimeGeneratorInterface +{ + /** + * @var NodeProviderInterface + */ + private $nodeProvider; + + /** + * @var TimeConverterInterface + */ + private $timeConverter; + + /** + * @var TimeProviderInterface + */ + private $timeProvider; + + public function __construct( + NodeProviderInterface $nodeProvider, + TimeConverterInterface $timeConverter, + TimeProviderInterface $timeProvider + ) { + $this->nodeProvider = $nodeProvider; + $this->timeConverter = $timeConverter; + $this->timeProvider = $timeProvider; + } + + public function generate($node = null, $clockSeq = null) + { + $node = $this->getValidNode($node); + + 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 = BinaryUtils::applyVersion($uuidTime['hi'], 1); + $clockSeqHi = BinaryUtils::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) + { + 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)); + } +} diff --git a/src/Generator/PeclUuidRandomGenerator.php b/src/Generator/PeclUuidRandomGenerator.php new file mode 100644 index 0000000..0c87df0 --- /dev/null +++ b/src/Generator/PeclUuidRandomGenerator.php @@ -0,0 +1,25 @@ + + * @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 PeclUuidRandomGenerator implements RandomGeneratorInterface +{ + public function generate($length) + { + $uuid = uuid_create(UUID_TYPE_RANDOM); + + return uuid_parse($uuid); + } +} diff --git a/src/Generator/PeclUuidTimeGenerator.php b/src/Generator/PeclUuidTimeGenerator.php new file mode 100644 index 0000000..eafec44 --- /dev/null +++ b/src/Generator/PeclUuidTimeGenerator.php @@ -0,0 +1,25 @@ + + * @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 PeclUuidTimeGenerator implements TimeGeneratorInterface +{ + public function generate($node = null, $clockSeq = null) + { + $uuid = uuid_create(UUID_TYPE_TIME); + + return uuid_parse($uuid); + } +} diff --git a/src/Generator/TimeGeneratorFactory.php b/src/Generator/TimeGeneratorFactory.php new file mode 100644 index 0000000..c2ba9bf --- /dev/null +++ b/src/Generator/TimeGeneratorFactory.php @@ -0,0 +1,39 @@ + + * @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\FeatureSet; + +class TimeGeneratorFactory +{ + /** + * @var FeatureSet + */ + private $featureSet; + + public function __construct(FeatureSet $featureSet) + { + $this->featureSet = $featureSet; + } + + public function getGenerator() + { + return new DefaultTimeGenerator( + $this->featureSet->getNodeProvider(), + $this->featureSet->getTimeConverter(), + $this->featureSet->getTimeProvider() + ); + } +} diff --git a/src/Generator/TimeGeneratorInterface.php b/src/Generator/TimeGeneratorInterface.php new file mode 100644 index 0000000..e47a246 --- /dev/null +++ b/src/Generator/TimeGeneratorInterface.php @@ -0,0 +1,34 @@ + + * @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; + +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 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($node = null, $clockSeq = null); +} diff --git a/src/UuidFactory.php b/src/UuidFactory.php index b7de729..b0121ef 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 @@ -69,7 +75,7 @@ class UuidFactory implements UuidFactoryInterface /** * Create a new a instance - * + * @param FeatureSet $features */ public function __construct(FeatureSet $features = null) { @@ -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; @@ -104,14 +121,9 @@ class UuidFactory implements UuidFactoryInterface return $this->timeConverter; } - public function setTimeConverter(TimeConverterInterface $converter) + public function getTimeProvider() { - $this->timeConverter = $converter; - } - - public function setTimeProvider(TimeProviderInterface $provider) - { - $this->timeProvider = $provider; + return $this->timeProvider; } public function setRandomGenerator(RandomGeneratorInterface $generator) @@ -119,11 +131,6 @@ class UuidFactory implements UuidFactoryInterface $this->randomGenerator = $generator; } - public function setNodeProvider(NodeProviderInterface $provider) - { - $this->nodeProvider = $provider; - } - public function setNumberConverter(NumberConverterInterface $converter) { $this->numberConverter = $converter; @@ -184,31 +191,10 @@ class UuidFactory implements UuidFactoryInterface */ public function uuid1($node = null, $clockSeq = null) { - $node = $this->getValidNode($node); + $bytes = $this->timeGenerator->generate($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,29 +246,6 @@ class UuidFactory implements UuidFactoryInterface return $this->uuidBuilder->build($this->codec, $fields); } - protected function applyVariant($clockSeqHi) - { - // Set the variant to RFC 4122 - $clockSeqHi = $clockSeqHi & 0x3f; - $clockSeqHi &= ~(0xc0); - $clockSeqHi |= 0x80; - - return $clockSeqHi; - } - - /** - * @param string $timeHi - * @param integer $version - */ - protected function applyVersion($timeHi, $version) - { - $timeHi = hexdec($timeHi) & 0x0fff; - $timeHi &= ~(0xf000); - $timeHi |= $version << 12; - - return $timeHi; - } - /** * @param string $name * @param integer $version @@ -309,8 +272,8 @@ class UuidFactory implements UuidFactoryInterface */ protected function uuidFromHashedName($hash, $version) { - $timeHi = $this->applyVersion(substr($hash, 12, 4), $version); - $clockSeqHi = $this->applyVariant(hexdec(substr($hash, 16, 2))); + $timeHi = BinaryUtils::applyVersion(substr($hash, 12, 4), $version); + $clockSeqHi = BinaryUtils::applyVariant(hexdec(substr($hash, 16, 2))); $fields = array( 'time_low' => substr($hash, 0, 8), @@ -323,22 +286,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)); - } } diff --git a/tests/UuidTest.php b/tests/UuidTest.php index 4d5109b..9eb170f 100644 --- a/tests/UuidTest.php +++ b/tests/UuidTest.php @@ -937,8 +937,11 @@ class UuidTest extends TestCase 'dsttime' => 0, )); + $featureSet = new FeatureSet(); + $featureSet->setTimeProvider($timeOfDay); + // For usec = 277885 - Uuid::getFactory()->setTimeProvider($timeOfDay); + Uuid::setFactory(new UuidFactory($featureSet)); $uuidA = Uuid::uuid1(0x00007ffffffe, 0x1669); $this->assertEquals('c4dbe7e2-097f-11e2-9669-00007ffffffe', (string) $uuidA); @@ -970,7 +973,6 @@ class UuidTest extends TestCase public function testCalculateUuidTimeForce32BitPath() { $this->skipIfNoMoontoastMath(); - Uuid::setFactory(new UuidFactory(new FeatureSet(false, true))); $timeOfDay = new FixedTimeProvider(array( 'sec' => 1348845514, @@ -979,8 +981,12 @@ class UuidTest extends TestCase 'dsttime' => 0, )); + $featureSet = new FeatureSet(false, true); + $featureSet->setTimeProvider($timeOfDay); + + Uuid::setFactory(new UuidFactory($featureSet)); + // For usec = 277885 - Uuid::getFactory()->setTimeProvider($timeOfDay); $uuidA = Uuid::uuid1(0x00007ffffffe, 0x1669); $this->assertEquals('c4dbe7e2-097f-11e2-9669-00007ffffffe', (string) $uuidA); @@ -1021,7 +1027,10 @@ class UuidTest extends TestCase 'dsttime' => 0, )); - Uuid::getFactory()->setTimeProvider($timeOfDay); + $featureSet = new FeatureSet(); + $featureSet->setTimeProvider($timeOfDay); + + Uuid::setFactory(new UuidFactory($featureSet)); $uuidA = Uuid::uuid1(0x00007ffffffe, 0x1669); $this->assertEquals('ff9785f6-ffff-1fff-9669-00007ffffffe', (string) $uuidA); @@ -1037,7 +1046,9 @@ class UuidTest extends TestCase 'dsttime' => 0, )); - Uuid::getFactory()->setTimeProvider($timeOfDay); + $featureSet->setTimeProvider($timeOfDay); + + Uuid::setFactory(new UuidFactory($featureSet)); $uuidB = Uuid::uuid1(0x00007ffffffe, 0x1669); $this->assertEquals('00000000-0000-1000-9669-00007ffffffe', (string) $uuidB); @@ -1056,7 +1067,7 @@ class UuidTest extends TestCase $this->skipIfNoMoontoastMath(); $this->skip64BitTest(); - Uuid::setFactory(new UuidFactory(new FeatureSet(false, true))); + $featureSet = new FeatureSet(false, true); // 5235-03-31T21:20:59+00:00 $timeOfDay = new FixedTimeProvider(array( @@ -1066,7 +1077,10 @@ class UuidTest extends TestCase 'dsttime' => 0, )); - Uuid::getFactory()->setTimeProvider($timeOfDay); + $featureSet->setTimeProvider($timeOfDay); + + Uuid::setFactory(new UuidFactory($featureSet)); + $uuidA = Uuid::uuid1(0x00007ffffffe, 0x1669); $this->assertEquals('ff9785f6-ffff-1fff-9669-00007ffffffe', (string) $uuidA); @@ -1082,7 +1096,10 @@ class UuidTest extends TestCase 'dsttime' => 0, )); - Uuid::getFactory()->setTimeProvider($timeOfDay); + $featureSet->setTimeProvider($timeOfDay); + + Uuid::setFactory(new UuidFactory($featureSet)); + $uuidB = Uuid::uuid1(0x00007ffffffe, 0x1669); $this->assertEquals('00000000-0000-1000-9669-00007ffffffe', (string) $uuidB); @@ -1096,7 +1113,6 @@ class UuidTest extends TestCase public function testCalculateUuidTimeUpperLowerBounds32Bit() { $this->skipIfNoMoontoastMath(); - Uuid::setFactory(new UuidFactory(new FeatureSet(false, true))); // 2038-01-19T03:14:07+00:00 $timeOfDay = new FixedTimeProvider(array( @@ -1106,7 +1122,11 @@ class UuidTest extends TestCase 'dsttime' => 0, )); - Uuid::getFactory()->setTimeProvider($timeOfDay); + $featureSet = new FeatureSet(false, true); + $featureSet->setTimeProvider($timeOfDay); + + Uuid::setFactory(new UuidFactory($featureSet)); + $uuidA = Uuid::uuid1(0x00007ffffffe, 0x1669); $this->assertEquals('13813ff6-6912-11fe-9669-00007ffffffe', (string) $uuidA); @@ -1122,7 +1142,10 @@ class UuidTest extends TestCase 'dsttime' => 0, )); - Uuid::getFactory()->setTimeProvider($timeOfDay); + $featureSet->setTimeProvider($timeOfDay); + + Uuid::setFactory(new UuidFactory($featureSet)); + $uuidB = Uuid::uuid1(0x00007ffffffe, 0x1669); $this->assertEquals('1419d680-d292-1165-9669-00007ffffffe', (string) $uuidB); @@ -1148,7 +1171,11 @@ class UuidTest extends TestCase 'dsttime' => 0, )); - Uuid::getFactory()->setTimeProvider($timeOfDay); + $featureSet = new FeatureSet(); + $featureSet->setTimeProvider($timeOfDay); + + Uuid::setFactory(new UuidFactory($featureSet)); + $uuidA = Uuid::uuid1(0x00007ffffffe, 0x1669); $this->assertEquals('13813ff6-6912-11fe-9669-00007ffffffe', (string) $uuidA); @@ -1164,7 +1191,10 @@ class UuidTest extends TestCase 'dsttime' => 0, )); - Uuid::getFactory()->setTimeProvider($timeOfDay); + $featureSet->setTimeProvider($timeOfDay); + + Uuid::setFactory(new UuidFactory($featureSet)); + ; $uuidB = Uuid::uuid1(0x00007ffffffe, 0x1669); $this->assertEquals('1419d680-d292-1165-9669-00007ffffffe', (string) $uuidB); @@ -1185,9 +1215,6 @@ class UuidTest extends TestCase $currentTime = strtotime('2012-12-11T00:00:00+00:00'); $endTime = $currentTime + 3600; - $factory = new UuidFactory(); - $smallIntFactory = new UuidFactory(new FeatureSet(false, true)); - $timeOfDay = new FixedTimeProvider(array( 'sec' => $currentTime, 'usec' => 0, @@ -1195,8 +1222,15 @@ class UuidTest extends TestCase 'dsttime' => 0, )); - $factory->setTimeProvider($timeOfDay); - $smallIntFactory->setTimeProvider($timeOfDay); + $smallIntFeatureSet = new FeatureSet(false, true); + $smallIntFeatureSet->setTimeProvider($timeOfDay); + + $smallIntFactory = new UuidFactory($smallIntFeatureSet); + + $featureSet = new FeatureSet(); + $featureSet->setTimeProvider($timeOfDay); + + $factory = new UuidFactory($featureSet); while ($currentTime <= $endTime) {