Skip to content

Commit ee89c94

Browse files
committed
support for url-encoded format
1 parent 098a852 commit ee89c94

File tree

6 files changed

+195
-5
lines changed

6 files changed

+195
-5
lines changed

README.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,23 @@ composer require sunrise/coder
1919
```php
2020
use Sunrise\Coder\CodecManager;
2121
use Sunrise\Coder\Codec\JsonCodec;
22+
use Sunrise\Coder\Codec\UrlEncodedCodec;
2223
use Sunrise\Coder\Dictionary\MediaType;
2324

2425
$codecManager = new CodecManager(codecs: [
2526
new JsonCodec(),
27+
new UrlEncodedCodec(),
2628
]);
2729

2830
// Encoding result: {"foo": "bar"}
2931
$codecManager->encode(MediaType::JSON, ['foo' => 'bar']);
30-
3132
// Decoding result: ['foo' => 'bar']
3233
$codecManager->decode(MediaType::JSON, '{"foo": "bar"}');
34+
35+
// Encoding result: foo=bar
36+
$codecManager->encode(MediaType::UrlEncoded, ['foo' => 'bar']);
37+
// Decoding result: ['foo' => 'bar']
38+
$codecManager->decode(MediaType::UrlEncoded, 'foo=bar');
3339
```
3440

3541
### PHP-DI definitions
@@ -41,6 +47,7 @@ use Sunrise\Coder\CodecManagerInterface;
4147
$containerBuilder = new ContainerBuilder();
4248
$containerBuilder->addDefinition(__DIR__ . '/../vendor/sunrise/coder/resources/definitions/coder.php');
4349
$containerBuilder->addDefinition(__DIR__ . '/../vendor/sunrise/coder/resources/definitions/codecs/json_codec.php');
50+
$containerBuilder->addDefinition(__DIR__ . '/../vendor/sunrise/coder/resources/definitions/codecs/url_encoded_codec.php');
4451

4552
$container = $containerBuilder->build();
4653

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Sunrise\Coder\Codec\UrlEncodedCodec;
6+
7+
use function DI\add;
8+
use function DI\create;
9+
use function DI\get;
10+
11+
return [
12+
'coder.url_encoded_codec.context' => [],
13+
14+
'coder.codecs' => add([
15+
create(UrlEncodedCodec::class)
16+
->constructor(
17+
context: get('coder.url_encoded_codec.context'),
18+
),
19+
]),
20+
];

src/Codec/JsonCodec.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727

2828
final class JsonCodec implements CodecInterface
2929
{
30-
public const CONTEXT_KEY_DECODING_FLAGS = 'decoding_flags';
31-
public const CONTEXT_KEY_DECODING_MAX_DEPTH = 'decoding_max_depth';
32-
public const CONTEXT_KEY_ENCODING_FLAGS = 'encoding_flags';
33-
public const CONTEXT_KEY_ENCODING_MAX_DEPTH = 'encoding_max_depth';
30+
public const CONTEXT_KEY_DECODING_FLAGS = 'json_decoding_flags';
31+
public const CONTEXT_KEY_DECODING_MAX_DEPTH = 'json_decoding_max_depth';
32+
public const CONTEXT_KEY_ENCODING_FLAGS = 'json_encoding_flags';
33+
public const CONTEXT_KEY_ENCODING_MAX_DEPTH = 'json_encoding_max_depth';
3434

3535
private const DEFAULT_CODING_MAX_DEPTH = 512;
3636

src/Codec/UrlEncodedCodec.php

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
/**
4+
* It's free open-source software released under the MIT License.
5+
*
6+
* @author Anatoly Nekhay <[email protected]>
7+
* @copyright Copyright (c) 2025, Anatoly Nekhay
8+
* @license https://github.com/sunrise-php/coder/blob/master/LICENSE
9+
* @link https://github.com/sunrise-php/coder
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Sunrise\Coder\Codec;
15+
16+
use Sunrise\Coder\CodecInterface;
17+
use Sunrise\Coder\Dictionary\MediaType;
18+
19+
use function http_build_query;
20+
use function parse_str;
21+
22+
use const PHP_QUERY_RFC1738;
23+
24+
/**
25+
* @since 1.1.0
26+
*/
27+
final class UrlEncodedCodec implements CodecInterface
28+
{
29+
public const CONTEXT_KEY_ENCODING_TYPE = 'url_encoding_type';
30+
31+
private const DEFAULT_ENCODING_TYPE = PHP_QUERY_RFC1738;
32+
33+
/**
34+
* @param array<array-key, mixed> $context
35+
*/
36+
public function __construct(
37+
private readonly array $context = [],
38+
) {
39+
}
40+
41+
/**
42+
* @inheritDoc
43+
*/
44+
public function getSupportedMediaTypes(): array
45+
{
46+
return [MediaType::UrlEncoded];
47+
}
48+
49+
/**
50+
* @inheritDoc
51+
*/
52+
public function decode(string $data, array $context = []): mixed
53+
{
54+
parse_str($data, $result);
55+
56+
return $result;
57+
}
58+
59+
/**
60+
* @inheritDoc
61+
*/
62+
public function encode(mixed $data, array $context = []): string
63+
{
64+
$context += $this->context;
65+
66+
/** @var int $encodingType */
67+
$encodingType = $context[self::CONTEXT_KEY_ENCODING_TYPE] ?? self::DEFAULT_ENCODING_TYPE;
68+
69+
return http_build_query((array) $data, '', '&', $encodingType);
70+
}
71+
}

src/Dictionary/MediaType.php

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
enum MediaType: string implements MediaTypeInterface
1919
{
2020
case JSON = 'application/json';
21+
case UrlEncoded = 'application/x-www-form-urlencoded';
2122

2223
public function getIdentifier(): string
2324
{

tests/Codec/UrlEncodedCodecTest.php

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sunrise\Coder\Tests\Codec;
6+
7+
use Generator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use PHPUnit\Framework\TestCase;
10+
use Sunrise\Coder\Codec\UrlEncodedCodec;
11+
12+
use const PHP_QUERY_RFC3986;
13+
14+
final class UrlEncodedCodecTest extends TestCase
15+
{
16+
public function testSupportedMediaTypes(): void
17+
{
18+
$supportedMediaTypeIdentifiers = [];
19+
foreach ((new UrlEncodedCodec())->getSupportedMediaTypes() as $supportedMediaType) {
20+
$supportedMediaTypeIdentifiers[] = $supportedMediaType->getIdentifier();
21+
}
22+
23+
self::assertSame(['application/x-www-form-urlencoded'], $supportedMediaTypeIdentifiers);
24+
}
25+
26+
#[DataProvider('decodeDataProvider')]
27+
public function testDecode(mixed $expectedData, string $codingData, array $codingContext = [], array $codecContext = []): void
28+
{
29+
self::assertSame($expectedData, (new UrlEncodedCodec($codecContext))->decode($codingData, $codingContext));
30+
}
31+
32+
#[DataProvider('encodeDataProvider')]
33+
public function testEncode(string $expectedData, mixed $codingData, array $codingContext = [], array $codecContext = []): void
34+
{
35+
self::assertSame($expectedData, (new UrlEncodedCodec($codecContext))->encode($codingData, $codingContext));
36+
}
37+
38+
public static function decodeDataProvider(): Generator
39+
{
40+
yield [
41+
['foo' => 'bar'],
42+
'foo=bar',
43+
];
44+
45+
yield [
46+
['foo' => 'bar', 'bar' => 'baz'],
47+
'foo=bar&bar=baz',
48+
];
49+
50+
yield [
51+
['foo' => 'bar baz'],
52+
'foo=bar+baz',
53+
];
54+
55+
yield [
56+
['foo' => 'bar baz'],
57+
'foo=bar%20baz',
58+
];
59+
}
60+
61+
public static function encodeDataProvider(): Generator
62+
{
63+
yield [
64+
'foo=bar',
65+
['foo' => 'bar'],
66+
];
67+
68+
yield [
69+
'foo=bar&bar=baz',
70+
['foo' => 'bar', 'bar' => 'baz'],
71+
];
72+
73+
yield [
74+
'foo=bar+baz',
75+
['foo' => 'bar baz'],
76+
];
77+
78+
yield [
79+
'foo=bar%20baz',
80+
['foo' => 'bar baz'],
81+
[UrlEncodedCodec::CONTEXT_KEY_ENCODING_TYPE => PHP_QUERY_RFC3986],
82+
];
83+
84+
yield [
85+
'foo=bar%20baz',
86+
['foo' => 'bar baz'],
87+
[],
88+
[UrlEncodedCodec::CONTEXT_KEY_ENCODING_TYPE => PHP_QUERY_RFC3986],
89+
];
90+
}
91+
}

0 commit comments

Comments
 (0)