From 13b3bf7ed9a4ca026a6ed972a585ba68c33c50b5 Mon Sep 17 00:00:00 2001 From: Thibaud Fabre Date: Thu, 30 Oct 2014 09:53:43 +0100 Subject: [PATCH] Refactor from/toString methods to codecs --- src/Codec.php | 12 ++++ src/Codec/GuidStringCodec.php | 93 +++++++++++++++++++++++++++++ src/Codec/StringCodec.php | 72 ++++++++++++++++++++++ src/Uuid.php | 85 ++++++-------------------- src/UuidInterface.php | 46 ++++++++++++++ tests/Codec/GuidStringCodecTest.php | 18 ++++++ tests/Codec/StringCodecTest.php | 18 ++++++ tests/bootstrap.php | 2 - 8 files changed, 276 insertions(+), 70 deletions(-) create mode 100644 src/Codec.php create mode 100644 src/Codec/GuidStringCodec.php create mode 100644 src/Codec/StringCodec.php create mode 100644 src/UuidInterface.php create mode 100644 tests/Codec/GuidStringCodecTest.php create mode 100644 tests/Codec/StringCodecTest.php diff --git a/src/Codec.php b/src/Codec.php new file mode 100644 index 0000000..0e045c7 --- /dev/null +++ b/src/Codec.php @@ -0,0 +1,12 @@ +getFieldsHex()); + + // Swap byte-order on the first three fields + $hex = unpack('H*', pack('V', hexdec($fields[0]))); + $fields[0] = $hex[1]; + $hex = unpack('H*', pack('v', hexdec($fields[1]))); + $fields[1] = $hex[1]; + $hex = unpack('H*', pack('v', hexdec($fields[2]))); + $fields[2] = $hex[1]; + + return vsprintf( + '%08s-%04s-%04s-%02s%02s-%012s', + $fields + ); + } + + public function decode($encodedUuid) + { + return $this->_decode($encodedUuid, true); + } + + public function decodeBytes($bytes) + { + if (strlen($bytes) !== 16) { + throw new InvalidArgumentException('$bytes string should contain 16 characters.'); + } + + $hexUuid = unpack('H*', $bytes); + + return $this->_decode($hexUuid, false); + } + + private function _decode($hex, $swap) + { + $nameParsed = str_replace(array( + 'urn:', + 'uuid:', + '{', + '}', + '-' + ), '', $hex); + + // We have stripped out the dashes and are breaking up the string using + // substr(). In this way, we can accept a full hex value that doesn't + // contain dashes. + $components = array( + substr($nameParsed, 0, 8), + substr($nameParsed, 8, 4), + substr($nameParsed, 12, 4), + substr($nameParsed, 16, 4), + substr($nameParsed, 20) + ); + + if ($swap) { + $hex = unpack('H*', pack('V', hexdec($components[0]))); + $components[0] = $hex[1]; + $hex = unpack('H*', pack('v', hexdec($components[1]))); + $components[1] = $hex[1]; + $hex = unpack('H*', pack('v', hexdec($components[2]))); + $components[2] = $hex[1]; + $nameParsed = implode('-', $components); + } + + if (! Uuid::isValid($nameParsed)) { + throw new InvalidArgumentException('Invalid UUID string: ' . $name); + } + + $fields = array( + 'time_low' => sprintf('%08s', $components[0]), + 'time_mid' => sprintf('%04s', $components[1]), + 'time_hi_and_version' => sprintf('%04s', $components[2]), + 'clock_seq_hi_and_reserved' => sprintf('%02s', substr($components[3], 0, 2)), + 'clock_seq_low' => sprintf('%02s', substr($components[3], 2)), + 'node' => sprintf('%012s', $components[4]) + ); + + return new Uuid($fields, $this); + } +} diff --git a/src/Codec/StringCodec.php b/src/Codec/StringCodec.php new file mode 100644 index 0000000..a65f108 --- /dev/null +++ b/src/Codec/StringCodec.php @@ -0,0 +1,72 @@ +getFieldsHex()); + + return vsprintf( + '%08s-%04s-%04s-%02s%02s-%012s', + $fields + ); + } + + public function decode($encodedUuid) + { + $nameParsed = str_replace(array( + 'urn:', + 'uuid:', + '{', + '}', + '-' + ), '', $encodedUuid); + + // We have stripped out the dashes and are breaking up the string using + // substr(). In this way, we can accept a full hex value that doesn't + // contain dashes. + $components = array( + substr($nameParsed, 0, 8), + substr($nameParsed, 8, 4), + substr($nameParsed, 12, 4), + substr($nameParsed, 16, 4), + substr($nameParsed, 20) + ); + + $nameParsed = implode('-', $components); + + if (! Uuid::isValid($nameParsed)) { + throw new InvalidArgumentException('Invalid UUID string: ' . $name); + } + + $fields = array( + 'time_low' => sprintf('%08s', $components[0]), + 'time_mid' => sprintf('%04s', $components[1]), + 'time_hi_and_version' => sprintf('%04s', $components[2]), + 'clock_seq_hi_and_reserved' => sprintf('%02s', substr($components[3], 0, 2)), + 'clock_seq_low' => sprintf('%02s', substr($components[3], 2)), + 'node' => sprintf('%012s', $components[4]) + ); + + return new Uuid($fields, $this); + } + + public function decodeBytes($bytes) + { + if (strlen($bytes) !== 16) { + throw new InvalidArgumentException('$bytes string should contain 16 characters.'); + } + + $hexUuid = unpack('H*', $bytes); + + return $this->decode($hexUuid); + } +} diff --git a/src/Uuid.php b/src/Uuid.php index a5bed74..4cdbddc 100644 --- a/src/Uuid.php +++ b/src/Uuid.php @@ -12,6 +12,8 @@ namespace Rhumsaa\Uuid; use InvalidArgumentException; +use Rhumsaa\Uuid\Codec\GuidStringCodec; +use Rhumsaa\Uuid\Codec\StringCodec; /** * Represents a universally unique identifier (UUID), according to RFC 4122 @@ -29,7 +31,8 @@ use InvalidArgumentException; * @link http://docs.python.org/3/library/uuid.html * @link http://docs.oracle.com/javase/6/docs/api/java/util/UUID.html */ -class Uuid implements \JsonSerializable + +final class Uuid implements UuidInterface, \JsonSerializable { /** * When this namespace is specified, the name string is a fully-qualified domain name. @@ -150,10 +153,10 @@ class Uuid implements \JsonSerializable ); /** - * Whether the first three fields are stored in little endian format (GUID's) - * @var bool + * String codec + * @var Codec */ - protected $isLittleEndian; + protected $codec; /** * Creates a universally unique identifier (UUID) from an array of fields. @@ -162,13 +165,13 @@ class Uuid implements \JsonSerializable * UUIDs. * * @param array $fields - * @param bool $littleEndian Whether the first three fields are in little-endian format. + * @param Codec $codec String codec * @link Rhumsaa.Uuid.Uuid.html#method_getFields */ - protected function __construct(array $fields, $littleEndian = false) + public function __construct(array $fields, Codec $codec = null) { $this->fields = $fields; - $this->isLittleEndian = (bool)$littleEndian; + $this->codec = $codec ?: new StringCodec(); } /** @@ -209,7 +212,7 @@ class Uuid implements \JsonSerializable * @param Uuid $uuid UUID to which this UUID is to be compared * @return int -1, 0 or 1 as this UUID is less than, equal to, or greater than $uuid */ - public function compareTo(Uuid $uuid) + public function compareTo(UuidInterface $uuid) { $comparison = null; @@ -844,22 +847,7 @@ class Uuid implements \JsonSerializable */ public function toString($forceBigEndian = false) { - $fields = array_values($this->fields); - - if ($this->isLittleEndian && $forceBigEndian == false) { - // Swap byte-order on the first three fields - $hex = unpack('H*', pack('V', hexdec($fields[0]))); - $fields[0] = $hex[1]; - $hex = unpack('H*', pack('v', hexdec($fields[1]))); - $fields[1] = $hex[1]; - $hex = unpack('H*', pack('v', hexdec($fields[2]))); - $fields[2] = $hex[1]; - } - - return vsprintf( - '%08s-%04s-%04s-%02s%02s-%012s', - $fields - ); + return $this->codec->encode($this); } /** @@ -871,16 +859,11 @@ class Uuid implements \JsonSerializable */ public static function fromBytes($bytes, $littleEndian = false) { - if (strlen($bytes) !== 16) { - throw new InvalidArgumentException('$bytes string should contain 16 characters.'); + if ($littleEndian) { + return (new GuidStringCodec())->decodeBytes($bytes); } - $hexUuid = unpack('H*', $bytes); - - $uuid = Uuid::fromString($hexUuid[1], false); - $uuid->isLittleEndian = $littleEndian; - - return $uuid; + return (new StringCodec())->decodeBytes($bytes); } /** @@ -894,45 +877,11 @@ class Uuid implements \JsonSerializable */ public static function fromString($name, $littleEndian = false) { - $nameParsed = str_replace(array('urn:', 'uuid:', '{', '}', '-'), '', $name); - - // We have stripped out the dashes and are breaking up the string using - // substr(). In this way, we can accept a full hex value that doesn't - // contain dashes. - $components = array( - substr($nameParsed, 0, 8), - substr($nameParsed, 8, 4), - substr($nameParsed, 12, 4), - substr($nameParsed, 16, 4), - substr($nameParsed, 20), - ); - - // Swap byte-order on the first three fields if ($littleEndian) { - $hex = unpack('H*', pack('V', hexdec($components[0]))); - $components[0] = $hex[1]; - $hex = unpack('H*', pack('v', hexdec($components[1]))); - $components[1] = $hex[1]; - $hex = unpack('H*', pack('v', hexdec($components[2]))); - $components[2] = $hex[1]; + return (new GuidStringCodec())->decode($name); } - $nameParsed = implode('-', $components); - - if (!self::isValid($nameParsed)) { - throw new InvalidArgumentException('Invalid UUID string: ' . $name); - } - - $fields = array( - 'time_low' => sprintf('%08s', $components[0]), - 'time_mid' => sprintf('%04s', $components[1]), - 'time_hi_and_version' => sprintf('%04s', $components[2]), - 'clock_seq_hi_and_reserved' => sprintf('%02s', substr($components[3], 0, 2)), - 'clock_seq_low' => sprintf('%02s', substr($components[3], 2)), - 'node' => sprintf('%012s', $components[4]), - ); - - return new self($fields, $littleEndian); + return (new StringCodec())->decode($name); } /** diff --git a/src/UuidInterface.php b/src/UuidInterface.php new file mode 100644 index 0000000..d268ff2 --- /dev/null +++ b/src/UuidInterface.php @@ -0,0 +1,46 @@ +decode('ff6f8cb0-c57d-11e1-9b21-0800200c9a66'); + + $this->assertInstanceOf('\Rhumsaa\Uuid\Uuid', $uuid); + $this->assertEquals('ff6f8cb0-c57d-11e1-9b21-0800200c9a66', $uuid->toString()); + } +} diff --git a/tests/Codec/StringCodecTest.php b/tests/Codec/StringCodecTest.php new file mode 100644 index 0000000..5426b66 --- /dev/null +++ b/tests/Codec/StringCodecTest.php @@ -0,0 +1,18 @@ +decode('ff6f8cb0-c57d-11e1-9b21-0800200c9a66'); + + $this->assertInstanceOf('\Rhumsaa\Uuid\Uuid', $uuid); + $this->assertEquals('ff6f8cb0-c57d-11e1-9b21-0800200c9a66', $uuid->toString()); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 72c9c19..50e981f 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -9,8 +9,6 @@ if (!file_exists(dirname(__DIR__) . '/vendor/autoload.php')) { . "See http://getcomposer.org for help with installing composer\n"); } -var_dump(phpversion()); - // Set a default timezone for HHVM tests date_default_timezone_set('UTC');