Skip to content

Commit 4b2dce3

Browse files
committed
refactor: remove markrogoyski/math-php
Signed-off-by: Alan Brault <alan.brault@visus.io>
1 parent 878b0fc commit 4b2dce3

4 files changed

Lines changed: 330 additions & 18 deletions

File tree

composer.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
],
1616
"require": {
1717
"php": "^8.2",
18-
"markrogoyski/math-php": "^2.11",
1918
"symfony/polyfill-php83": "^1.32"
2019
},
2120
"require-dev": {

src/Cuid2.php

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
use Exception;
88
use JsonSerializable;
9-
use MathPHP\Exception\BadParameterException;
10-
use MathPHP\Functions\BaseEncoderDecoder;
119
use OutOfRangeException;
1210
use Override;
1311

@@ -42,11 +40,6 @@
4240
*/
4341
final class Cuid2 implements JsonSerializable
4442
{
45-
/**
46-
* Base36 alphabet used for encoding (0-9, a-z).
47-
*/
48-
public const BASE36_ALPHANUMERIC = '0123456789abcdefghijklmnopqrstuvwxyz';
49-
5043
/**
5144
* Cached list of available hash algorithms.
5245
*
@@ -72,7 +65,7 @@ final class Cuid2 implements JsonSerializable
7265
/**
7366
* Configured length of the CUID string.
7467
*
75-
* @var int<1, max>
68+
* @var int<4, 32>
7669
*/
7770
private readonly int $length;
7871

@@ -106,7 +99,7 @@ final class Cuid2 implements JsonSerializable
10699
*
107100
* @throws OutOfRangeException If $maxLength is less than 4 or greater than 32.
108101
* @throws Exception If random byte generation fails (extremely rare).
109-
* @throws BadParameterException If base conversion fails (should never occur).
102+
* @throws InvalidOperationException If SHA3-512 algorithm is not supported (extremely rare).
110103
*/
111104
public function __construct(int $maxLength = 24)
112105
{
@@ -137,7 +130,7 @@ public function __construct(int $maxLength = 24)
137130
*
138131
* @throws OutOfRangeException If $maxLength is less than 4 or greater than 32.
139132
* @throws Exception If random byte generation fails (extremely rare).
140-
* @throws BadParameterException If base conversion fails (should never occur).
133+
* @throws InvalidOperationException If SHA3-512 algorithm is not supported (extremely rare).
141134
*/
142135
public static function generate(int $maxLength = 24): Cuid2
143136
{
@@ -272,7 +265,6 @@ private static function generateTimestamp(): int
272265
* @return string The final CUID2 identifier string.
273266
*
274267
* @throws InvalidOperationException If SHA3-512 algorithm is not supported.
275-
* @throws BadParameterException If base conversion fails (should never occur).
276268
*/
277269
private function render(): string
278270
{
@@ -302,26 +294,23 @@ private function render(): string
302294
* Converts a base16 (hexadecimal) string to base36.
303295
*
304296
* Uses GMP extension if available for significantly better performance,
305-
* otherwise falls back to math-php library for arbitrary precision arithmetic.
297+
* otherwise falls back to the Utils::hexToBase36() method for arbitrary
298+
* precision arithmetic.
306299
*
307300
* Base36 encoding uses 0-9 and a-z (36 characters total), producing shorter
308301
* strings than hexadecimal while remaining URL-safe and case-insensitive.
309302
*
310303
* @param string $value Base16 (hexadecimal) string to convert.
311304
*
312305
* @return string The value encoded in base36.
313-
*
314-
* @throws BadParameterException If base conversion fails (should never occur with valid input).
315306
*/
316307
private static function convert(string $value): string
317308
{
318309
if (extension_loaded('gmp')) {
319310
return gmp_strval(gmp_init($value, 16), 36);
320311
}
321312

322-
$integer = BaseEncoderDecoder::createArbitraryInteger($value, 16);
323-
324-
return BaseEncoderDecoder::toBase($integer, 36, self::BASE36_ALPHANUMERIC);
313+
return Utils::hexToBase36($value);
325314
}
326315

327316
/**

src/Utils.php

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Visus\Cuid2;
6+
7+
/**
8+
* Utility functions for CUID2 generation.
9+
*/
10+
final class Utils
11+
{
12+
/**
13+
* Base36 alphabet for encoding (0-9, a-z).
14+
*/
15+
private const BASE36_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
16+
17+
/**
18+
* Prevents instantiation of utility class.
19+
*
20+
* @codeCoverageIgnore
21+
*/
22+
private function __construct()
23+
{
24+
}
25+
26+
/**
27+
* Converts a single hexadecimal character to its numeric value.
28+
*
29+
* @param string $char Single hex character (0-9, a-f, A-F).
30+
*
31+
* @return int Numeric value (0-15).
32+
*/
33+
private static function parseHexCharacter(string $char): int
34+
{
35+
return match (true) {
36+
$char >= '0' && $char <= '9' => ord($char) - 48,
37+
$char >= 'a' && $char <= 'f' => ord($char) - 87,
38+
$char >= 'A' && $char <= 'F' => ord($char) - 55,
39+
default => 0,
40+
};
41+
}
42+
43+
/**
44+
* Converts a hexadecimal string to large base representation.
45+
*
46+
* @param string $hexValue Hexadecimal string to convert.
47+
* @param int $base Large base for digit storage (100 million).
48+
*
49+
* @return array<int> Array of digits in large base representation.
50+
*/
51+
private static function convertHexToLargeBase(string $hexValue, int $base): array
52+
{
53+
$digits = [0];
54+
55+
for ($i = 0, $len = strlen($hexValue); $i < $len; $i++) {
56+
$hexDigit = self::parseHexCharacter($hexValue[$i]);
57+
$carry = $hexDigit;
58+
59+
for ($j = 0, $jlen = count($digits); $j < $jlen; $j++) {
60+
$current = $digits[$j] * 16 + $carry;
61+
$digits[$j] = $current % $base;
62+
$carry = intdiv($current, $base);
63+
}
64+
65+
while ($carry > 0) {
66+
$digits[] = $carry % $base;
67+
$carry = intdiv($carry, $base);
68+
}
69+
}
70+
71+
return $digits;
72+
}
73+
74+
/**
75+
* Converts large base digit array to base36 string.
76+
*
77+
* @param array<int> $digits Array of digits in large base representation.
78+
* @param int $base Large base (100 million).
79+
*
80+
* @return string Base36 encoded string.
81+
*/
82+
private static function convertLargeBaseToBase36(array $digits, int $base): string
83+
{
84+
$resultChars = [];
85+
86+
while (count($digits) > 1 || $digits[0] !== 0) {
87+
$carry = 0;
88+
$newDigits = [];
89+
90+
for ($i = count($digits) - 1; $i >= 0; $i--) {
91+
$current = $carry * $base + $digits[$i];
92+
$quotient = intdiv($current, 36);
93+
$carry = $current % 36;
94+
95+
if ($quotient > 0 || $newDigits !== []) {
96+
$newDigits[] = $quotient;
97+
}
98+
}
99+
100+
$resultChars[] = self::BASE36_ALPHABET[$carry];
101+
$digits = $newDigits !== [] ? array_reverse($newDigits) : [0];
102+
}
103+
104+
return implode('', array_reverse($resultChars));
105+
}
106+
107+
/**
108+
* Converts a hexadecimal string to base36 encoding.
109+
*
110+
* This function performs arbitrary precision base conversion without requiring
111+
* the GMP extension. It uses a large intermediate base (100 million) for efficient
112+
* arithmetic operations on large numbers.
113+
*
114+
* Base36 encoding uses digits 0-9 and lowercase letters a-z (36 characters total),
115+
* producing shorter strings than hexadecimal while remaining URL-safe.
116+
*
117+
* Algorithm:
118+
* 1. Convert hex string to internal representation using base 100M
119+
* 2. Convert internal representation to base36 by repeated division
120+
*
121+
* @param string $hexValue Hexadecimal string to convert (case-insensitive).
122+
*
123+
* @return string The value encoded in base36 (lowercase alphanumeric).
124+
*/
125+
public static function hexToBase36(string $hexValue): string
126+
{
127+
if ($hexValue === '' || $hexValue === '0') {
128+
return '0';
129+
}
130+
131+
$base = 100_000_000;
132+
$digits = self::convertHexToLargeBase($hexValue, $base);
133+
134+
return self::convertLargeBaseToBase36($digits, $base);
135+
}
136+
}

0 commit comments

Comments
 (0)