vendor/symfony/uid/Ulid.php line 21

  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <[email protected]>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Uid;
  11. /**
  12.  * A ULID is lexicographically sortable and contains a 48-bit timestamp and 80-bit of crypto-random entropy.
  13.  *
  14.  * @see https://github.com/ulid/spec
  15.  *
  16.  * @author Nicolas Grekas <[email protected]>
  17.  */
  18. class Ulid extends AbstractUid implements TimeBasedUidInterface
  19. {
  20.     protected const NIL '00000000000000000000000000';
  21.     protected const MAX '7ZZZZZZZZZZZZZZZZZZZZZZZZZ';
  22.     private static string $time '';
  23.     private static array $rand = [];
  24.     public function __construct(string $ulid null)
  25.     {
  26.         if (null === $ulid) {
  27.             $this->uid = static::generate();
  28.         } elseif (self::NIL === $ulid) {
  29.             $this->uid $ulid;
  30.         } elseif (self::MAX === strtr($ulid'z''Z')) {
  31.             $this->uid $ulid;
  32.         } else {
  33.             if (!self::isValid($ulid)) {
  34.                 throw new \InvalidArgumentException(sprintf('Invalid ULID: "%s".'$ulid));
  35.             }
  36.             $this->uid strtoupper($ulid);
  37.         }
  38.     }
  39.     public static function isValid(string $ulid): bool
  40.     {
  41.         if (26 !== \strlen($ulid)) {
  42.             return false;
  43.         }
  44.         if (26 !== strspn($ulid'0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz')) {
  45.             return false;
  46.         }
  47.         return $ulid[0] <= '7';
  48.     }
  49.     public static function fromString(string $ulid): static
  50.     {
  51.         if (36 === \strlen($ulid) && preg_match('{^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$}Di'$ulid)) {
  52.             $ulid uuid_parse($ulid);
  53.         } elseif (22 === \strlen($ulid) && 22 === strspn($ulidBinaryUtil::BASE58[''])) {
  54.             $ulid str_pad(BinaryUtil::fromBase($ulidBinaryUtil::BASE58), 16"\0"\STR_PAD_LEFT);
  55.         }
  56.         if (16 !== \strlen($ulid)) {
  57.             return match (strtr($ulid'z''Z')) {
  58.                 self::NIL => new NilUlid(),
  59.                 self::MAX => new MaxUlid(),
  60.                 default => new static($ulid),
  61.             };
  62.         }
  63.         $ulid bin2hex($ulid);
  64.         $ulid sprintf('%02s%04s%04s%04s%04s%04s%04s',
  65.             base_convert(substr($ulid02), 1632),
  66.             base_convert(substr($ulid25), 1632),
  67.             base_convert(substr($ulid75), 1632),
  68.             base_convert(substr($ulid125), 1632),
  69.             base_convert(substr($ulid175), 1632),
  70.             base_convert(substr($ulid225), 1632),
  71.             base_convert(substr($ulid275), 1632)
  72.         );
  73.         if (self::NIL === $ulid) {
  74.             return new NilUlid();
  75.         }
  76.         if (self::MAX === $ulid strtr($ulid'abcdefghijklmnopqrstuv''ABCDEFGHJKMNPQRSTVWXYZ')) {
  77.             return new MaxUlid();
  78.         }
  79.         $u = new static(self::NIL);
  80.         $u->uid $ulid;
  81.         return $u;
  82.     }
  83.     public function toBinary(): string
  84.     {
  85.         $ulid strtr($this->uid'ABCDEFGHJKMNPQRSTVWXYZ''abcdefghijklmnopqrstuv');
  86.         $ulid sprintf('%02s%05s%05s%05s%05s%05s%05s',
  87.             base_convert(substr($ulid02), 3216),
  88.             base_convert(substr($ulid24), 3216),
  89.             base_convert(substr($ulid64), 3216),
  90.             base_convert(substr($ulid104), 3216),
  91.             base_convert(substr($ulid144), 3216),
  92.             base_convert(substr($ulid184), 3216),
  93.             base_convert(substr($ulid224), 3216)
  94.         );
  95.         return hex2bin($ulid);
  96.     }
  97.     public function toBase32(): string
  98.     {
  99.         return $this->uid;
  100.     }
  101.     public function getDateTime(): \DateTimeImmutable
  102.     {
  103.         $time strtr(substr($this->uid010), 'ABCDEFGHJKMNPQRSTVWXYZ''abcdefghijklmnopqrstuv');
  104.         if (\PHP_INT_SIZE >= 8) {
  105.             $time = (string) hexdec(base_convert($time3216));
  106.         } else {
  107.             $time sprintf('%02s%05s%05s',
  108.                 base_convert(substr($time02), 3216),
  109.                 base_convert(substr($time24), 3216),
  110.                 base_convert(substr($time64), 3216)
  111.             );
  112.             $time BinaryUtil::toBase(hex2bin($time), BinaryUtil::BASE10);
  113.         }
  114.         if (\strlen($time)) {
  115.             $time '000'.$time;
  116.         }
  117.         return \DateTimeImmutable::createFromFormat('U.u'substr_replace($time'.', -30));
  118.     }
  119.     public static function generate(\DateTimeInterface $time null): string
  120.     {
  121.         if (null === $mtime $time) {
  122.             $time microtime(false);
  123.             $time substr($time11).substr($time23);
  124.         } elseif ($time $time->format('Uv')) {
  125.             throw new \InvalidArgumentException('The timestamp must be positive.');
  126.         }
  127.         if ($time self::$time || (null !== $mtime && $time !== self::$time)) {
  128.             randomize:
  129.             $r unpack('n*'random_bytes(10));
  130.             $r[1] |= ($r[5] <<= 4) & 0xF0000;
  131.             $r[2] |= ($r[5] <<= 4) & 0xF0000;
  132.             $r[3] |= ($r[5] <<= 4) & 0xF0000;
  133.             $r[4] |= ($r[5] <<= 4) & 0xF0000;
  134.             unset($r[5]);
  135.             self::$rand $r;
  136.             self::$time $time;
  137.         } elseif ([=> 0xFFFFF0xFFFFF0xFFFFF0xFFFFF] === self::$rand) {
  138.             if (\PHP_INT_SIZE >= || 10 \strlen($time self::$time)) {
  139.                 $time = (string) ($time);
  140.             } elseif ('999999999' === $mtime substr($time, -9)) {
  141.                 $time = (substr($time0, -9)).'000000000';
  142.             } else {
  143.                 $time substr_replace($timestr_pad(++$mtime9'0'\STR_PAD_LEFT), -9);
  144.             }
  145.             goto randomize;
  146.         } else {
  147.             for ($i 4$i && 0xFFFFF === self::$rand[$i]; --$i) {
  148.                 self::$rand[$i] = 0;
  149.             }
  150.             ++self::$rand[$i];
  151.             $time self::$time;
  152.         }
  153.         if (\PHP_INT_SIZE >= 8) {
  154.             $time base_convert($time1032);
  155.         } else {
  156.             $time str_pad(bin2hex(BinaryUtil::fromBase($timeBinaryUtil::BASE10)), 12'0'\STR_PAD_LEFT);
  157.             $time sprintf('%s%04s%04s',
  158.                 base_convert(substr($time02), 1632),
  159.                 base_convert(substr($time25), 1632),
  160.                 base_convert(substr($time75), 1632)
  161.             );
  162.         }
  163.         return strtr(sprintf('%010s%04s%04s%04s%04s',
  164.             $time,
  165.             base_convert(self::$rand[1], 1032),
  166.             base_convert(self::$rand[2], 1032),
  167.             base_convert(self::$rand[3], 1032),
  168.             base_convert(self::$rand[4], 1032)
  169.         ), 'abcdefghijklmnopqrstuv''ABCDEFGHJKMNPQRSTVWXYZ');
  170.     }
  171. }