Skip to content

Commit

Permalink
refactor: Adds improvements to decoding functionality. (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
gustavofreze authored Oct 4, 2024
1 parent 15dd108 commit 8dbd26f
Show file tree
Hide file tree
Showing 13 changed files with 300 additions and 98 deletions.
14 changes: 14 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/tests export-ignore
/vendor export-ignore

/LICENSE export-ignore
/Makefile export-ignore
/README.md export-ignore
/phpmd.xml export-ignore
/phpunit.xml export-ignore
/phpstan.neon.dist export-ignore
/infection.json.dist export-ignore

/.github export-ignore
/.gitignore export-ignore
/.gitattributes export-ignore
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v3

- name: Use PHP 8.2
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'

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

Expand All @@ -33,6 +38,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v3

- name: Use PHP 8.2
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'

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

Expand Down
11 changes: 7 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
DOCKER_RUN = docker run --rm -it --net=host -v ${PWD}:/app -w /app gustavofreze/php:8.2

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

configure:
@${DOCKER_RUN} composer update --optimize-autoloader

test: review
test:
@${DOCKER_RUN} composer tests

test-no-coverage: review
test-file:
@${DOCKER_RUN} composer tests-file-no-coverage ${FILE}

test-no-coverage:
@${DOCKER_RUN} composer tests-no-coverage

review:
Expand All @@ -19,4 +22,4 @@ show-reports:

clean:
@sudo chown -R ${USER}:${USER} ${PWD}
@rm -rf report vendor
@rm -rf report vendor .phpunit.cache
31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,39 @@ composer require tiny-blocks/encoder

## How to use

The library exposes concrete implementations for encoding and decoding data.
The library provides concrete implementations of the `Encoder` interface, enabling encoding and decoding of data into
specific formats like Base62.

### Using Base62

To encode a value into Base62 format:

```php
$encoder = Base62::from(value: 'Hello world!');
$encoded = $encoder->encode();

# Output: T8dgcjRGuYUueWht
```

To decode a Base62-encoded value back to its original form:

```php
$encoded = Base62::encode(value: 'Hello world!') # T8dgcjRGuYUueWht
$encoder = Base62::from(value: 'T8dgcjRGuYUueWht');
$decoded = $encoder->decode();

# Output: Hello world!
```

Base62::decode(value: $encoded) # Hello world!
If you attempt to decode an invalid Base62 value, an `InvalidDecoding` exception will be thrown:

```php
try {
$encoder = Base62::from(value: 'invalid_value');
$decoded = $encoder->decode();
} catch (InvalidDecoding $exception) {
echo $exception->getMessage();
# Output: The value <invalid_value> could not be decoded.
}
```

<div id='license'></div>
Expand Down
21 changes: 13 additions & 8 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
"minimum-stability": "stable",
"keywords": [
"psr",
"psr-4",
"psr-12",
"base62",
"decoder",
"encoder",
Expand All @@ -21,6 +19,10 @@
"homepage": "https://github.com/gustavofreze"
}
],
"support": {
"issues": "https://github.com/tiny-blocks/encoder/issues",
"source": "https://github.com/tiny-blocks/encoder"
},
"config": {
"sort-packages": true,
"allow-plugins": {
Expand All @@ -38,28 +40,31 @@
}
},
"require": {
"php": "^8.1||^8.2",
"php": "^8.2",
"ext-gmp": "*"
},
"require-dev": {
"infection/infection": "^0.26",
"phpmd/phpmd": "^2.13",
"phpunit/phpunit": "^9.6",
"squizlabs/php_codesniffer": "^3.7"
"phpmd/phpmd": "^2.15",
"phpunit/phpunit": "^11",
"phpstan/phpstan": "^1",
"infection/infection": "^0.29",
"squizlabs/php_codesniffer": "^3.10"
},
"suggest": {
"ext-gmp": "Enables faster math with arbitrary-precision integers using GMP."
},
"scripts": {
"phpcs": "phpcs --standard=PSR12 --extensions=php ./src",
"phpmd": "phpmd ./src text phpmd.xml --suffixes php --ignore-violations-on-exit",
"phpstan": "phpstan analyse -c phpstan.neon.dist --quiet --no-progress",
"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"
"@phpmd",
"@phpstan"
],
"tests": [
"@test",
Expand Down
16 changes: 7 additions & 9 deletions infection.json.dist
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
{
"timeout": 10,
"testFramework": "phpunit",
"tmpDir": "report/",
"tmpDir": "report/infection/",
"source": {
"directories": [
"src"
]
},
"logs": {
"text": "report/logs/infection-text.log",
"summary": "report/logs/infection-summary.log"
"text": "report/infection/logs/infection-text.log",
"summary": "report/infection/logs/infection-summary.log"
},
"mutators": {
"@default": true,
"Concat": false,
"FalseValue": false,
"IncrementInteger": false,
"DecrementInteger": false,
"ConcatOperandRemoval": false
"UnwrapSubstr": false,
"LogicalAndNegation": false,
"LogicalAndAllSubExprNegation": false
},
"phpUnit": {
"configDir": "",
"customPath": "./vendor/bin/phpunit"
}
}
}
7 changes: 7 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
parameters:
paths:
- src
level: 9
tmpDir: report/phpstan
ignoreErrors:
reportUnmatchedIgnoredErrors: false
38 changes: 24 additions & 14 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
bootstrap="vendor/autoload.php"
cacheResultFile="report/.phpunit.result.cache"
backupGlobals="false"
backupStaticAttributes="false"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
cacheDirectory=".phpunit.cache"
beStrictAboutOutputDuringTests="true">

<source>
<include>
<directory>src</directory>
</include>
</source>

<testsuites>
<testsuite name="default">
<directory suffix="Test.php">tests</directory>
<directory>tests</directory>
</testsuite>
</testsuites>

<coverage>
<include>
<directory suffix=".php">src</directory>
</include>
<report>
<text outputFile="report/coverage.txt"/>
<html outputDirectory="report/html/"/>
<clover outputFile="report/coverage-clover.xml"/>
</report>
</coverage>

<logging>
<junit outputFile="report/execution-result.xml"/>
</logging>

</phpunit>
52 changes: 32 additions & 20 deletions src/Base62.php
Original file line number Diff line number Diff line change
@@ -1,60 +1,72 @@
<?php

declare(strict_types=1);

namespace TinyBlocks\Encoder;

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

final class Base62
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 static function encode(string $value): string
private function __construct(private string $value)
{
$bytes = 0;
$hexadecimal = bin2hex($value);
}

while (str_starts_with($hexadecimal, '00')) {
$bytes++;
$hexadecimal = substr($hexadecimal, 2);
}
public static function from(string $value): Encoder
{
return new Base62(value: $value);
}

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

$base62 = str_repeat(self::BASE62_ALPHABET[0], $bytes);

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

$number = gmp_init($hexadecimal, self::BASE62_HEXADECIMAL_RADIX);
$number = $hexadecimal->toGmpInit(base: self::BASE62_HEXADECIMAL_RADIX);

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

public static function decode(string $value): string
public function decode(): string
{
if (strlen($value) !== strspn($value, self::BASE62_ALPHABET)) {
throw new InvalidBase62Encoding(value: $value);
if (strlen($this->value) !== strspn($this->value, self::BASE62_ALPHABET)) {
throw new InvalidDecoding(value: $this->value);
}

$bytes = 0;
$value = $this->value;

while (!empty($value) && str_starts_with($value, self::BASE62_ALPHABET[0])) {
$bytes++;
$value = substr($value, 1);
$value = substr($value, self::BASE62_CHARACTER_LENGTH);
}

if (empty($value)) {
return str_repeat("\x00", $bytes);
}

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

$binary = hex2bin(sprintf('%s%s', str_repeat('00', $bytes), $hexadecimal->toString()));

if (strlen($hexadecimal) % 2) {
$hexadecimal = '0' . $hexadecimal;
if (!is_string($binary)) {
throw new InvalidDecoding(value: $this->value);
}

return hex2bin(str_repeat('00', $bytes) . $hexadecimal);
return $binary;
}
}
28 changes: 28 additions & 0 deletions src/Encoder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace TinyBlocks\Encoder;

use TinyBlocks\Encoder\Internal\Exceptions\InvalidDecoding;

/**
* Define a contract for encoding and decoding data.
*/
interface Encoder
{
/**
* Encodes the current value into a specific format.
*
* @return string The encoded value.
*/
public function encode(): string;

/**
* Decodes the current encoded value back to its original form.
*
* @return string The decoded value.
* @throws InvalidDecoding if decoding fails.
*/
public function decode(): string;
}
Loading

0 comments on commit 8dbd26f

Please sign in to comment.