diff --git a/.gitignore b/.gitignore index 5657f6e..cade4c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -vendor \ No newline at end of file +vendor +/.idea diff --git a/composer.lock b/composer.lock index 29ebc34..ccc08da 100644 --- a/composer.lock +++ b/composer.lock @@ -1,5 +1,9 @@ { - "hash": "bc20a38645558f91ae19a28e2e9950d4", + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" + ], + "hash": "66ced95c19f95960857667c11709849a", "packages": [ ], @@ -9,19 +13,18 @@ "version": "v1.1.0", "source": { "type": "git", - "url": "https://github.com/mikey179/vfsStream", - "reference": "v1.1.0" + "url": "https://github.com/mikey179/vfsStream.git", + "reference": "fc0fe8f4d0b527254a2dc45f0c265567c881d07e" }, "dist": { "type": "zip", - "url": "https://github.com/mikey179/vfsStream/zipball/v1.1.0", - "reference": "v1.1.0", + "url": "https://api.github.com/repos/mikey179/vfsStream/zipball/fc0fe8f4d0b527254a2dc45f0c265567c881d07e", + "reference": "fc0fe8f4d0b527254a2dc45f0c265567c881d07e", "shasum": "" }, "require": { "php": ">=5.3.0" }, - "time": "2012-08-25 05:49:29", "type": "library", "autoload": { "psr-0": { @@ -32,7 +35,8 @@ "license": [ "BSD" ], - "homepage": "http://vfs.bovigo.org/" + "homepage": "http://vfs.bovigo.org/", + "time": "2012-08-25 12:49:29" } ], "aliases": [ @@ -41,5 +45,11 @@ "minimum-stability": "stable", "stability-flags": [ + ], + "platform": { + "php": ">=5.3.2" + }, + "platform-dev": [ + ] } diff --git a/lib/PasswordLib/Uuid/UuidGenerator.php b/lib/PasswordLib/Uuid/UuidGenerator.php new file mode 100644 index 0000000..2d40f1c --- /dev/null +++ b/lib/PasswordLib/Uuid/UuidGenerator.php @@ -0,0 +1,330 @@ + + */ +class UuidGenerator { + + /** + * The integer in hexadecimal notation that denotes the version of UUIDs that + * are comprised of random numbers. + * + * @var int + */ + const UUID_VERSION_4 = 0x40; + + /** + * A list of integers that correspond to the indices where the hyphens (-) + * are expected to be present in a formatted UUID. + * + * Notice that the indices are zero-based. + * + * @var int[] + */ + protected static $separatorsIdxs = array(8, 12, 16, 20); + + /** + * The size of the UUID in bytes. + * + * @var int + */ + protected static $uuidByteSize = 16; + + /**#@+ + * The integers denoting the index of the byte to be manipulated. + * + * A big-endian byte representation is assumed. + * + * @var int + */ + + /** + * The index of the byte that indicates the variant of the UUID. + * + */ + protected static $variantByteIndex = 9; + + /** + * The index of the byte that indicates the version of the UUID. + * + */ + protected static $versionByteIndex = 7; + /**#@-*/ + + /** + * The singleton instance of this class. + * + * @var UuidGenerator + */ + private static $instance = null; + + /** + * The random numbers factory. + * + * @var Factory + */ + private $factory; + + /** + * The random number generator utilized by this UuidGenerator. + * + * @var Generator + */ + private $rng = null; + + /** + * Returns the singleton UuidGenerator for the current process. + * + * @return UuidGenerator The singleton instance of this class. + */ + public static function getInstance() { + if (static::$instance === null) { + static::$instance = new static(new Factory()); + } + + return static::$instance; + } + + /** + * Generates a new version 4 UUID utilizing a medium strength generator of + * the PasswordLib. + * + * If the {@link Generator} has yet to be instantiated, then it is constructed + * here. Through this tactic we achieve [lazy loading][1] of the generator. + * + * + * [1]: http://en.wikipedia.org/wiki/Lazy_loading + * "Lazy Loading | Wikipedia" + * + * @return string The new version 4 UUID. + */ + public function generateVersion4Uuid() { + if ($this->rng === null) { + $this->rng = $this->factory->getMediumStrengthGenerator(); + } + + $randomString = $this->rng->generate(static::$uuidByteSize); + $bytes = $this->getBytesOf($randomString); + $this->setVersionToUuid($bytes, static::UUID_VERSION_4); + $this->setVariantToUuid($bytes); + $hexString = $this->getHexadecimalOf($bytes); + + return $this->formatUuid($hexString); + } + + /** + * Returns the factory utilized by this UuidGenerator for the creation of + * random numbers generators. + * + * @return Factory The random numbers generator factory. + */ + public function getFactory() { + return $this->factory; + } + + /** + * Returns the random numbers generator utilized by this UuidGenerator. + * + * This is a medium strength {@link Generator}. In addition, it is instantiated + * only once during the first call to the {@link #generateVersion4Uuid} method. + * + * @return Generator + */ + public function getRandomNumberGenerator() { + return $this->rng; + } + + /** + * Creates a new UuidGenerator. + * + * The {@link Factory factory} is utilized by this UuidGenerator for the + * construction of a medium strength {@link Generator}. + * + * @param Factory $factory The factory of random number. + * @throws \InvalidArgumentException if `$factory` is null. + */ + protected function __construct(Factory $factory) { + if ($factory === null) { + throw new \InvalidArgumentException('No factory was provided.'); + } + $this->factory = $factory; + } + + /** + * Formats the hexadecimal representation of a UUID to the official form as + * described in [RFC 4122][1]. + * + * The algorithm used can be described by the following code: + * + * $uuid = substr($hexString, 0, 8) . '-' + * . substr($hexString, 8, 4) . '-' + * . substr($hexString, 12, 4) . '-' + * . substr($hexString, 16, 4) . '-' + * . substr($hexString, 20); + * + * + * [1]: http://tools.ietf.org/html/rfc4122 + * "RFC 4122: A Universally Unique IDentifier (UUID) URN Namespace" + * + * @param string $hexString The string containing the hexadecimal representation + * of the UUID. + * @return string The string representation of the UUID in the official + * format. + */ + protected function formatUuid($hexString) { + $uuid = ''; + $lastIdx = 0; + foreach (static::$separatorsIdxs as $sepIdx) { + $numOfCharsToCut = $sepIdx - $lastIdx; + $uuid .= substr($hexString, $lastIdx, $numOfCharsToCut) . '-'; + $lastIdx = $sepIdx; + } + $uuid .= substr($hexString, $lastIdx); + + return $uuid; + } + + /** + * Calculates the byte representation of a string. + * + * This method uses the PHP [unpack][1] function in order to get the byte + * representation of `$string`. + * + * Every character of `$string` is expected to be one byte long. That goes + * to say that this method does not support multi-byte strings. + * + * Moreover, the format of the array returned is the following: + * + * array( + * index: int => byte representation: int + * ) + * + * The indices start from one. So, the first byte is given by code such as: + * + * $bytes[1] + * + * Furthermore, the representation is an integer in the space [0, 255]. + * + * ### Example ### + * + * The code below: + * + * $bytes = getBytesOf("Hello, world!"); + * print_r($bytes); + * + * would result in the following output: + * + * Array ( + * [1] => 72 + * [2] => 101 + * [3] => 108 + * [4] => 108 + * [5] => 111 + * [6] => 32 + * [7] => 119 + * [8] => 111 + * [9] => 114 + * [10] => 108 + * [11] => 100 + * [12] => 33 + * ) + * + * + * [1]: http://php.net/manual/en/function.unpack.php "PHP unpack function" + * + * @param string $string The string whose byte representation this method + * calculates. + * @return array The array containing the bytes of the `$string`. Pay attention + * to the fact that the array's first index is one (and not zero). + * @throws \InvalidArgumentException if `$string` is null. + */ + protected function getBytesOf($string) { + if ($string === null) { + throw new \InvalidArgumentException('No string was provided.'); + } + + return unpack('C*', $string); + } + + /** + * Calculates the hexadecimal string representation of an array of bytes. + * + * This method performs the reverse operation of the {@link #getBytesOf} + * method. + * + * @param array $bytes The array of bytes whose hexadecimal string representation + * this method creates. + * @return string The string representation of `$bytes` in hexadecimal notation. + */ + protected function getHexadecimalOf(array $bytes) { + $binaryString = call_user_func_array( + 'pack', + array_merge( + array('C*'), + $bytes + ) + ); + + return bin2hex($binaryString); + } + + /** + * Sets the variant to the UUID provided. + * + * The UUID is expected to be given in big-endian byte representation. + * + * The variant is set by manipulating the `$uuidBytes` with bitwise operations. + * + * @param array $uuidBytes The byte representation of the UUID. + * @return void + * @see http://tinyurl.com/bcd69xo + */ + protected function setVariantToUuid(array &$uuidBytes) { + $uuidBytes[self::$variantByteIndex] &= 0x3f; + $uuidBytes[self::$variantByteIndex] |= 0x80; + } + + /** + * Sets the version number to a UUID. + * + * The UUID is expected to be given in big-endian byte representation. + * + * `$version` is set by manipulating the `$uuidBytes` with bitwise operations. + * + * It is due to the use of bitwise operators that `$version` is expected in + * hexadecimal format. That is, version four should be given like this: + * + * 0x40 + * + * @param array $uuidBytes The byte representation of the UUID. + * @param int $version The integer in hexadecimal notation that denotes the + * UUID version to be set. + * @return void + */ + protected function setVersionToUuid(array &$uuidBytes, $version) { + $uuidBytes[self::$versionByteIndex] &= 0x0f; + $uuidBytes[self::$versionByteIndex] |= $version; + } +} \ No newline at end of file diff --git a/test/Unit/Uuid/UuidGeneratorTest.php b/test/Unit/Uuid/UuidGeneratorTest.php new file mode 100644 index 0000000..f8483f5 --- /dev/null +++ b/test/Unit/Uuid/UuidGeneratorTest.php @@ -0,0 +1,98 @@ + + */ +class Unit_Uuid_UuidGeneratorTest extends PHPUnit_Framework_TestCase { + + /** + * The string denoting the regular expression pattern for a character in + * hexadecimal notation. + * + * @var string + */ + private static $hexCharPattern = '[0-9a-f]'; + + /** + * The string denoting the regular expression pattern of the UUID variant. + * + * @var string + */ + private static $variantPattern = '[89ab]'; + + /** + * The string denoting the regular expression pattern of the UUID version. + * + * @var string + */ + private static $versionPattern = '4'; + + /** + * @var UuidGenerator + */ + private $uuidGenerator; + + /** + * Tests if the *generateVersion4Uuid* method produces any duplicates. + * + * It goes without saying that there should be zero tolerance for duplicates. + * + * The testing algorithm goes as follows: + * + *
    + *
  1. Create an empty list where the new UUIDs will be stored. Let's call + * it *list*.
  2. + *
  3. Repeat 10,000 times: + *
      + *
    1. Create a new UUID. Let's call it *uuid*.
    2. + *
    3. Check if *uuid* is in *list*. + *
        + *
      1. If *yes*, then declare failure.
      2. + *
      3. If *no*, then add *uuid* to *list*.
      4. + *
      + *
    4. + *
    + *
  4. + *
+ */ + function testGenerateVersion4UuidForUniqueness() { + $uuidArr = array(); + for ($i = 1; $i <= 10000; $i++) { + $uuid = $this->uuidGenerator->generateVersion4UUID(); + if ( in_array($uuid, $uuidArr) ) { + $this->fail("Duplicate UUID: {$uuid} (iteration: {$i})"); + } + + $uuidArr[] = $uuid; + } + } + + /** + * Tests the "generateVersion4Uuid" method for valid [version 4 UUIDs][1]. + * + * + * [1]: http://tinyurl.com/3znd239 "Version 4 UUID" + */ + function testGenerateVersion4UuidForValidity() { + $uuid = $this->uuidGenerator->generateVersion4UUID(); + $pattern = "/^" . static::$hexCharPattern . "{8}-" + . static::$hexCharPattern . "{4}-" + . static::$versionPattern . static::$hexCharPattern . "{3}-" + . static::$variantPattern . static::$hexCharPattern . "{3}-" + . static::$hexCharPattern . "{12}$/"; + $valid = (boolean) preg_match($pattern, $uuid); + $this->assertTrue($valid); + } + + /** + * @inheritdoc + */ + protected function setUp() { + $this->uuidGenerator = UuidGenerator::getInstance(); + } +}