BinaryCodec.php 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. <?php
  2. namespace Illuminate\Support;
  3. use InvalidArgumentException;
  4. use Ramsey\Uuid\Uuid;
  5. use Ramsey\Uuid\UuidInterface;
  6. use Symfony\Component\Uid\Ulid;
  7. class BinaryCodec
  8. {
  9. /** @var array<string, array{encode: callable(UuidInterface|Ulid|string|null): ?string, decode: callable(?string): ?string}> */
  10. protected static array $customCodecs = [];
  11. /**
  12. * Register a custom codec.
  13. */
  14. public static function register(string $name, callable $encode, callable $decode): void
  15. {
  16. self::$customCodecs[$name] = [
  17. 'encode' => $encode,
  18. 'decode' => $decode,
  19. ];
  20. }
  21. /**
  22. * Encode a value to binary.
  23. */
  24. public static function encode(UuidInterface|Ulid|string|null $value, string $format): ?string
  25. {
  26. if (blank($value)) {
  27. return null;
  28. }
  29. if (isset(self::$customCodecs[$format])) {
  30. return (self::$customCodecs[$format]['encode'])($value);
  31. }
  32. return match ($format) {
  33. 'uuid' => match (true) {
  34. $value instanceof UuidInterface => $value->getBytes(),
  35. self::isBinary($value) => $value,
  36. default => Uuid::fromString($value)->getBytes(),
  37. },
  38. 'ulid' => match (true) {
  39. $value instanceof Ulid => $value->toBinary(),
  40. self::isBinary($value) => $value,
  41. default => Ulid::fromString($value)->toBinary(),
  42. },
  43. default => throw new InvalidArgumentException("Format [$format] is invalid."),
  44. };
  45. }
  46. /**
  47. * Decode a binary value to string.
  48. */
  49. public static function decode(?string $value, string $format): ?string
  50. {
  51. if (blank($value)) {
  52. return null;
  53. }
  54. if (isset(self::$customCodecs[$format])) {
  55. return (self::$customCodecs[$format]['decode'])($value);
  56. }
  57. return match ($format) {
  58. 'uuid' => (self::isBinary($value) ? Uuid::fromBytes($value) : Uuid::fromString($value))->toString(),
  59. 'ulid' => (self::isBinary($value) ? Ulid::fromBinary($value) : Ulid::fromString($value))->toString(),
  60. default => throw new InvalidArgumentException("Format [$format] is invalid."),
  61. };
  62. }
  63. /**
  64. * Get all available format names.
  65. *
  66. * @return list<string>
  67. */
  68. public static function formats(): array
  69. {
  70. return array_unique([...['uuid', 'ulid'], ...array_keys(self::$customCodecs)]);
  71. }
  72. /**
  73. * Determine if the given value is binary data.
  74. */
  75. public static function isBinary(mixed $value): bool
  76. {
  77. if (! is_string($value) || $value === '') {
  78. return false;
  79. }
  80. if (str_contains($value, "\0")) {
  81. return true;
  82. }
  83. return ! mb_check_encoding($value, 'UTF-8');
  84. }
  85. }