Skip to content

Commit

Permalink
Release/3.0.0 (#9)
Browse files Browse the repository at this point in the history
* refactor: Remove GMP dependency and switch to BC math for arithmetic operations.
  • Loading branch information
gustavofreze authored Dec 3, 2024
1 parent 3698734 commit 0fab019
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 84 deletions.
31 changes: 13 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,44 @@ on:
permissions:
contents: read

env:
PHP_VERSION: '8.3'

jobs:
auto-review:
name: Auto review
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Use PHP 8.2
- name: Configure PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
php-version: ${{ env.PHP_VERSION }}

- name: Install dependencies
run: composer update --no-progress --optimize-autoloader

- name: Run phpcs
run: composer phpcs

- name: Run phpmd
run: composer phpmd
- name: Run review
run: composer review

tests:
name: Tests
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Use PHP 8.2
- name: Use PHP ${{ env.PHP_VERSION }}
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
php-version: ${{ env.PHP_VERSION }}

- name: Install dependencies
run: composer update --no-progress --optimize-autoloader

- name: Run unit tests
env:
XDEBUG_MODE: coverage
run: composer test

- name: Run mutation tests
run: composer test-mutation
- name: Run tests
run: composer tests
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
DOCKER_RUN = docker run --rm -it --net=host -v ${PWD}:/app -w /app gustavofreze/php:8.2
DOCKER_RUN = docker run --rm -it --net=host -v ${PWD}:/app -w /app gustavofreze/php:8.3

.PHONY: configure test test-file test-no-coverage review show-reports clean

Expand Down
12 changes: 5 additions & 7 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,18 @@
}
},
"require": {
"php": "^8.2",
"ext-gmp": "*"
"php": "^8.3",
"ext-bcmath": "*"
},
"require-dev": {
"phpmd/phpmd": "^2.15",
"phpunit/phpunit": "^11",
"phpstan/phpstan": "^1",
"infection/infection": "^0.29",
"squizlabs/php_codesniffer": "^3.10"
"squizlabs/php_codesniffer": "^3.11"
},
"suggest": {
"ext-gmp": "Enables faster math with arbitrary-precision integers using GMP."
"ext-bcmath": "Enables the extension which is an interface to the GNU implementation as a Basic Calculator utility library."
},
"scripts": {
"phpcs": "phpcs --standard=PSR12 --extensions=php ./src",
Expand All @@ -60,7 +60,6 @@
"test": "phpunit --log-junit=report/coverage/junit.xml --coverage-xml=report/coverage/coverage-xml --coverage-html=report/coverage/coverage-html tests",
"test-mutation": "infection --only-covered --logger-html=report/coverage/mutation-report.html --coverage=report/coverage --min-msi=100 --min-covered-msi=100 --threads=4",
"test-no-coverage": "phpunit --no-coverage",
"test-mutation-no-coverage": "infection --only-covered --min-msi=100 --threads=4",
"review": [
"@phpcs",
"@phpmd",
Expand All @@ -71,8 +70,7 @@
"@test-mutation"
],
"tests-no-coverage": [
"@test-no-coverage",
"@test-mutation-no-coverage"
"@test-no-coverage"
]
}
}
7 changes: 6 additions & 1 deletion infection.json.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"timeout": 10,
"timeout": 30,
"testFramework": "phpunit",
"tmpDir": "report/infection/",
"source": {
Expand All @@ -13,7 +13,12 @@
},
"mutators": {
"@default": true,
"BCMath": false,
"CastInt": false,
"Increment": false,
"GreaterThan": false,
"UnwrapSubstr": false,
"UnwrapStrToLower": false,
"LogicalAndNegation": false,
"LogicalAndAllSubExprNegation": false
},
Expand Down
34 changes: 16 additions & 18 deletions src/Base62.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@

namespace TinyBlocks\Encoder;

use TinyBlocks\Encoder\Internal\Decimal;
use TinyBlocks\Encoder\Internal\Exceptions\InvalidDecoding;
use TinyBlocks\Encoder\Internal\Hexadecimal;

final readonly class Base62 implements Encoder
{
private const BASE62_RADIX = 62;
private const BASE62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
private const BASE62_CHARACTER_LENGTH = 1;
private const BASE62_HEXADECIMAL_RADIX = 16;
public const int BASE62_RADIX = 62;
private const int BASE62_CHARACTER_LENGTH = 1;

private const string BASE62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

private function __construct(private string $value)
{
Expand All @@ -25,18 +26,18 @@ public static function from(string $value): Encoder

public function encode(): string
{
$hexadecimal = Hexadecimal::fromBinary(binary: $this->value);
$bytes = $hexadecimal->removeLeadingZeroBytes();
$hexadecimal = Hexadecimal::fromBinary(binary: $this->value, alphabet: self::BASE62_ALPHABET);
$hexadecimal = $hexadecimal->removeLeadingZeroBytes();

$base62 = str_repeat(self::BASE62_ALPHABET[0], $bytes);
$base62 = str_repeat(self::BASE62_ALPHABET[0], $hexadecimal->getBytes());

if ($hexadecimal->isEmpty()) {
return $base62;
}

$number = $hexadecimal->toGmpInit(base: self::BASE62_HEXADECIMAL_RADIX);
$base62Value = $hexadecimal->toBase(base: self::BASE62_RADIX);

return sprintf('%s%s', $base62, gmp_strval($number, self::BASE62_RADIX));
return sprintf('%s%s', $base62, $base62Value);
}

public function decode(): string
Expand All @@ -57,16 +58,13 @@ public function decode(): string
return str_repeat("\x00", $bytes);
}

$number = gmp_init($value, self::BASE62_RADIX);
$hexadecimal = Hexadecimal::fromGmp(number: $number, base: self::BASE62_HEXADECIMAL_RADIX);
$hexadecimal->padLeft();

$binary = hex2bin(sprintf('%s%s', str_repeat('00', $bytes), $hexadecimal->toString()));
$decimal = Decimal::fromBase62(number: $value, alphabet: self::BASE62_ALPHABET);
$hexadecimal = Hexadecimal::from(value: $decimal->toHexadecimal())
->fillWithZeroIfNecessary()
->toString();

if (!is_string($binary)) {
throw new InvalidDecoding(value: $this->value);
}
$binary = hex2bin($hexadecimal);

return $binary;
return sprintf('%s%s', str_repeat("\x00", $bytes), $binary);
}
}
42 changes: 42 additions & 0 deletions src/Internal/Decimal.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace TinyBlocks\Encoder\Internal;

use TinyBlocks\Encoder\Base62;

final readonly class Decimal
{
private function __construct(private string $value)
{
}

public static function fromBase62(string $number, string $alphabet): Decimal
{
$value = '0';
$length = strlen($number);

for ($index = 0; $index < $length; $index++) {
$digit = strpos($alphabet, $number[$index]);
$value = bcmul($value, (string)Base62::BASE62_RADIX);
$value = bcadd($value, (string)$digit);
}

return new Decimal(value: $value);
}

public function toHexadecimal(): string
{
$value = $this->value;
$hexadecimalValue = '';

while (bccomp($value, '0') > 0) {
$remainder = bcmod($value, Hexadecimal::HEXADECIMAL_RADIX);
$hexadecimalValue = sprintf('%s%s', Hexadecimal::HEXADECIMAL_ALPHABET[(int)$remainder], $hexadecimalValue);
$value = bcdiv($value, Hexadecimal::HEXADECIMAL_RADIX);
}

return $hexadecimalValue;
}
}
1 change: 1 addition & 0 deletions src/Internal/Exceptions/InvalidDecoding.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ final class InvalidDecoding extends RuntimeException
public function __construct(private readonly string $value)
{
$template = 'The value <%s> could not be decoded.';

parent::__construct(message: sprintf($template, $this->value));
}
}
82 changes: 54 additions & 28 deletions src/Internal/Hexadecimal.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,86 @@

namespace TinyBlocks\Encoder\Internal;

use GMP;

final class Hexadecimal
final readonly class Hexadecimal
{
private const HEXADECIMAL_BYTE_LENGTH = 2;
private const int DEFAULT_BYTE_COUNT = 0;
private const int HEXADECIMAL_BYTE_LENGTH = 2;

private string $value;
public const string HEXADECIMAL_RADIX = '16';
public const string HEXADECIMAL_ALPHABET = '0123456789abcdef';

private function __construct(string $value)
{
$this->value = $value;
private function __construct(
private string $value,
private string $alphabet,
private int $bytes = self::DEFAULT_BYTE_COUNT
) {
}

public static function fromGmp(GMP $number, int $base): Hexadecimal
public static function from(string $value): Hexadecimal
{
return new Hexadecimal(value: gmp_strval($number, $base));
return new Hexadecimal(value: $value, alphabet: self::HEXADECIMAL_ALPHABET);
}

public static function fromBinary(string $binary): Hexadecimal
public static function fromBinary(string $binary, string $alphabet): Hexadecimal
{
return new Hexadecimal(value: bin2hex($binary));
return new Hexadecimal(value: bin2hex($binary), alphabet: $alphabet);
}

public function isEmpty(): bool
public function removeLeadingZeroBytes(): Hexadecimal
{
return empty($this->value);
$bytes = 0;
$newValue = $this->value;

while (str_starts_with($newValue, '00')) {
$bytes++;
$newValue = substr($newValue, self::HEXADECIMAL_BYTE_LENGTH);
}

return new Hexadecimal(value: $newValue, alphabet: $this->alphabet, bytes: $bytes);
}

public function padLeft(): void
public function fillWithZeroIfNecessary(): Hexadecimal
{
if (strlen($this->value) % 2 !== 0) {
$this->value = sprintf('0%s', $this->value);
}
$newValue = strlen($this->value) % 2 !== 0 ? sprintf('0%s', $this->value) : $this->value;

return new Hexadecimal(value: $newValue, alphabet: $this->alphabet, bytes: $this->bytes);
}

public function toString(): string
public function getBytes(): int
{
return $this->value;
return $this->bytes;
}

public function toGmpInit(int $base): GMP
public function isEmpty(): bool
{
return gmp_init($this->value, $base);
return empty($this->value);
}

public function removeLeadingZeroBytes(): int
public function toBase(int $base): string
{
$bytes = 0;
$length = strlen($this->value);
$decimalValue = '0';

while (str_starts_with($this->value, '00')) {
$bytes++;
$this->value = substr($this->value, self::HEXADECIMAL_BYTE_LENGTH);
for ($index = 0; $index < $length; $index++) {
$digit = strpos(self::HEXADECIMAL_ALPHABET, strtolower($this->value[$index]));
$decimalValue = bcmul($decimalValue, self::HEXADECIMAL_RADIX);
$decimalValue = bcadd($decimalValue, (string)$digit);
}

$digits = $this->alphabet;
$result = '';

while (bccomp($decimalValue, '0') > 0) {
$remainder = bcmod($decimalValue, (string)$base);
$result = sprintf('%s%s', $digits[(int)$remainder], $result);
$decimalValue = bcdiv($decimalValue, (string)$base);
}

return $bytes;
return $result ?: '0';
}

public function toString(): string
{
return $this->value;
}
}
Loading

0 comments on commit 0fab019

Please sign in to comment.