Компонент UID

Дата обновления перевода 2023-08-25

Компонент UID

Компонент UID предоставляет утилиты для работы с уникальными идентификаторами (UID), вроде UUID и ULID.

Установка

1
$ composer require symfony/uid

Note

Если вы устанавливаете этот компонент вне приложения Symfony, вам нужно подключить файл vendor/autoload.php в вашем коде для включения механизма автозагрузки классов, предоставляемых Composer. Детальнее читайте в этой статье.

UUID

UUID (универсально-уникальные идентификаторы) - это одни из наиболее популярных UID в индустрии ПО. UUID - это 128-битные числа, обычно представленные в виде 5 групп шестнадцатеричных символов: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx (знак M - UUID-версия, а знак N - UUID-вариант).

Создание UUID

Используйте именованные конструкторы класса Uuid или любых других конкретных классов, чтобы создать каждый тип UUID:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
use Symfony\Component\Uid\Uuid;

// UUID 1го типа генерирует UUID, используя MAC-адрес вашего устройства и временную отметку.
// Оба получаются автоматически, поэтому вам не нужно передавать никакие аргументы конструктора.
$uuid = Uuid::v1(); // $uuid is an instance of Symfony\Component\Uid\UuidV1

// UUID 4го типа генерирует рандомный UUID, поэтому вам не нужно передавать никакие аргументы конструктора.
$uuid = Uuid::v4(); // $uuid is an instance of Symfony\Component\Uid\UuidV4

// UUID 3го и 5го типов генерируют UUID, хещируя заданное пространство имен и имя. 3й тип использует
// MD5-хеши, а 5й тип использует SHA-1. Пространство имен - это еще один UUID (например, UUID 4го типа),
// а имя - произвольная строка (например, название продукта; если оно уникально).
$namespace = Uuid::v4();
$name = $product->getUniqueName();

$uuid = Uuid::v3($namespace, $name); // $uuid is an instance of Symfony\Component\Uid\UuidV3
$uuid = Uuid::v5($namespace, $name); // $uuid is an instance of Symfony\Component\Uid\UuidV5

// пространства имён, определенные RFC 4122 (см. https://tools.ietf.org/html/rfc4122#appendix-C)
// доступны как PHP-константы и как значения строк
$uuid = Uuid::v3(Uuid::NAMESPACE_DNS, $name);  // same as: Uuid::v3('dns', $name);
$uuid = Uuid::v3(Uuid::NAMESPACE_URL, $name);  // same as: Uuid::v3('url', $name);
$uuid = Uuid::v3(Uuid::NAMESPACE_OID, $name);  // same as: Uuid::v3('oid', $name);
$uuid = Uuid::v3(Uuid::NAMESPACE_X500, $name); // same as: Uuid::v3('x500', $name);

// UUID 6го типа пока не являются частью стандарта UUID. Он лексикографически сортируемый
// (как ULID) и содержит 60-битную временную отметку вместе с 63 дополнительными уникальными битами.
// Он определяется в https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-uuid-version-6
$uuid = Uuid::v6(); // $uuid is an instance of Symfony\Component\Uid\UuidV6

// UUID версии 7 представляет поле значения, упорядоченное по времени, выведенное из хорошо известного
// источника временной отметки Unix Epoch: количество секунд с полуночи 1 января 1970 UTC, високосные секунды исключены.
// А также улучшенные характеристики энтропии по сравнению с версиями 1 и 6.
$uuid = Uuid::v7();

// UUID версии 8 предоставляет формат, совместимый с RFC, для экспериментальных случаев использования, для конкретных поставщиков.
// Единственное требование - биты варианта и версии ДОЛЖНЫ быть установлены, как определено в Разделе 4:
// https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#variant_and_version_fields
// уникальность UUIDv8 будет зависеть от реализации и НЕ ДОЛЖНА предполагаться.
$uuid = Uuid::v8();

6.2

Версии UUID 7 и 8 были представлены в Symfony 6.2.

Если ваше значение UUID уже сгенировано в другом формате, используйте любой из следующих методов, чтобы создать из него объект Uuid:

1
2
3
4
5
6
// все следующие примеры сгенерируют одинаковый объект Uuid
$uuid = Uuid::fromString('d9e7a184-5d5b-11ea-a62a-3499710062d0');
$uuid = Uuid::fromBinary("\xd9\xe7\xa1\x84\x5d\x5b\x11\xea\xa6\x2a\x34\x99\x71\x00\x62\xd0");
$uuid = Uuid::fromBase32('6SWYGR8QAV27NACAHMK5RG0RPG');
$uuid = Uuid::fromBase58('TuetYWNHhmuSQ3xPoVLv9M');
$uuid = Uuid::fromRfc4122('d9e7a184-5d5b-11ea-a62a-3499710062d0');

Вы также можете использовать UuidFactory, чтобы генерировать UUID. Для начала вы можете сконфигурировать поведение фабрики, используя файлы конфигурации:

1
2
3
4
5
6
7
8
# config/packages/uid.yaml
framework:
    uid:
        default_uuid_version: 7
        name_based_uuid_version: 5
        name_based_uuid_namespace: 6ba7b810-9dad-11d1-80b4-00c04fd430c8
        time_based_uuid_version: 7
        time_based_uuid_node: 121212121212

Затем вы можете внедрить фабрику в ваши сервисы и использовать её, чтобы генерировать UUID, основываясь на определённой вами конфигурации:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace App\Service;

use Symfony\Component\Uid\Factory\UuidFactory;

class FooService
{
    public function __construct(
        private UuidFactory $uuidFactory,
    ) {
    }

    public function generate(): void
    {
        // Это создаёт UUID версии, заданной в файле конфигурации (v6 по умолчанию)
        $uuid = $this->uuidFactory->create();

        $nameBasedUuid = $this->uuidFactory->nameBased(/** ... */);
        $randomBasedUuid = $this->uuidFactory->randomBased();
        $timestampBased = $this->uuidFactory->timeBased();

        // ...
    }
}

Конвертация UUID

Используйте эти методы, чтобы преобразовать объекты UUID в разные основы:

1
2
3
4
5
6
7
8
9
$uuid = Uuid::fromString('d9e7a184-5d5b-11ea-a62a-3499710062d0');

$uuid = Uuid::fromString('d9e7a184-5d5b-11ea-a62a-3499710062d0');

$uuid->toBinary();  // string(16) "\xd9\xe7\xa1\x84\x5d\x5b\x11\xea\xa6\x2a\x34\x99\x71\x00\x62\xd0"
$uuid->toBase32();  // string(26) "6SWYGR8QAV27NACAHMK5RG0RPG"
$uuid->toBase58();  // string(22) "TuetYWNHhmuSQ3xPoVLv9M"
$uuid->toRfc4122(); // string(36) "d9e7a184-5d5b-11ea-a62a-3499710062d0"
$uuid->toHex();     // string(34) "0xd9e7a1845d5b11eaa62a3499710062d0"

6.2

Метод toHex() был представлен в Symfony 6.2.

Работа с UUID

UUID-объекты, созданные с классом Uuid могут использовать следующие методы (которые эквивалентны методы uuid_*() PHP-расширения):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use Symfony\Component\Uid\NilUuid;
use Symfony\Component\Uid\Uuid;

// проверка, является ли UUID null (отметьте, что класс называется
// NilUuid вместо NullUuid, чтобы следовать cтандартной нотации UUID)
$uuid = Uuid::v4();
$uuid instanceof NilUuid; // false

// проверка типа UUID
use Symfony\Component\Uid\UuidV4;
$uuid = Uuid::v4();
$uuid instanceof UuidV4; // true

// получение даты и времени UUID (доступно только в некоторых типах UUID)
$uuid = Uuid::v1();
$uuid->getDateTime();  // returns a \DateTimeImmutable instance

// проверка, является ли заданное значение валидным как UUID
$isValid = Uuid::isValid($uuid); // true or false

// сравнение UUID и проверка равенства
$uuid1 = Uuid::v1();
$uuid4 = Uuid::v4();
$uuid1->equals($uuid4); // false

// этот метод возвращает:
//   * int(0), если $uuid1 и $uuid4 равны
//   * int > 0, если $uuid1 больше, чем $uuid4
//   * int < 0, если $uuid1 меньше, чем $uuid4
$uuid1->compare($uuid4); // например, int(4)

Хранение UUID в базах данных

Если вы используете Doctrine, рассмотрите использование типа Doctrine uuid, который преобразует из/в объекты UUID автоматически:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Entity/Product.php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;

#[ORM\Entity(repositoryClass: ProductRepository::class)]
class Product
{
    #[ORM\Column(type: UuidType::NAME)]
    private Uuid $someProperty;

    // ...
}

6.2

Константа UuidType::NAME была представлена в Symfony 6.2.

Также существует генератор Doctrine, чтобы помочь с автогенериррованием значений UUID для основных ключей сущности:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;

class User implements UserInterface
{
    #[ORM\Id]
    #[ORM\Column(type: UuidType::NAME, unique: true)]
    #[ORM\GeneratedValue(strategy: 'CUSTOM')]
    #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
    private ?Uuid $id;

    public function getId(): ?Uuid
    {
        return $this->id;
    }

    // ...
}

При использовании встроенных методов хранилища Doctrine (например, findOneBy()), Doctrine знает, как преобразовывать эти типы UUID, чтобы создать SQL-запрос (например, ->findOneBy(['user' => $user->getUuid()])). Однако, при использовании DQL-запросов, или создании запроса самостоятельно, вам понадобится установить uuid как тип UUID-параметров:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/Repository/ProductRepository.php

// ...
use Symfony\Bridge\Doctrine\Types\UuidType;

class ProductRepository extends ServiceEntityRepository
{
    // ...

    public function findUserProducts(User $user): array
    {
        $qb = $this->createQueryBuilder('p')
            // ...
            // добавьте 'uuid' как третий аргумент, чтобы сообщить Doctrine, что это UUID
            ->setParameter('user', $user->getUuid(), 'uuid')

            // как вариант, вы можете преобразовать его в значение, совместимое с
            // типом, определенным Doctrine
            ->setParameter('user', $user->getUuid()->toBinary())
        ;

        // ...
    }
}

ULID

ULID (Универсально-уникальные лексикографически сортируемые идентификаторы) - это 128-битные числа, обычно представленные в виде строк из 26 знаком: TTTTTTTTTTRRRRRRRRRRRRRRRR (где T - это временная отметка, а R - рандомные биты).

ULID - это альтернатива UUID, когда использование последних непрактично. Они предоставляет 128-битную совместимость с UUID, лексикографически сортируемы и зашифрованы как строки из 26 знаков (против 36-знаковых UUID).

Note

Если вы генерируете более одного ULID во время одной и той же миллисекунды в одном процессе, тогда рандомная часть увеличивается на один бит, чтобы предоставить монотонность для сортировки. В этом случае, рандомная часть не настолько случайна, по сравнению с предыдущим ULID.

Создание ULID

Инстанциируйте класс Ulid, чтобы сгенерировать рандомное значение ULID:

1
2
3
use Symfony\Component\Uid\Ulid;

$ulid = new Ulid();  // например, 01AN4Z07BY79KA1307SR9X4MV3

Если ваше значение ULID уже сгенерировано в другом формате, используйте любой из следующих методов, чтобы создать из него объект Ulid:

1
2
3
4
5
6
// все следующие примеры создадут одинаковый объект Ulid
$ulid = Ulid::fromString('01E439TP9XJZ9RPFH3T1PYBCR8');
$ulid = Ulid::fromBinary("\x01\x71\x06\x9d\x59\x3d\x97\xd3\x8b\x3e\x23\xd0\x6d\xe5\xb3\x08");
$ulid = Ulid::fromBase32('01E439TP9XJZ9RPFH3T1PYBCR8');
$ulid = Ulid::fromBase58('1BKocMc5BnrVcuq2ti4Eqm');
$ulid = Ulid::fromRfc4122('0171069d-593d-97d3-8b3e-23d06de5b308');

Как и UUID, ULID имеют свою собственную фабрику, UlidFactory, которая может быть использована для их генерирования:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace App\Service;

use Symfony\Component\Uid\Factory\UlidFactory;

class FooService
{
    public function __construct(
        private UlidFactory $ulidFactory,
    ) {
    }

    public function generate(): void
    {
        $ulid = $this->ulidFactory->create();

        // ...
    }
}

Также существует специальный класс NilUlid, чтобы представлять значения ULID null:

1
2
3
4
use Symfony\Component\Uid\NilUlid;

$ulid = new NilUlid();
// эквивалентно $ulid = new Ulid('00000000000000000000000000');

Конвертация ULID

Используйте эти методы, чтобы преобразовать объект ULID в разные основы:

1
2
3
4
5
6
7
$ulid = Ulid::fromString('01E439TP9XJZ9RPFH3T1PYBCR8');

$ulid->toBinary();  // string(16) "\x01\x71\x06\x9d\x59\x3d\x97\xd3\x8b\x3e\x23\xd0\x6d\xe5\xb3\x08"
$ulid->toBase32();  // string(26) "01E439TP9XJZ9RPFH3T1PYBCR8"
$ulid->toBase58();  // string(22) "1BKocMc5BnrVcuq2ti4Eqm"
$ulid->toRfc4122(); // string(36) "0171069d-593d-97d3-8b3e-23d06de5b308"
$ulid->toHex();     // string(34) "0x0171069d593d97d38b3e23d06de5b308"

6.2

Метод toHex() был представлен в Symfony 6.2.

Работа с ULID

ULID-объекты, созданные с классом Ulid, могут использовать следующие методы:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use Symfony\Component\Uid\Ulid;

$ulid1 = new Ulid();
$ulid2 = new Ulid();

// проверка, является ли заданное значение валидным как ULID
$isValid = Ulid::isValid($ulidValue); // true or false

// получение времени и даты ULID
$ulid1->getDateTime(); // returns a \DateTimeImmutable instance

// сравнение ULID и проверка равенства
$ulid1->equals($ulid2); // false
// this method returns $ulid1 <=> $ulid2
$ulid1->compare($ulid2); // e.g. int(-1)

Хранение ULID в базах данных

Если вы используете Doctrine, рассмотрите использование типа Doctrine ulid, который автотматически преобразует в/из ULID-объекты:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Entity/Product.php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Types\UlidType;
use Symfony\Component\Uid\Ulid;

#[ORM\Entity(repositoryClass: ProductRepository::class)]
class Product
{
    #[ORM\Column(type: UlidType::NAME)]
    private Ulid $someProperty;

    // ...
}

6.2

Константа UlidType::NAME была представлена в Symfony 6.2.

Также существует генератор Doctrine, чтобы помочь с автогенерированием ULID-значений для главных ключей сущеностей:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Types\UlidType;
use Symfony\Component\Uid\Ulid;

class Product
{
    #[ORM\Id]
    #[ORM\Column(type: UlidType::NAME, unique: true)]
    #[ORM\GeneratedValue(strategy: 'CUSTOM')]
    #[ORM\CustomIdGenerator(class: 'doctrine.ulid_generator')]
    private ?Ulid $id;

    public function getId(): ?Ulid
    {
        return $this->id;
    }

    // ...

}

При использовании встроенных методов хранилища Doctrine (например, findOneBy()), Doctrine знает, как преобразовывать эти ULID-типы для создания SQL-запроса (например, ->findOneBy(['user' => $user->getUlid()])). Однако, при использовании DQL-запросов, или создании запроса самостоятельно, вам нужно будет установить ulid как тип ULID-параметров:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/Repository/ProductRepository.php

// ...
use Symfony\Bridge\Doctrine\Types\UlidType;

class ProductRepository extends ServiceEntityRepository
{
    // ...

    public function findUserProducts(User $user): array
    {
        $qb = $this->createQueryBuilder('p')
            // ...
            // добавьте 'ulid' как третий аргумент, чтобы сообщить Doctrine, что это ULID
            ->setParameter('user', $user->getUlid(), 'ulid')

            // как вариант, вы можете преобразовать его в значение, совместимое с
            // типом, определенным Doctrine
            ->setParameter('user', $user->getUlid()->toBinary())
        ;

        // ...
    }
}

Генерирование и исследование UUID/ULID в консоли

Этот компонент предоставляет несколько команд для генерирования и исследования UUID/ULID в консоли. Они не включены по умолчанию, поэтому вы должны добавить следующую конфигурацию в вашем приложении перед тем, как использовать эти команды:

1
2
3
4
5
6
# config/services.yaml
services:
    Symfony\Component\Uid\Command\GenerateUlidCommand: ~
    Symfony\Component\Uid\Command\GenerateUuidCommand: ~
    Symfony\Component\Uid\Command\InspectUlidCommand: ~
    Symfony\Component\Uid\Command\InspectUuidCommand: ~

Теперь вы можете генерировать UUID/ULID следующим образом (добавьте опцию --help к командам, чтобы узнать все об их опциях):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# сгенерировать 1 рандомно-основанный UUID
$ php bin/console uuid:generate --random-based

# сгенерировать 1 UUID, основанный на времени, с конкретным узлом
$ php bin/console uuid:generate --time-based=now --node=fb3502dc-137e-4849-8886-ac90d07f64a7

# сгенерировать 2 UUID и вывести их в формате base58
$ php bin/console uuid:generate --count=2 --format=base58

# сгенерировать 1 ULID с текущем временем в качестве временной отметки
$ php bin/console ulid:generate

# сгенерировать 1 ULID с конкретной временной отметкой
$ php bin/console ulid:generate --time="2021-02-02 14:00:00"

# сгенерировать 2 ULID и вывести их в формате RFC4122
$ php bin/console ulid:generate --count=2 --format=rfc4122

В дополенение к созданию новых UID, вы также можете исследовать их с помощью следующих команд, чтобы увидеть всю информацию о заданном UID:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ php bin/console uuid:inspect d0a3a023-f515-4fe0-915c-575e63693998
 ------------------------- --------------------------------------
  Ярлык                     Значение
 ------------------------- --------------------------------------
  Версия                    4
  Каноничность (RFC 4122)   d0a3a023-f515-4fe0-915c-575e63693998
  База 58                   SmHvuofV4GCF7QW543rDD9
  База 32                   6GMEG27X8N9ZG92Q2QBSHPJECR
 ------------------------- --------------------------------------

$ php bin/console ulid:inspect 01F2TTCSYK1PDRH73Z41BN1C4X
 ------------------------ --------------------------------------
  Ярлык                    Значение
 ------------------------ --------------------------------------
  Каноникал (База 32)      01F2TTCSYK1PDRH73Z41BN1C4X
  База 58                  1BYGm16jS4kX3VYCysKKq6
  RFC 4122                 0178b5a6-67d3-0d9b-889c-7f205750b09d
 ------------------------ --------------------------------------
  Временная отметка        2021-04-09 08:01:24.947
 ------------------------ --------------------------------------