1<?php
2
3/**
4 * This file is part of the ramsey/uuid library
5 *
6 * For the full copyright and license information, please view the LICENSE
7 * file that was distributed with this source code.
8 *
9 * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
10 * @license http://opensource.org/licenses/MIT MIT
11 */
12
13declare(strict_types=1);
14
15namespace Ramsey\Uuid\Codec;
16
17use Ramsey\Uuid\Builder\UuidBuilderInterface;
18use Ramsey\Uuid\Exception\InvalidArgumentException;
19use Ramsey\Uuid\Exception\InvalidUuidStringException;
20use Ramsey\Uuid\Rfc4122\FieldsInterface;
21use Ramsey\Uuid\Uuid;
22use Ramsey\Uuid\UuidInterface;
23
24use function hex2bin;
25use function implode;
26use function str_replace;
27use function strlen;
28use function substr;
29
30/**
31 * StringCodec encodes and decodes RFC 4122 UUIDs
32 *
33 * @link http://tools.ietf.org/html/rfc4122
34 *
35 * @psalm-immutable
36 */
37class StringCodec implements CodecInterface
38{
39    /**
40     * @var UuidBuilderInterface
41     */
42    private $builder;
43
44    /**
45     * Constructs a StringCodec
46     *
47     * @param UuidBuilderInterface $builder The builder to use when encoding UUIDs
48     */
49    public function __construct(UuidBuilderInterface $builder)
50    {
51        $this->builder = $builder;
52    }
53
54    public function encode(UuidInterface $uuid): string
55    {
56        /** @var FieldsInterface $fields */
57        $fields = $uuid->getFields();
58
59        return $fields->getTimeLow()->toString()
60            . '-'
61            . $fields->getTimeMid()->toString()
62            . '-'
63            . $fields->getTimeHiAndVersion()->toString()
64            . '-'
65            . $fields->getClockSeqHiAndReserved()->toString()
66            . $fields->getClockSeqLow()->toString()
67            . '-'
68            . $fields->getNode()->toString();
69    }
70
71    /**
72     * @psalm-return non-empty-string
73     * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty
74     * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty
75     */
76    public function encodeBinary(UuidInterface $uuid): string
77    {
78        return $uuid->getFields()->getBytes();
79    }
80
81    /**
82     * @throws InvalidUuidStringException
83     *
84     * @inheritDoc
85     */
86    public function decode(string $encodedUuid): UuidInterface
87    {
88        return $this->builder->build($this, $this->getBytes($encodedUuid));
89    }
90
91    public function decodeBytes(string $bytes): UuidInterface
92    {
93        if (strlen($bytes) !== 16) {
94            throw new InvalidArgumentException(
95                '$bytes string should contain 16 characters.'
96            );
97        }
98
99        return $this->builder->build($this, $bytes);
100    }
101
102    /**
103     * Returns the UUID builder
104     */
105    protected function getBuilder(): UuidBuilderInterface
106    {
107        return $this->builder;
108    }
109
110    /**
111     * Returns a byte string of the UUID
112     */
113    protected function getBytes(string $encodedUuid): string
114    {
115        $parsedUuid = str_replace(
116            ['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}', '-'],
117            '',
118            $encodedUuid
119        );
120
121        $components = [
122            substr($parsedUuid, 0, 8),
123            substr($parsedUuid, 8, 4),
124            substr($parsedUuid, 12, 4),
125            substr($parsedUuid, 16, 4),
126            substr($parsedUuid, 20),
127        ];
128
129        if (!Uuid::isValid(implode('-', $components))) {
130            throw new InvalidUuidStringException(
131                'Invalid UUID string: ' . $encodedUuid
132            );
133        }
134
135        return (string) hex2bin($parsedUuid);
136    }
137}
138