Skip to content

Commit 8dbd26f

Browse files
authored
refactor: Adds improvements to decoding functionality. (#6)
1 parent 15dd108 commit 8dbd26f

File tree

13 files changed

+300
-98
lines changed

13 files changed

+300
-98
lines changed

.gitattributes

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/tests export-ignore
2+
/vendor export-ignore
3+
4+
/LICENSE export-ignore
5+
/Makefile export-ignore
6+
/README.md export-ignore
7+
/phpmd.xml export-ignore
8+
/phpunit.xml export-ignore
9+
/phpstan.neon.dist export-ignore
10+
/infection.json.dist export-ignore
11+
12+
/.github export-ignore
13+
/.gitignore export-ignore
14+
/.gitattributes export-ignore

.github/workflows/ci.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ jobs:
1616
- name: Checkout
1717
uses: actions/checkout@v3
1818

19+
- name: Use PHP 8.2
20+
uses: shivammathur/setup-php@v2
21+
with:
22+
php-version: '8.2'
23+
1924
- name: Install dependencies
2025
run: composer update --no-progress --optimize-autoloader
2126

@@ -33,6 +38,11 @@ jobs:
3338
- name: Checkout
3439
uses: actions/checkout@v3
3540

41+
- name: Use PHP 8.2
42+
uses: shivammathur/setup-php@v2
43+
with:
44+
php-version: '8.2'
45+
3646
- name: Install dependencies
3747
run: composer update --no-progress --optimize-autoloader
3848

Makefile

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
DOCKER_RUN = docker run --rm -it --net=host -v ${PWD}:/app -w /app gustavofreze/php:8.2
22

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

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

8-
test: review
8+
test:
99
@${DOCKER_RUN} composer tests
1010

11-
test-no-coverage: review
11+
test-file:
12+
@${DOCKER_RUN} composer tests-file-no-coverage ${FILE}
13+
14+
test-no-coverage:
1215
@${DOCKER_RUN} composer tests-no-coverage
1316

1417
review:
@@ -19,4 +22,4 @@ show-reports:
1922

2023
clean:
2124
@sudo chown -R ${USER}:${USER} ${PWD}
22-
@rm -rf report vendor
25+
@rm -rf report vendor .phpunit.cache

README.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,39 @@ composer require tiny-blocks/encoder
2626

2727
## How to use
2828

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

3132
### Using Base62
3233

34+
To encode a value into Base62 format:
35+
36+
```php
37+
$encoder = Base62::from(value: 'Hello world!');
38+
$encoded = $encoder->encode();
39+
40+
# Output: T8dgcjRGuYUueWht
41+
```
42+
43+
To decode a Base62-encoded value back to its original form:
44+
3345
```php
34-
$encoded = Base62::encode(value: 'Hello world!') # T8dgcjRGuYUueWht
46+
$encoder = Base62::from(value: 'T8dgcjRGuYUueWht');
47+
$decoded = $encoder->decode();
48+
49+
# Output: Hello world!
50+
```
3551

36-
Base62::decode(value: $encoded) # Hello world!
52+
If you attempt to decode an invalid Base62 value, an `InvalidDecoding` exception will be thrown:
53+
54+
```php
55+
try {
56+
$encoder = Base62::from(value: 'invalid_value');
57+
$decoded = $encoder->decode();
58+
} catch (InvalidDecoding $exception) {
59+
echo $exception->getMessage();
60+
# Output: The value <invalid_value> could not be decoded.
61+
}
3762
```
3863

3964
<div id='license'></div>

composer.json

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
"minimum-stability": "stable",
99
"keywords": [
1010
"psr",
11-
"psr-4",
12-
"psr-12",
1311
"base62",
1412
"decoder",
1513
"encoder",
@@ -21,6 +19,10 @@
2119
"homepage": "https://github.com/gustavofreze"
2220
}
2321
],
22+
"support": {
23+
"issues": "https://github.com/tiny-blocks/encoder/issues",
24+
"source": "https://github.com/tiny-blocks/encoder"
25+
},
2426
"config": {
2527
"sort-packages": true,
2628
"allow-plugins": {
@@ -38,28 +40,31 @@
3840
}
3941
},
4042
"require": {
41-
"php": "^8.1||^8.2",
43+
"php": "^8.2",
4244
"ext-gmp": "*"
4345
},
4446
"require-dev": {
45-
"infection/infection": "^0.26",
46-
"phpmd/phpmd": "^2.13",
47-
"phpunit/phpunit": "^9.6",
48-
"squizlabs/php_codesniffer": "^3.7"
47+
"phpmd/phpmd": "^2.15",
48+
"phpunit/phpunit": "^11",
49+
"phpstan/phpstan": "^1",
50+
"infection/infection": "^0.29",
51+
"squizlabs/php_codesniffer": "^3.10"
4952
},
5053
"suggest": {
5154
"ext-gmp": "Enables faster math with arbitrary-precision integers using GMP."
5255
},
5356
"scripts": {
5457
"phpcs": "phpcs --standard=PSR12 --extensions=php ./src",
5558
"phpmd": "phpmd ./src text phpmd.xml --suffixes php --ignore-violations-on-exit",
59+
"phpstan": "phpstan analyse -c phpstan.neon.dist --quiet --no-progress",
5660
"test": "phpunit --log-junit=report/coverage/junit.xml --coverage-xml=report/coverage/coverage-xml --coverage-html=report/coverage/coverage-html tests",
5761
"test-mutation": "infection --only-covered --logger-html=report/coverage/mutation-report.html --coverage=report/coverage --min-msi=100 --min-covered-msi=100 --threads=4",
5862
"test-no-coverage": "phpunit --no-coverage",
5963
"test-mutation-no-coverage": "infection --only-covered --min-msi=100 --threads=4",
6064
"review": [
6165
"@phpcs",
62-
"@phpmd"
66+
"@phpmd",
67+
"@phpstan"
6368
],
6469
"tests": [
6570
"@test",

infection.json.dist

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
11
{
22
"timeout": 10,
33
"testFramework": "phpunit",
4-
"tmpDir": "report/",
4+
"tmpDir": "report/infection/",
55
"source": {
66
"directories": [
77
"src"
88
]
99
},
1010
"logs": {
11-
"text": "report/logs/infection-text.log",
12-
"summary": "report/logs/infection-summary.log"
11+
"text": "report/infection/logs/infection-text.log",
12+
"summary": "report/infection/logs/infection-summary.log"
1313
},
1414
"mutators": {
1515
"@default": true,
16-
"Concat": false,
17-
"FalseValue": false,
18-
"IncrementInteger": false,
19-
"DecrementInteger": false,
20-
"ConcatOperandRemoval": false
16+
"UnwrapSubstr": false,
17+
"LogicalAndNegation": false,
18+
"LogicalAndAllSubExprNegation": false
2119
},
2220
"phpUnit": {
2321
"configDir": "",
2422
"customPath": "./vendor/bin/phpunit"
2523
}
26-
}
24+
}

phpstan.neon.dist

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
parameters:
2+
paths:
3+
- src
4+
level: 9
5+
tmpDir: report/phpstan
6+
ignoreErrors:
7+
reportUnmatchedIgnoredErrors: false

phpunit.xml

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,35 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3-
bootstrap="vendor/autoload.php"
4-
cacheResultFile="report/.phpunit.result.cache"
5-
backupGlobals="false"
6-
backupStaticAttributes="false"
3+
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
74
colors="true"
8-
convertErrorsToExceptions="true"
9-
convertNoticesToExceptions="true"
10-
convertWarningsToExceptions="true"
11-
processIsolation="false"
12-
stopOnFailure="false"
13-
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
5+
bootstrap="vendor/autoload.php"
6+
failOnRisky="true"
7+
failOnWarning="true"
8+
cacheDirectory=".phpunit.cache"
9+
beStrictAboutOutputDuringTests="true">
10+
11+
<source>
12+
<include>
13+
<directory>src</directory>
14+
</include>
15+
</source>
16+
1417
<testsuites>
1518
<testsuite name="default">
16-
<directory suffix="Test.php">tests</directory>
19+
<directory>tests</directory>
1720
</testsuite>
1821
</testsuites>
1922

2023
<coverage>
21-
<include>
22-
<directory suffix=".php">src</directory>
23-
</include>
24+
<report>
25+
<text outputFile="report/coverage.txt"/>
26+
<html outputDirectory="report/html/"/>
27+
<clover outputFile="report/coverage-clover.xml"/>
28+
</report>
2429
</coverage>
30+
31+
<logging>
32+
<junit outputFile="report/execution-result.xml"/>
33+
</logging>
34+
2535
</phpunit>

src/Base62.php

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,72 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
namespace TinyBlocks\Encoder;
46

5-
use TinyBlocks\Encoder\Internal\Exceptions\InvalidBase62Encoding;
7+
use TinyBlocks\Encoder\Internal\Exceptions\InvalidDecoding;
8+
use TinyBlocks\Encoder\Internal\Hexadecimal;
69

7-
final class Base62
10+
final readonly class Base62 implements Encoder
811
{
912
private const BASE62_RADIX = 62;
1013
private const BASE62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
14+
private const BASE62_CHARACTER_LENGTH = 1;
1115
private const BASE62_HEXADECIMAL_RADIX = 16;
1216

13-
public static function encode(string $value): string
17+
private function __construct(private string $value)
1418
{
15-
$bytes = 0;
16-
$hexadecimal = bin2hex($value);
19+
}
1720

18-
while (str_starts_with($hexadecimal, '00')) {
19-
$bytes++;
20-
$hexadecimal = substr($hexadecimal, 2);
21-
}
21+
public static function from(string $value): Encoder
22+
{
23+
return new Base62(value: $value);
24+
}
25+
26+
public function encode(): string
27+
{
28+
$hexadecimal = Hexadecimal::fromBinary(binary: $this->value);
29+
$bytes = $hexadecimal->removeLeadingZeroBytes();
2230

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

25-
if (empty($hexadecimal)) {
33+
if ($hexadecimal->isEmpty()) {
2634
return $base62;
2735
}
2836

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

31-
return $base62 . gmp_strval($number, self::BASE62_RADIX);
39+
return sprintf('%s%s', $base62, gmp_strval($number, self::BASE62_RADIX));
3240
}
3341

34-
public static function decode(string $value): string
42+
public function decode(): string
3543
{
36-
if (strlen($value) !== strspn($value, self::BASE62_ALPHABET)) {
37-
throw new InvalidBase62Encoding(value: $value);
44+
if (strlen($this->value) !== strspn($this->value, self::BASE62_ALPHABET)) {
45+
throw new InvalidDecoding(value: $this->value);
3846
}
3947

4048
$bytes = 0;
49+
$value = $this->value;
4150

4251
while (!empty($value) && str_starts_with($value, self::BASE62_ALPHABET[0])) {
4352
$bytes++;
44-
$value = substr($value, 1);
53+
$value = substr($value, self::BASE62_CHARACTER_LENGTH);
4554
}
4655

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

5160
$number = gmp_init($value, self::BASE62_RADIX);
52-
$hexadecimal = gmp_strval($number, self::BASE62_HEXADECIMAL_RADIX);
61+
$hexadecimal = Hexadecimal::fromGmp(number: $number, base: self::BASE62_HEXADECIMAL_RADIX);
62+
$hexadecimal->padLeft();
63+
64+
$binary = hex2bin(sprintf('%s%s', str_repeat('00', $bytes), $hexadecimal->toString()));
5365

54-
if (strlen($hexadecimal) % 2) {
55-
$hexadecimal = '0' . $hexadecimal;
66+
if (!is_string($binary)) {
67+
throw new InvalidDecoding(value: $this->value);
5668
}
5769

58-
return hex2bin(str_repeat('00', $bytes) . $hexadecimal);
70+
return $binary;
5971
}
6072
}

src/Encoder.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TinyBlocks\Encoder;
6+
7+
use TinyBlocks\Encoder\Internal\Exceptions\InvalidDecoding;
8+
9+
/**
10+
* Define a contract for encoding and decoding data.
11+
*/
12+
interface Encoder
13+
{
14+
/**
15+
* Encodes the current value into a specific format.
16+
*
17+
* @return string The encoded value.
18+
*/
19+
public function encode(): string;
20+
21+
/**
22+
* Decodes the current encoded value back to its original form.
23+
*
24+
* @return string The decoded value.
25+
* @throws InvalidDecoding if decoding fails.
26+
*/
27+
public function decode(): string;
28+
}

0 commit comments

Comments
 (0)