Skip to content

Commit 1e99ad0

Browse files
committed
Improve Ordered Map public API
1 parent ef489a9 commit 1e99ad0

File tree

8 files changed

+258
-131
lines changed

8 files changed

+258
-131
lines changed

README.md

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ Structured Field Values for PHP
66
[![Build](https://github.com/bakame-php/http-structured-fields/workflows/build/badge.svg)](https://github.com/bakame-php/http-structured-fields/actions?query=workflow%3A%22build%22)
77

88
The package uses pragmatic value objects to parse and serialize [HTTP Structured Fields][1] in PHP.
9-
Structured fields are intended for use by specifications of new HTTP fields that wish to use a
10-
common syntax that is more restrictive than traditional HTTP field values.
9+
HTTP Structured fields are intended for use by specifications of new HTTP fields that wish to
10+
use a common syntax that is more restrictive than traditional HTTP field values or could be
11+
used to [retrofit current headers](https://www.ietf.org/id/draft-ietf-httpbis-retrofit-00.html) to have them compliant with the new syntax.
1112

12-
You will be able to:
13+
The package can be used to:
1314

1415
- parse and serialize HTTP Structured Fields
1516
- create and update HTTP Structured Fields in a predicable way;
@@ -25,7 +26,7 @@ echo $fields->toHttpValue(); //display "/terms";rel="copyright";anchor="#foo"
2526
System Requirements
2627
-------
2728

28-
- You require **PHP >= 8.1** but the latest stable version of PHP is recommended
29+
**PHP >= 8.1** is required but the latest stable version of PHP is recommended.
2930

3031
Installation
3132
------------
@@ -51,7 +52,7 @@ For each of those top-level types, the package provide a dedicated value object
5152
and to serialize the value object back to the textual representation.
5253

5354
- Parsing is done via a common named constructor `fromHttpValue` which expects the Header or Trailer string value.
54-
- Serializing is done via a common `toHttpValue` public method. The method returns the normalized string representation suited for HTTP textual representation
55+
- Serializing is done via a common `toHttpValue` public method. The method returns the normalized string representation suited for HTTP textual representation.
5556

5657
```php
5758
use Bakame\Http\StructuredFields\Dictionary;
@@ -68,14 +69,14 @@ $item = Item::fromHttpValue('"foo";a=1;b=2"');
6869
echo $item->toHttpValue(); // "foo";a=1;b=2
6970
```
7071

71-
## Structured Data Types
72+
## Manipulating Structured Fields Value Objects
7273

7374
### Items
7475

7576
#### Types
7677

77-
Bare types defined in the RFC are translated to PHP
78-
native type when possible. Two additional classes:
78+
Item types [defined in the RFC](https://www.rfc-editor.org/rfc/rfc8941.html#section-3.3) are translated to PHP native type when possible.
79+
Two additional classes
7980

8081
- `Bakame\Http\StructuredFields\Token` and
8182
- `Bakame\Http\StructuredFields\ByteSequence`
@@ -93,10 +94,10 @@ are used to represent non-native types as shown in the table below:
9394

9495
#### Parameters
9596

96-
As explain in the RFC, `Parameters` are containers of `Item` instances. They can be associated
97-
to an `Item` instance or other container types **BUT** the items it contains can not
98-
themselves contain `Parameters` instance. More on parameters public API
99-
will be cover in subsequent paragraphs.
97+
As explain in the RFC, `Parameters` are an ordered map of key-value pairs that can be
98+
associated with an `Item`. They can be associated **BUT** the items they contain
99+
can not themselves contain `Parameters` instance. More on parameters
100+
public API will be cover in subsequent paragraphs.
100101

101102
#### Usage
102103

@@ -109,13 +110,13 @@ $item = Item::from("hello world", ["a" => 1]);
109110
$item->value(); //returns "hello world"
110111
$item->isString(); //return true
111112
$item->isToken(); //return false
112-
$item->parameters()->getByKey("a")->value(); //returns 1
113+
$item->parameters()->get("a")->value(); //returns 1
113114
```
114115

115116
Once instantiated, accessing `Item` properties is done via two methods:
116117

117118
- `Item::value()` which returns the instance underlying value
118-
- `Item::parameters()` which returns the item associated parameters as a distinct `Parameters` object
119+
- `Item::parameters()` which returns the parameters associated to the `Item` as a distinct `Parameters` object
119120

120121
**To instantiate a decimal number a float MUST be used as the first argument input.**
121122

@@ -134,19 +135,24 @@ $item->isInteger(); //return true
134135
### Containers
135136

136137
Apart from the `Item`, the RFC defines different containers with different requirements. The
137-
package exposes those containers via the following value objects `Parameters`, `Dictionary`,
138-
`InnerList` and `OrderedList` with the same basic public API. At any given time it
139-
is possible to:
138+
package exposes those containers via the following value objects:
139+
140+
- `Parameters`,
141+
- `Dictionary`,
142+
- `InnerList`,
143+
- and `OrderedList` to represent a generic list,
144+
145+
At any given time it is possible with each of these objects to:
140146

141147
- iterate over each contained element and its optional associated key via the `IteratorAggregate` interface;
142148
- tell whether the container is empty via an `isEmpty` method;
143149
- know the number of elements contained in the container via the `Countable` interface;
144-
- merge multiple instance of the same container using the `merge` method;
150+
- merge multiple instance of **the same type** using the `merge` method;
145151

146152
```php
147153
use Bakame\Http\StructuredFields\Parameters;
148154

149-
$parameters = new Parameters(['a' => 1, 'b' => 2, 'c' => Item::from("hello world")]);
155+
$parameters = Parameters::fromAssociative(['a' => 1, 'b' => 2, 'c' => "hello world"]);
150156
count($parameters); // return 2
151157
$parameters->isEmpty(); // returns false
152158
$parameters->toHttpValue(); // return ";a=1;b=2"
@@ -157,34 +163,39 @@ $parameters->toHttpValue(); // return ";a=1;b=2"
157163
The `Parameters` and the `Dictionary` classes allow associating a string
158164
key to its members as such they expose the following methods:
159165

166+
- `fromAssociative` a named constructor to instantiate the container with an associative array;
167+
- `fromPairs` a named constructor to instantiate the container with a list of key-value pairs;
168+
- `has` tell whether a specific element is associated to a given `key`;
169+
- `get` returns the element associated to a specific `key`;
170+
- `hasPair` tell whether a `key-value` association exists at a given `index` (negative indexes are supported);
171+
- `pair` returns the key-pair association present at a specific `index` (negative indexes are supported);
172+
- `pairs` returns an iterator to iterate over the container pairs;
160173
- `set` add an element at the end of the container if the key is new otherwise only the value is updated;
161174
- `append` always add an element at the end of the container, if already present the previous value is removed;
162175
- `prepend` always add an element at the beginning of the container, if already present the previous value is removed;
163176
- `delete` to remove elements based on their associated keys;
164-
- tell whether an element is attached to the container using its `index` or `key` via `hasIndex` and `hasKey` methods;
165-
- get any element by its string `key` or by its integer `index` via `getByKey` and `getByIndex` methods when applicable;
166177

167178
```php
168179
use Bakame\Http\StructuredFields\Dictionary;
169180
use Bakame\Http\StructuredFields\Item;
170181

171-
$dictionary = new Dictionary();
172-
$dictionary->set('b', true);
182+
$dictionary = Dictionary::fromPairs([['b', true]]);
173183
$dictionary->append('c', Item::from(true, ['foo' => new Token('bar')]));
174184
$dictionary->prepend('a', false);
175185
$dictionary->toHttpValue(); //returns "a=?0, b, c;foo=bar"
176-
$dictionary->hasKey('a'); //return true
177-
$dictionary->hasKey('foo'); //return false
178-
$dictionary->getByIndex(1); //return Item::fromBoolean(true)
179-
$dictionary->hasIndex(-1); //return Item::fromBoolean(true)
186+
$dictionary->has('a'); //return true
187+
$dictionary->has('foo'); //return false
188+
$dictionary->pair(1); //return ['b', Item::fromBoolean(true)]
189+
$dictionary->hasPair(-1); //return true
180190
$dictionary->append('z', 42.0);
181191
$dictionary->delete('b', 'c');
182192
echo $dictionary->toHttpValue(); //returns "a=?0, z=42.0"
183193
```
184194

185195
**Item types are inferred using `Item::from` if a `Item` object is not submitted.**
186196

187-
- `getByIndex` supports negative index
197+
**EVERY CHANGE IN THE ORDERED MAP WILL RE-INDEX THE PAIRS AS TO NOT EXPOSE MISSING INDEXES**
198+
188199
- `Parameters` can only contains `Item` instances
189200
- `Dictionary` instance can contain `Item` and `InnerList` instances.
190201

@@ -233,11 +244,12 @@ Contributions are welcome and will be fully credited. Please see [CONTRIBUTING](
233244
Testing
234245
-------
235246

236-
The library has a :
247+
The library :
237248

238-
- a [PHPUnit](https://phpunit.de) test suite
239-
- a coding style compliance test suite using [PHP CS Fixer](https://cs.sensiolabs.org/).
240-
- a code analysis compliance test suite using [PHPStan](https://github.com/phpstan/phpstan).
249+
- has a [PHPUnit](https://phpunit.de) test suite
250+
- has a coding style compliance test suite using [PHP CS Fixer](https://cs.sensiolabs.org/).
251+
- has a code analysis compliance test suite using [PHPStan](https://github.com/phpstan/phpstan).
252+
- is compliant with [the language agnostic HTTP Structured Fields Test suite](https://github.com/httpwg/structured-field-tests).
241253

242254
To run the tests, run the following command from the project folder.
243255

src/Dictionary.php

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,36 @@
1313
*/
1414
final class Dictionary implements Countable, IteratorAggregate, StructuredField
1515
{
16-
/** @var array<string, Item|InnerList> */
17-
private array $elements;
16+
private function __construct(
17+
/** @var array<string, Item|InnerList> */
18+
private array $elements = []
19+
) {
20+
}
1821

1922
/**
2023
* @param iterable<string, InnerList|Item|ByteSequence|Token|bool|int|float|string> $elements
2124
*/
22-
public function __construct(iterable $elements = [])
25+
public static function fromAssociative(iterable $elements = []): self
2326
{
24-
$this->elements = [];
27+
$instance = new self();
2528
foreach ($elements as $index => $element) {
26-
$this->set($index, $element);
29+
$instance->set($index, $element);
30+
}
31+
32+
return $instance;
33+
}
34+
35+
/**
36+
* @param iterable<array{0:string, 1:InnerList|Item|ByteSequence|Token|bool|int|float|string}> $pairs
37+
*/
38+
public static function fromPairs(iterable $pairs = []): self
39+
{
40+
$instance = new self();
41+
foreach ($pairs as [$key, $element]) {
42+
$instance->set($key, $element);
2743
}
44+
45+
return $instance;
2846
}
2947

3048
public static function fromHttpValue(string $httpValue): self
@@ -114,6 +132,16 @@ public function getIterator(): Iterator
114132
}
115133
}
116134

135+
/**
136+
* @return Iterator<array{0:string, 1:Item|InnerList}>
137+
*/
138+
public function toPairs(): Iterator
139+
{
140+
foreach ($this->elements as $index => $element) {
141+
yield [$index, $element];
142+
}
143+
}
144+
117145
/**
118146
* @return array<string>
119147
*/
@@ -122,12 +150,12 @@ public function keys(): array
122150
return array_keys($this->elements);
123151
}
124152

125-
public function hasKey(string $key): bool
153+
public function has(string $key): bool
126154
{
127155
return array_key_exists($key, $this->elements);
128156
}
129157

130-
public function getByKey(string $key): Item|InnerList|null
158+
public function get(string $key): Item|InnerList
131159
{
132160
if (!array_key_exists($key, $this->elements)) {
133161
throw InvalidOffset::dueToKeyNotFound($key);
@@ -136,7 +164,7 @@ public function getByKey(string $key): Item|InnerList|null
136164
return $this->elements[$key];
137165
}
138166

139-
public function hasIndex(int $index): bool
167+
public function hasPair(int $index): bool
140168
{
141169
return null !== $this->filterIndex($index);
142170
}
@@ -152,14 +180,20 @@ private function filterIndex(int $index): int|null
152180
};
153181
}
154182

155-
public function getByIndex(int $index): Item|InnerList|null
183+
/**
184+
* @return array{0:string, 1:Item|InnerList}
185+
*/
186+
public function pair(int $index): array
156187
{
157188
$offset = $this->filterIndex($index);
158189
if (null === $offset) {
159190
throw InvalidOffset::dueToIndexNotFound($index);
160191
}
161192

162-
return array_values($this->elements)[$offset];
193+
return [
194+
array_keys($this->elements)[$offset],
195+
array_values($this->elements)[$offset],
196+
];
163197
}
164198

165199
public function set(string $key, InnerList|Item|ByteSequence|Token|bool|int|float|string $element): void

0 commit comments

Comments
 (0)