Skip to content

Commit 8a0a1b5

Browse files
committed
Inprove package DX
1 parent c912c1d commit 8a0a1b5

File tree

8 files changed

+164
-90
lines changed

8 files changed

+164
-90
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,25 @@
22

33
All Notable changes to `bakame/http-strucured-fields` will be documented in this file.
44

5+
## [Next](https://github.com/bakame-php/http-structured-fields/compare/1.2.2...master) - TBD
6+
7+
### Added
8+
9+
- `DataType::create` returns an `StructuredField` instance from an `iterable` construct.
10+
- `DataType::serialize` returns the HTTP string representation of a structured field.
11+
12+
### Fixed
13+
14+
- `InnerList::fromPair` should not throw if the pair is empty becuse an inner list can be emoty.
15+
16+
### Deprecated
17+
18+
- `DataType::build` use `DataType::serialize` instead.
19+
20+
### Removed
21+
22+
- None
23+
524
## [1.2.2](https://github.com/bakame-php/http-structured-fields/compare/1.2.1...1.2.2) - 2024-01-01
625

726
### Added

README.md

Lines changed: 111 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
[![Sponsor development of this project](https://img.shields.io/badge/sponsor%20this%20package-%E2%9D%A4-ff69b4.svg?style=flat-square)](https://github.com/sponsors/nyamsprod)
99

1010
`bakame/http-structured-fields` is a framework-agnostic PHP library that allows you to parse, serialize
11-
build and update HTTP Structured Fields in PHP according to the [RFC8941](https://www.rfc-editor.org/rfc/rfc8941.html).
11+
create and update HTTP Structured Fields in PHP according to the [RFC8941](https://www.rfc-editor.org/rfc/rfc8941.html).
1212

1313
Once installed you will be able to do the following:
1414

@@ -24,20 +24,20 @@ $field[2]->parameter('q'); // returns (float) 0.9
2424
$field[0]->value()->toString(); // returns 'text/html'
2525
$field[0]->parameter('q'); // returns null
2626

27-
//2 - building a Retrofit Cookie Header
28-
echo DataType::List->build([
27+
//2 - building a retrofit Cookie Header
28+
echo DataType::List->serialize([
2929
[
3030
['foo', 'bar'],
3131
[
32-
['expire', $expire],
32+
['expire', new DateTimeImmutable('2023-04-14 20:32:08')],
3333
['path', '/'],
3434
[ 'max-age', 2500],
3535
['secure', true],
3636
['httponly', true],
3737
['samesite', Token::fromString('lax')],
3838
]
3939
],
40-
]),
40+
]);
4141
// returns ("foo" "bar");expire=@1681504328;path="/";max-age=2500;secure;httponly=?0;samesite=lax
4242
```
4343

@@ -58,9 +58,11 @@ composer require bakame/http-structured-fields
5858
### Foreword
5959

6060
> [!CAUTION]
61-
> While this package parses and serializes the header value, it does not validate its content.
62-
It is still required to validate the parsed data against the constraints of the corresponding
63-
header. Content validation is out of scope for this library.
61+
> While this package parses and serializes the HTTP value, it does not validate its content
62+
> against any conformance rule. You are still required to perform a compliance check
63+
> against the constraints of the corresponding field. Content validation is
64+
> out of scope for this library even though you can leverage some of its feature to
65+
> ease the required validation.
6466
6567
### Parsing and Serializing Structured Fields
6668

@@ -69,7 +71,7 @@ header. Content validation is out of scope for this library.
6971
> [!NOTE]
7072
> New in version 1.2.0
7173
72-
To quickly parse or build one of the five (5) available data type according to the RFC, you can use the `DataType` enum.
74+
To quickly parse or serialize one of the five (5) available data type according to the RFC, you can use the `DataType` enum.
7375
Apart from listing the data types (`List`, `InnerList`, `Parameters`, `Dictionary` and `Item`) you can give to
7476
its `parse` method a string or a stringable object representing a field text representation. On success,
7577
it will return an object representing the structured field otherwise an exception will be thrown.
@@ -81,14 +83,36 @@ $field->value(); // returns Token::fromString('bar); the found token va
8183
$field->parameter('baz'); // returns 42; the value of the parameter or null if the parameter is not defined.
8284
```
8385

84-
On the other hand, `build` method expects an iterable structure composed of pair values
85-
that matches any structured field data type and returns its text representation.
86+
To complement the behaviour, you can use its `serialize` method to turn an iterable structure
87+
composed of pair values that matches any structured field data type and returns its
88+
text representation.
89+
90+
```php
91+
use Bakame\Http\StructuredFields\Item;
92+
use Bakame\Http\StructuredFields\DataType;
93+
94+
echo DataType::List->serialize([
95+
[
96+
'dumela lefatshe',
97+
[['a', false]]
98+
],
99+
[
100+
['a', 'b', Item::fromDateString('+30 minutes')],
101+
[['a', true]]
102+
],
103+
]);
104+
// display "dumela lefatshe";a=?0, ("a" "b" @1703319068);a
105+
```
106+
107+
The `serialize` method is a shortcut to converting the iterable structure into a `StructuredField` via
108+
the `create` method and calling on the newly created object its `toHttpValue` method. With that
109+
in mind, it is possible to rewrite The last example:
86110

87111
```php
88112
use Bakame\Http\StructuredFields\Item;
89113
use Bakame\Http\StructuredFields\DataType;
90114

91-
echo DataType::List->build([
115+
$list = DataType::List->create([
92116
[
93117
'dumela lefatshe',
94118
[['a', false]]
@@ -98,18 +122,20 @@ echo DataType::List->build([
98122
[['a', true]]
99123
],
100124
]);
125+
126+
echo $list->toHttpValue();
101127
// display "dumela lefatshe";a=?0, ("a" "b" @1703319068);a
102128
```
103129

104130
> [!TIP]
105131
> While the format can be overwhelming at first, you will come to understand it while reading
106132
> the rest of the documentation. Under the hood, the `DataType` enum uses the mechanism discussed hereafter.
107133
108-
#### Using specific named constructor
134+
#### Using specific data type classes
109135

110-
The package provides specific classes for each data type. Parsing the structured field value is done
111-
via the `fromHttpValue` named constructor. The method is attached to each library's structured
112-
field representation as shown below:
136+
The package provides specific classes for each data type. Parsing is done their respective
137+
`fromHttpValue` named constructor. A example of how the method works can be seen below
138+
using the `Item` class:
113139

114140
```php
115141
declare(strict_types=1);
@@ -127,9 +153,13 @@ $field->value(); // returns Token::fromString('bar); the found token va
127153
$field->parameter('baz'); // returns 42; the value of the parameter or null if the parameter is not defined.
128154
```
129155

156+
> [!TIP]
157+
> The `DataType::parse` method uses the `fromHttpValue` named constructor for
158+
> each specific class to generate the structured field PHP representation.
159+
130160
The `fromHttpValue` method returns an instance which implements the `StructuredField` interface.
131161
The interface provides the `toHttpValue` method that serializes it into a normalized RFC
132-
compliant HTTP field string value. To ease integration, the `__toString` method is
162+
compliant HTTP field string value. To ease integration, the `__toString` method is
133163
implemented as an alias to the `toHttpValue` method.
134164

135165
````php
@@ -145,6 +175,10 @@ header('foo: '. $field->toHttpValue());
145175
header('foo: '. $field);
146176
````
147177

178+
> [!TIP]
179+
> This is the mechanism used by the `DataType::serialize` method. Once the Structured
180+
> field has been created, the method calls its `toHttpValue` method.
181+
148182
All five (5) structured data type as defined in the RFC are provided inside the
149183
`Bakame\Http\StructuredFields` namespace. They all implement the
150184
`StructuredField` interface and expose a `fromHttpValue` named constructor:
@@ -155,65 +189,13 @@ All five (5) structured data type as defined in the RFC are provided inside the
155189
- `OuterList` (named `List` in the RFC but renamed in the package because `list` is a reserved word in PHP.)
156190
- `InnerList`
157191

158-
#### Advance parsing usage
159-
160-
Starting with version `1.1` the internal parser has been made public in order to allow:
161-
162-
- clearer decoupling between parsing and objet building
163-
- different parsers implementations
164-
- improve the package usage in testing.
165-
166-
Each `fromHttpValue` method signature has been updated to take a second optional argument
167-
that represents the parser interface to use in order to allow parsing of the HTTP string
168-
representation value.
169-
170-
By default, if no parser is provided, the package will default to use the package `Parser` class,
171-
172-
```php
173-
Item::fromHttpValue(Stringable|string $httpValue, ItemParser $parser = new Parser()): Item;
174-
InnerList::fromHttpValue(Stringable|string $httpValue, InnerListParser $parser = new Parser()): InnerList;
175-
Dictionary::fromHttpValue(Stringable|string $httpValue, DictionaryParser $parser = new Parser()): Dictionary;
176-
OuterList::fromHttpValue(Stringable|string $httpValue, ListParser $parser = new Parser()): OuterList;
177-
Parameters::fromHttpValue(Stringable|string $httpValue, ParametersParser $parser = new Parser()): Parameters;
178-
```
179-
180-
The `Parser` class exposes the following method each belonging to a different contract or interface.
181-
182-
```php
183-
Parser::parseValue(Stringable|string $httpValue): ByteSequence|Token|DateTimeImmutable|string|int|float|bool;
184-
Parser::parseItem(Stringable|string $httpValue): array;
185-
Parser::parseParameters(Stringable|string $httpValue): array;
186-
Parser::parseInnerList(Stringable|string $httpValue): array;
187-
Parser::parseList(Stringable|string $httpValue): array;
188-
Parser::parseDictionary(Stringable|string $httpValue): array;
189-
```
190-
191-
Once instantiated, calling one of the above listed method is straightforward:
192-
193-
```php
194-
use Bakame\Http\StructuredFields\Parser;
195-
196-
$parser = new Parser();
197-
$parser->parseValue('text/csv'); //returns Token::fromString('text/csv')
198-
$parser->parseItem('@1234567890;file=24');
199-
//returns an array
200-
// [
201-
// new DateTimeImmutable('@1234567890'),
202-
// ['file' => 24],
203-
// ]
204-
```
205-
206-
> [!NOTE]
207-
> While the provided default `Parser` class implements all these methods you are free to only implement
208-
the methods you need.
209-
210192
### Accessing Structured Fields Values
211193

212194
#### RFC Value type
213195

214-
Per the RFC, items can have different types that are translated to PHP using:
196+
Per the RFC, items value can have different types that are translated to PHP using:
215197

216-
- native type where possible
198+
- native type or classes where possible;
217199
- specific classes defined in the package namespace to represent non-native type
218200

219201
The table below summarizes the item value type.
@@ -231,13 +213,13 @@ The table below summarizes the item value type.
231213

232214
> [!NOTE]
233215
> The `Date` and `DisplayString` type are not yet part of any accepted
234-
> RFC. But they are already added as new types in the superseeding
216+
> RFC. But they are already added as new types in the super-seeding
235217
> RFC proposal.
236218
>
237219
> See https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-sfbis
238220
> for more information.
239221
240-
The Enum `Type` which list all available types can be used to determine the RFC type
222+
The Enum `Type` list all available types and can be used to determine the RFC type
241223
corresponding to a PHP structure using the `Type::fromVariable` static method.
242224
The method will throw if the structure is not recognized. Alternatively
243225
it is possible to use the `Type::tryFromVariable` which will instead
@@ -311,8 +293,8 @@ $displayString->type(); // returns Type::DisplayString
311293
```
312294

313295
> [!WARNING]
314-
> The classes DO NOT expose the `Stringable` interface to distinguish them
315-
> from a regular string or a string like object
296+
> The classes DO NOT expose the `Stringable` interface to help distinguish
297+
> them from a string or a stringable object
316298
317299
#### Item
318300

@@ -406,7 +388,7 @@ use Bakame\Http\StructuredFields\ByteSequence;
406388
use Bakame\Http\StructuredFields\Item;
407389
use Bakame\Http\StructuredFields\Token;
408390

409-
Item:new(DateTimeInterface|ByteSequence|Token|DisplayString|string|int|float|bool $value): self
391+
Item:new(DateTimeInterface|ByteSequence|Token|DisplayString|string|int|array|float|bool $value): self
410392
Item::fromDecodedByteSequence(Stringable|string $value): self;
411393
Item::fromEncodedDisplayString(Stringable|string $value): self;
412394
Item::fromDecodedDisplayString(Stringable|string $value): self;
@@ -674,7 +656,7 @@ $list = OuterList::fromPairs([
674656
[
675657
['foo', 'bar'],
676658
[
677-
['expire', $expire],
659+
['expire', new DateTime('2024-01-01 12:33:45')],
678660
['path', '/'],
679661
[ 'max-age', 2500],
680662
['secure', true],
@@ -784,6 +766,58 @@ echo InnerList::new('foo', 'bar')
784766
// ("foo" "bar");expire=@1681538756;path="/";max-age=2500
785767
```
786768

769+
### Advance parsing usage
770+
771+
Starting with version `1.1` the internal parser has been made public in order to allow:
772+
773+
- clearer decoupling between parsing and objet building
774+
- different parsers implementations
775+
- improve the package usage in testing.
776+
777+
Each `fromHttpValue` method signature has been updated to take a second optional argument
778+
that represents the parser interface to use in order to allow parsing of the HTTP string
779+
representation value.
780+
781+
By default, if no parser is provided, the package will default to use the package `Parser` class,
782+
783+
```php
784+
Item::fromHttpValue(Stringable|string $httpValue, ItemParser $parser = new Parser()): Item;
785+
InnerList::fromHttpValue(Stringable|string $httpValue, InnerListParser $parser = new Parser()): InnerList;
786+
Dictionary::fromHttpValue(Stringable|string $httpValue, DictionaryParser $parser = new Parser()): Dictionary;
787+
OuterList::fromHttpValue(Stringable|string $httpValue, ListParser $parser = new Parser()): OuterList;
788+
Parameters::fromHttpValue(Stringable|string $httpValue, ParametersParser $parser = new Parser()): Parameters;
789+
```
790+
791+
The `Parser` class exposes the following method each belonging to a different contract or interface.
792+
793+
```php
794+
Parser::parseValue(Stringable|string $httpValue): ByteSequence|Token|DateTimeImmutable|string|int|float|bool;
795+
Parser::parseItem(Stringable|string $httpValue): array;
796+
Parser::parseParameters(Stringable|string $httpValue): array;
797+
Parser::parseInnerList(Stringable|string $httpValue): array;
798+
Parser::parseList(Stringable|string $httpValue): array;
799+
Parser::parseDictionary(Stringable|string $httpValue): array;
800+
```
801+
802+
Once instantiated, calling one of the above listed method is straightforward:
803+
804+
```php
805+
use Bakame\Http\StructuredFields\Parser;
806+
807+
$parser = new Parser();
808+
$parser->parseValue('text/csv'); //returns Token::fromString('text/csv')
809+
$parser->parseItem('@1234567890;file=24');
810+
//returns an array
811+
// [
812+
// new DateTimeImmutable('@1234567890'),
813+
// ['file' => 24],
814+
// ]
815+
```
816+
817+
> [!NOTE]
818+
> While the provided default `Parser` class implements all these methods you are free to only implement
819+
the methods you need.
820+
787821
## Contributing
788822

789823
Contributions are welcome and will be fully credited. Please see [CONTRIBUTING](.github/CONTRIBUTING.md) and [CODE OF CONDUCT](.github/CODE_OF_CONDUCT.md) for details.

phpstan.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ parameters:
1111
ignoreErrors:
1212
- message: '#it_fails_to_create_an_item_from_an_array_of_pairs\(\)#'
1313
path: tests/ItemTest.php
14-
- message: '#Method Bakame\\Http\\StructuredFields\\DataType::build\(\) has parameter \$data with no value type specified in iterable type iterable.#'
14+
- message: '#Method Bakame\\Http\\StructuredFields\\DataType::(serialize|build|create)\(\) has parameter \$data with no value type specified in iterable type iterable.#'
1515
path: src/DataType.php
1616
excludePaths:
1717
- tests/Record.php

src/DataType.php

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,35 @@ public function parse(Stringable|string $httpValue): StructuredField
3131
/**
3232
* @throws StructuredFieldError
3333
*/
34-
public function build(iterable $data): string
34+
public function serialize(iterable $data): string
35+
{
36+
return $this->create($data)->toHttpValue();
37+
}
38+
39+
/**
40+
* @throws StructuredFieldError
41+
*/
42+
public function create(iterable $data): StructuredField
3543
{
36-
return (match ($this) {
44+
return match ($this) {
3745
self::List => OuterList::fromPairs($data),
3846
self::InnerList => InnerList::fromPair([...$data]), /* @phpstan-ignore-line */
3947
self::Parameters => Parameters::fromPairs($data),
4048
self::Dictionary => Dictionary::fromPairs($data),
4149
self::Item => Item::fromPair([...$data]), /* @phpstan-ignore-line */
42-
})->toHttpValue();
50+
};
51+
}
52+
53+
/**
54+
* DEPRECATION WARNING! This method will be removed in the next major point release.
55+
*
56+
* @deprecated Since version 1.3.0
57+
* @codeCoverageIgnore
58+
*
59+
* @see DataType::serialize()
60+
*/
61+
public function build(iterable $data): string
62+
{
63+
return $this->serialize($data);
4364
}
4465
}

0 commit comments

Comments
 (0)