Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Adds improvements to decoding functionality. #6

Merged
merged 1 commit into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading