Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add generation of COMB UUID's #43

Merged
merged 2 commits into from
Mar 10, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/Console/Command/GenerateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
use Symfony\Component\Console\Output\OutputInterface;
use Rhumsaa\Uuid\Console\Exception;
use Rhumsaa\Uuid\Uuid;
use Rhumsaa\Uuid\Generator\CombGenerator;
use Rhumsaa\Uuid\Codec\GuidStringCodec;
use Rhumsaa\Uuid\FeatureSet;
use Rhumsaa\Uuid\UuidFactory;

/**
* Provides the console command to generate UUIDs
Expand Down Expand Up @@ -60,6 +64,18 @@ protected function configure()
InputOption::VALUE_REQUIRED,
'Generate count UUIDs instead of just a single one.',
1
)
->addOption(
'comb',
null,
InputOption::VALUE_NONE,
'For version 4 UUIDs, uses the COMB strategy to generate the random data.'
)
->addOption(
'guid',
'g',
InputOption::VALUE_NONE,
'Returns a GUID formatted UUID.'
);
}

Expand All @@ -82,6 +98,21 @@ protected function execute(InputInterface $input, OutputInterface $output)
)
);

if (((bool) $input->getOption('guid')) == true) {
$features = new FeatureSet(true);

Uuid::setFactory(new UuidFactory($features));
}

if (((bool) $input->getOption('comb')) === true) {
Uuid::getFactory()->setRandomGenerator(
new CombGenerator(
Uuid::getFactory()->getRandomGenerator(),
Uuid::getFactory()->getNumberConverter()
)
);
}

for ($i = 0; $i < $count; $i++) {
$uuids[] = $this->createUuid(
$input->getArgument('version'),
Expand Down
68 changes: 68 additions & 0 deletions src/Generator/CombGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace Rhumsaa\Uuid\Generator;

use Rhumsaa\Uuid\RandomGeneratorInterface;
use Rhumsaa\Uuid\Converter\NumberConverterInterface;

/**
* Generator to be used for COMB sequential UUID's.
*
* @author thibaud
*
*/
class CombGenerator implements RandomGeneratorInterface
{

private $randomGenerator;

private $converter;

private $timestampBytes;

/**
* @param RandomGeneratorInterface $generator RNG for the non-time part.
* @param NumberConverterInterface $numberConverter Instance of number converter.
*/
public function __construct(RandomGeneratorInterface $generator, NumberConverterInterface $numberConverter)
{
$this->converter = $numberConverter;
$this->randomGenerator = $generator;
$this->timestampBytes = 6;
}

/**
* (non-PHPdoc) @see \Rhumsaa\Uuid\RandomGeneratorInterface::generate()
*/
public function generate($length)
{
if ($length < $this->timestampBytes || $length < 0) {
throw new \InvalidArgumentException('Length must be a positive integer.');
}

$hash = '';

if ($this->timestampBytes > 0 && $length > $this->timestampBytes) {
$hash = $this->randomGenerator->generate($length - $this->timestampBytes);
}

$lsbTime = str_pad($this->converter->toHex($this->timestamp()), $this->timestampBytes * 2, '0', STR_PAD_LEFT);

if ($this->timestampBytes > 0 && strlen($lsbTime) > $this->timestampBytes * 2) {
$lsbTime = substr($lsbTime, 0 - ($this->timestampBytes * 2));
}

return hex2bin(str_pad(bin2hex($hash), $length - $this->timestampBytes, '0')) . hex2bin($lsbTime);
}

/**
* Returns current timestamp as integer, precise to 0.00001 seconds
* @return number
*/
private function timestamp()
{
$time = explode(' ', microtime(false));

return $time[1] . substr($time[0], 2, 5);
}
}
15 changes: 15 additions & 0 deletions src/UuidFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,21 @@ public function getCodec()
return $this->codec;
}

public function getRandomGenerator()
{
return $this->randomGenerator;
}

public function getNumberConverter()
{
return $this->numberConverter;
}

public function getTimeConverter()
{
return $this->timeConverter;
}

public function setTimeConverter(TimeConverterInterface $converter)
{
$this->timeConverter = $converter;
Expand Down
45 changes: 45 additions & 0 deletions tests/UuidTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Rhumsaa\Uuid\Provider\Time\SystemTimeProvider;
use Rhumsaa\Uuid\Provider\Time\FixedTimeProvider;
use Rhumsaa\Uuid\Generator\CombGenerator;

class UuidTest extends TestCase
{
Expand Down Expand Up @@ -785,6 +786,50 @@ public function testUuid4WithoutOpensslRandomPseudoBytes()
$this->assertEquals(4, $uuid->getVersion());
}

/**
* Tests that generated UUID's using COMB are sequential
* @return string
*/
public function testUuid4Comb()
{
$mock = $this->getMock('Rhumsaa\Uuid\RandomGeneratorInterface');
$mock->expects($this->any())
->method('generate')
->willReturnCallback(function ($length)
{
// Makes first fields of UUIDs equal
return str_pad('', $length, '0');
});

$factory = new UuidFactory();
$generator = new CombGenerator($mock, $factory->getNumberConverter());
$factory->setRandomGenerator($generator);

$previous = $factory->uuid4();

for ($i = 0; $i < 1000; $i ++) {
$uuid = $factory->uuid4();
$this->assertGreaterThan($previous->toString(), $uuid->toString());

$previous = $uuid;
}
}

/**
* Test that COMB UUID's have a version 4 flag
*/
public function testUuid4CombVersion()
{
$factory = new UuidFactory();
$generator = new CombGenerator(RandomGeneratorFactory::getGenerator(), $factory->getNumberConverter());

$factory->setRandomGenerator($generator);

$uuid = $factory->uuid4();

$this->assertEquals(4, $uuid->getVersion());
}

/**
* The "python.org" UUID is a known entity, so we're testing that this
* library generates a matching UUID for the same name.
Expand Down