diff --git a/src/Codec/GuidStringCodec.php b/src/Codec/GuidStringCodec.php index 48f9ab1..40d8d71 100644 --- a/src/Codec/GuidStringCodec.php +++ b/src/Codec/GuidStringCodec.php @@ -14,8 +14,6 @@ declare(strict_types=1); namespace Ramsey\Uuid\Codec; -use Ramsey\Uuid\Exception\InvalidArgumentException; -use Ramsey\Uuid\Exception\InvalidUuidStringException; use Ramsey\Uuid\UuidInterface; /** @@ -26,34 +24,18 @@ use Ramsey\Uuid\UuidInterface; class GuidStringCodec extends StringCodec { /** - * @psalm-pure - */ - public function encodeBinary(UuidInterface $uuid): string - { - $components = $this->swapBytes($this->extractComponents($uuid->toString())); - - return (string) hex2bin(implode('', $components)); - } - - /** - * @throws InvalidUuidStringException - * * @inheritDoc - * * @psalm-pure */ public function decode(string $encodedUuid): UuidInterface { - $components = $this->swapBytes($this->extractComponents($encodedUuid)); + $bytes = $this->getBytes($encodedUuid); - return $this->getBuilder()->build($this, $this->getFields($components)); + return $this->getBuilder()->build($this, $this->swapBytes($bytes)); } /** - * @throws InvalidArgumentException if $bytes is an invalid length - * * @inheritDoc - * * @psalm-pure */ public function decodeBytes(string $bytes): UuidInterface @@ -63,22 +45,15 @@ class GuidStringCodec extends StringCodec } /** - * @param string[] $fields The fields that comprise this UUID - * - * @return string[] + * Swaps bytes according to the GUID rules * * @psalm-pure */ - private function swapBytes(array $fields): array + private function swapBytes(string $bytes): string { - $fields = array_values($fields); - - // Swap bytes to support GUID byte order. - $bytes = (string) hex2bin(implode('', $fields)); - $fields[0] = bin2hex($bytes[3] . $bytes[2] . $bytes[1] . $bytes[0]); - $fields[1] = bin2hex($bytes[5] . $bytes[4]); - $fields[2] = bin2hex($bytes[7] . $bytes[6]); - - return $fields; + return $bytes[3] . $bytes[2] . $bytes[1] . $bytes[0] + . $bytes[5] . $bytes[4] + . $bytes[7] . $bytes[6] + . substr($bytes, 8); } } diff --git a/src/Codec/OrderedTimeCodec.php b/src/Codec/OrderedTimeCodec.php index f30cf58..8dc4ebe 100644 --- a/src/Codec/OrderedTimeCodec.php +++ b/src/Codec/OrderedTimeCodec.php @@ -58,19 +58,12 @@ class OrderedTimeCodec extends StringCodec ); } - /** @var Rfc4122FieldsInterface $fields */ - $fields = $uuid->getFields(); + $bytes = $uuid->getFields()->getBytes(); - $optimized = [ - $fields->getTimeHiAndVersion()->toString(), - $fields->getTimeMid()->toString(), - $fields->getTimeLow()->toString(), - $fields->getClockSeqHiAndReserved()->toString(), - $fields->getClockSeqLow()->toString(), - $fields->getNode()->toString(), - ]; - - return (string) hex2bin(implode('', $optimized)); + return $bytes[6] . $bytes[7] + . $bytes[4] . $bytes[5] + . $bytes[0] . $bytes[1] . $bytes[2] . $bytes[3] + . substr($bytes, 8); } /** @@ -92,10 +85,9 @@ class OrderedTimeCodec extends StringCodec } // Rearrange the bytes to their original order. - $rearrangedBytes = substr($bytes, 4, 2) - . substr($bytes, 6, 2) - . substr($bytes, 2, 2) - . substr($bytes, 0, 2) + $rearrangedBytes = $bytes[4] . $bytes[5] . $bytes[6] . $bytes[7] + . $bytes[2] . $bytes[3] + . $bytes[0] . $bytes[1] . substr($bytes, 8); $uuid = parent::decodeBytes($rearrangedBytes); diff --git a/src/Codec/StringCodec.php b/src/Codec/StringCodec.php index 1321601..64455bf 100644 --- a/src/Codec/StringCodec.php +++ b/src/Codec/StringCodec.php @@ -51,19 +51,16 @@ class StringCodec implements CodecInterface /** @var FieldsInterface $fields */ $fields = $uuid->getFields(); - $components = [ - $fields->getTimeLow()->toString(), - $fields->getTimeMid()->toString(), - $fields->getTimeHiAndVersion()->toString(), - $fields->getClockSeqHiAndReserved()->toString(), - $fields->getClockSeqLow()->toString(), - $fields->getNode()->toString(), - ]; - - return vsprintf( - '%08s-%04s-%04s-%02s%02s-%012s', - $components - ); + return $fields->getTimeLow()->toString() + . '-' + . $fields->getTimeMid()->toString() + . '-' + . $fields->getTimeHiAndVersion()->toString() + . '-' + . $fields->getClockSeqHiAndReserved()->toString() + . $fields->getClockSeqLow()->toString() + . '-' + . $fields->getNode()->toString(); } /** @@ -71,7 +68,7 @@ class StringCodec implements CodecInterface */ public function encodeBinary(UuidInterface $uuid): string { - return (string) hex2bin($uuid->getHex()); + return $uuid->getFields()->getBytes(); } /** @@ -83,17 +80,11 @@ class StringCodec implements CodecInterface */ public function decode(string $encodedUuid): UuidInterface { - $components = $this->extractComponents($encodedUuid); - $fields = $this->getFields($components); - - return $this->builder->build($this, $fields); + return $this->builder->build($this, $this->getBytes($encodedUuid)); } /** - * @throws InvalidArgumentException if $bytes is an invalid length - * * @inheritDoc - * * @psalm-pure */ public function decodeBytes(string $bytes): UuidInterface @@ -104,9 +95,7 @@ class StringCodec implements CodecInterface ); } - $hexUuid = unpack('H*', $bytes); - - return $this->decode((string) $hexUuid[1]); + return $this->builder->build($this, $bytes); } /** @@ -118,69 +107,32 @@ class StringCodec implements CodecInterface } /** - * Returns an array of UUID components (the UUID exploded on its dashes) - * - * @param string $encodedUuid A hexadecimal string representation of a UUID - * - * @return string[] - * - * @throws InvalidUuidStringException + * Returns a byte string of the UUID * * @psalm-pure */ - protected function extractComponents(string $encodedUuid): array + protected function getBytes(string $encodedUuid): string { - $nameParsed = str_replace([ - 'urn:', - 'uuid:', - '{', - '}', - '-', - ], '', $encodedUuid); + $parsedUuid = str_replace( + ['urn:', 'uuid:', '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 = [ - substr($nameParsed, 0, 8), - substr($nameParsed, 8, 4), - substr($nameParsed, 12, 4), - substr($nameParsed, 16, 4), - substr($nameParsed, 20), + substr($parsedUuid, 0, 8), + substr($parsedUuid, 8, 4), + substr($parsedUuid, 12, 4), + substr($parsedUuid, 16, 4), + substr($parsedUuid, 20), ]; - $nameParsed = implode('-', $components); - - if (!Uuid::isValid($nameParsed)) { + if (!Uuid::isValid(implode('-', $components))) { throw new InvalidUuidStringException( 'Invalid UUID string: ' . $encodedUuid ); } - return $components; - } - - /** - * Returns the fields that make up this UUID - * - * @param string[] $components An array of hexadecimal strings representing - * the fields of an RFC 4122 UUID - * - * @return string The byte string for the UUID - * - * @psalm-pure - */ - protected function getFields(array $components): string - { - $fields = [ - 'time_low' => str_pad($components[0], 8, '0', STR_PAD_LEFT), - 'time_mid' => str_pad($components[1], 4, '0', STR_PAD_LEFT), - 'time_hi_and_version' => str_pad($components[2], 4, '0', STR_PAD_LEFT), - 'clock_seq_hi_and_reserved' => str_pad(substr($components[3], 0, 2), 2, '0', STR_PAD_LEFT), - 'clock_seq_low' => str_pad(substr($components[3], 2), 2, '0', STR_PAD_LEFT), - 'node' => str_pad($components[4], 12, '0', STR_PAD_LEFT), - ]; - - return (string) hex2bin(implode('', $fields)); + return (string) hex2bin($parsedUuid); } } diff --git a/src/Codec/TimestampFirstCombCodec.php b/src/Codec/TimestampFirstCombCodec.php index bff41fa..5ea4d62 100644 --- a/src/Codec/TimestampFirstCombCodec.php +++ b/src/Codec/TimestampFirstCombCodec.php @@ -14,9 +14,7 @@ declare(strict_types=1); namespace Ramsey\Uuid\Codec; -use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Exception\InvalidUuidStringException; -use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\UuidInterface; /** @@ -50,23 +48,15 @@ class TimestampFirstCombCodec extends StringCodec */ public function encode(UuidInterface $uuid): string { - /** @var FieldsInterface $fields */ - $fields = $uuid->getFields(); + $bytes = $this->swapBytes($uuid->getFields()->getBytes()); - $sixPieceComponents = [ - $fields->getTimeLow()->toString(), - $fields->getTimeMid()->toString(), - $fields->getTimeHiAndVersion()->toString(), - $fields->getClockSeqHiAndReserved()->toString(), - $fields->getClockSeqLow()->toString(), - $fields->getNode()->toString(), - ]; - - $sixPieceComponents = $this->swapTimestampAndRandomBits($sixPieceComponents); - - return vsprintf( - '%08s-%04s-%04s-%02s%02s-%012s', - $sixPieceComponents + return sprintf( + '%08s-%04s-%04s-%04s-%012s', + bin2hex(substr($bytes, 0, 4)), + bin2hex(substr($bytes, 4, 2)), + bin2hex(substr($bytes, 6, 2)), + bin2hex(substr($bytes, 8, 2)), + bin2hex(substr($bytes, 10)) ); } @@ -75,9 +65,7 @@ class TimestampFirstCombCodec extends StringCodec */ public function encodeBinary(UuidInterface $uuid): string { - $stringEncoding = $this->encode($uuid); - - return (string) hex2bin(str_replace('-', '', $stringEncoding)); + return $this->swapBytes($uuid->getFields()->getBytes()); } /** @@ -89,48 +77,33 @@ class TimestampFirstCombCodec extends StringCodec */ public function decode(string $encodedUuid): UuidInterface { - $fivePieceComponents = $this->extractComponents($encodedUuid); + $bytes = $this->getBytes($encodedUuid); - $fivePieceComponents = $this->swapTimestampAndRandomBits($fivePieceComponents); - - return $this->getBuilder()->build($this, $this->getFields($fivePieceComponents)); + return $this->getBuilder()->build($this, $this->swapBytes($bytes)); } /** - * @throws InvalidArgumentException if $bytes is an invalid length - * * @inheritDoc - * * @psalm-pure */ public function decodeBytes(string $bytes): UuidInterface { - return $this->decode(bin2hex($bytes)); + return $this->getBuilder()->build($this, $this->swapBytes($bytes)); } /** - * Swaps the first 48 bits with the last 48 bits - * - * @param string[] $components An array of UUID components (the UUID exploded on its dashes) - * - * @return string[] The adjusted components + * Swaps bytes according to the timestamp-first COMB rules * * @psalm-pure */ - private function swapTimestampAndRandomBits(array $components): array + private function swapBytes(string $bytes): string { - $last48Bits = $components[4]; + $first48Bits = substr($bytes, 0, 6); + $last48Bits = substr($bytes, -6); - if (count($components) === 6) { - $last48Bits = $components[5]; - $components[5] = $components[0] . $components[1]; - } else { - $components[4] = $components[0] . $components[1]; - } + $bytes = substr_replace($bytes, $last48Bits, 0, 6); + $bytes = substr_replace($bytes, $first48Bits, -6); - $components[0] = substr($last48Bits, 0, 8); - $components[1] = substr($last48Bits, 8, 4); - - return $components; + return $bytes; } } diff --git a/tests/Codec/StringCodecTest.php b/tests/Codec/StringCodecTest.php index f8b4ac0..f457d0a 100644 --- a/tests/Codec/StringCodecTest.php +++ b/tests/Codec/StringCodecTest.php @@ -4,10 +4,12 @@ declare(strict_types=1); namespace Ramsey\Uuid\Test\Codec; +use Mockery; use PHPUnit\Framework\MockObject\MockObject; use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Codec\StringCodec; use Ramsey\Uuid\Rfc4122\Fields; +use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\Test\TestCase; use Ramsey\Uuid\UuidInterface; @@ -68,10 +70,13 @@ class StringCodecTest extends TestCase public function testEncodeBinaryReturnsBinaryString(): void { $expected = hex2bin('123456781234abcdabef1234abcd4321'); - $this->uuid->method('getHex') - ->willReturn('123456781234abcdabef1234abcd4321'); - $this->uuid->method('getBytes') - ->willReturn(hex2bin('123456781234abcdabef1234abcd4321')); + + $fields = Mockery::mock(FieldsInterface::class, [ + 'getBytes' => hex2bin('123456781234abcdabef1234abcd4321'), + ]); + + $this->uuid->method('getFields')->willReturn($fields); + $codec = new StringCodec($this->builder); $result = $codec->encodeBinary($this->uuid); $this->assertSame($expected, $result); diff --git a/tests/Encoder/TimestampLastCombCodecTest.php b/tests/Encoder/TimestampLastCombCodecTest.php index 3985ecf..4c81098 100644 --- a/tests/Encoder/TimestampLastCombCodecTest.php +++ b/tests/Encoder/TimestampLastCombCodecTest.php @@ -10,6 +10,7 @@ use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Codec\TimestampLastCombCodec; use Ramsey\Uuid\Rfc4122\Fields; +use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\Test\TestCase; use Ramsey\Uuid\UuidInterface; @@ -46,14 +47,14 @@ class TimestampLastCombCodecTest extends TestCase public function testBinaryEncoding(): void { + $fields = Mockery::mock(FieldsInterface::class, [ + 'getBytes' => hex2bin('0800200c9a6611e19b21ff6f8cb0c57d'), + ]); + /** @var MockObject & UuidInterface $uuidMock */ $uuidMock = $this->getMockBuilder(UuidInterface::class)->getMock(); - $uuidMock->expects($this->any()) - ->method('getHex') - ->willReturn('0800200c9a6611e19b21ff6f8cb0c57d'); - $uuidMock->expects($this->any()) - ->method('getBytes') - ->willReturn(hex2bin('0800200c9a6611e19b21ff6f8cb0c57d')); + $uuidMock->expects($this->any())->method('getFields')->willReturn($fields); + $encodedUuid = $this->codec->encodeBinary($uuidMock); $this->assertSame(hex2bin('0800200c9a6611e19b21ff6f8cb0c57d'), $encodedUuid);