Skip to content

Commit 6cf26b9

Browse files
committed
Make list container more strict
1 parent 1e99ad0 commit 6cf26b9

File tree

6 files changed

+78
-64
lines changed

6 files changed

+78
-64
lines changed

README.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -219,14 +219,19 @@ to enable manipulation their content.
219219
**EVERY CHANGE IN THE LIST WILL RE-INDEX THE LIST AS TO NOT EXPOSE MISSING INDEXES**
220220

221221
```php
222+
use Bakame\Http\StructuredFields\InnerList;
222223
use Bakame\Http\StructuredFields\OrderedList;
224+
use Bakame\Http\StructuredFields\Token;
223225

224-
$list = OrderedList::fromHttpValue('("foo" "bar"), ("baz"), ("bat" "one"), ()');
225-
$list->has(2); //return true
226-
$list->has(42); //return false
227-
$list->push(42);
228-
$list->remove(0, 2);
229-
echo $list->toHttpValue(); //returns "("baz"), (), 42.0"
226+
$innerList = InnerList::fromElements([42, 42.0, "42"], ["a" => true]);
227+
$innerList->has(2); //return true
228+
$innerList->has(42); //return false
229+
$innerList->push(new Token('forty-two'));
230+
$innerList->remove(0, 2);
231+
echo $innerList->toHttpValue(); //returns '(42.0 forty-two);a'
232+
233+
$orderedList = new OrderedList(Item::from("42", ["foo" => "bar"]), $innerList);
234+
echo $orderedList->toHttpValue(); //returns '"42";foo="bar", (42.0 forty-two);a'
230235
```
231236

232237
The distinction between `InnerList` and `OrderedList` is well explained in the

src/InnerList.php

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,30 @@
99
use IteratorAggregate;
1010

1111
/**
12-
* @implements IteratorAggregate<array-key, Item|null>
12+
* @implements IteratorAggregate<array-key, Item>
1313
*/
1414
final class InnerList implements Countable, IteratorAggregate, StructuredField, SupportsParameters
1515
{
16-
/** @var array<Item|null> */
16+
/** @var array<Item> */
1717
private array $elements;
18-
private Parameters $parameters;
18+
19+
public function __construct(private Parameters $parameters, Item ...$elements)
20+
{
21+
$this->elements = $elements;
22+
}
1923

2024
/**
21-
* @param iterable<Item|ByteSequence|Token|bool|int|float|string|null> $elements
25+
* @param iterable<Item|ByteSequence|Token|bool|int|float|string> $elements
2226
* @param iterable<string,Item|ByteSequence|Token|bool|int|float|string> $parameters
2327
*/
24-
public function __construct(iterable $elements = [], iterable $parameters = [])
28+
public static function fromElements(iterable $elements = [], iterable $parameters = []): self
2529
{
26-
$this->elements = [];
30+
$newElements = [];
2731
foreach ($elements as $element) {
28-
$this->push($element);
32+
$newElements[] = self::convertItem($element);
2933
}
3034

31-
$this->parameters = $parameters instanceof Parameters ? $parameters : Parameters::fromAssociative($parameters);
35+
return new self(Parameters::fromAssociative($parameters), ...$newElements);
3236
}
3337

3438
public static function fromHttpValue(string $httpValue): self
@@ -73,17 +77,17 @@ public static function fromHttpValue(string $httpValue): self
7377
}, []);
7478

7579
return new self(
76-
array_map(
80+
Parameters::fromHttpValue($found['parameters']),
81+
...array_filter(array_map(
7782
fn (string $field): Item|null => '' === $field ? null : Item::fromHttpValue($field),
7883
$components
79-
),
80-
Parameters::fromHttpValue($found['parameters'])
84+
))
8185
);
8286
}
8387

8488
public function toHttpValue(): string
8589
{
86-
$returnArray = array_map(fn (Item|null $value): string|null => $value?->toHttpValue(), $this->elements);
90+
$returnArray = array_map(fn (Item $value): string => $value->toHttpValue(), $this->elements);
8791

8892
return '('.implode(' ', $returnArray).')'.$this->parameters->toHttpValue();
8993
}
@@ -104,7 +108,7 @@ public function isEmpty(): bool
104108
}
105109

106110
/**
107-
* @return Iterator<Item|null>
111+
* @return Iterator<Item>
108112
*/
109113
public function getIterator(): Iterator
110114
{
@@ -129,7 +133,7 @@ private function filterIndex(int $index): int|null
129133
};
130134
}
131135

132-
public function get(int $index): Item|null
136+
public function get(int $index): Item
133137
{
134138
$offset = $this->filterIndex($index);
135139
if (null === $offset) {
@@ -139,27 +143,27 @@ public function get(int $index): Item|null
139143
return $this->elements[$offset];
140144
}
141145

142-
public function unshift(Item|ByteSequence|Token|bool|int|float|string|null ...$elements): void
146+
public function unshift(Item|ByteSequence|Token|bool|int|float|string ...$elements): void
143147
{
144148
$this->elements = [...array_map(self::convertItem(...), $elements), ...$this->elements];
145149
}
146150

147-
private static function convertItem(Item|ByteSequence|Token|bool|int|float|string|null $item): Item|null
151+
private static function convertItem(Item|ByteSequence|Token|bool|int|float|string $item): Item
148152
{
149153
return match (true) {
150-
$item instanceof Item, null === $item => $item,
154+
$item instanceof Item => $item,
151155
default => Item::from($item),
152156
};
153157
}
154158

155-
public function push(Item|ByteSequence|Token|bool|int|float|string|null ...$elements): void
159+
public function push(Item|ByteSequence|Token|bool|int|float|string ...$elements): void
156160
{
157161
foreach (array_map(self::convertItem(...), $elements) as $element) {
158162
$this->elements[] = $element;
159163
}
160164
}
161165

162-
public function insert(int $index, Item|ByteSequence|Token|bool|int|float|string|null ...$elements): void
166+
public function insert(int $index, Item|ByteSequence|Token|bool|int|float|string ...$elements): void
163167
{
164168
$offset = $this->filterIndex($index);
165169
match (true) {
@@ -170,7 +174,7 @@ public function insert(int $index, Item|ByteSequence|Token|bool|int|float|string
170174
};
171175
}
172176

173-
public function replace(int $index, Item|ByteSequence|Token|bool|int|float|string|null $element): void
177+
public function replace(int $index, Item|ByteSequence|Token|bool|int|float|string $element): void
174178
{
175179
if (!$this->has($index)) {
176180
throw InvalidOffset::dueToIndexNotFound($index);

src/InnerListTest.php

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public function it_can_be_instantiated_with_an_collection_of_item(): void
1919
$stringItem = Item::from('helloWorld');
2020
$booleanItem = Item::from(true);
2121
$arrayParams = [$stringItem, $booleanItem];
22-
$instance = new InnerList($arrayParams, Parameters::fromAssociative(['test' => Item::from(42)]));
22+
$instance = InnerList::fromElements($arrayParams, Parameters::fromAssociative(['test' => Item::from(42)]));
2323
self::assertFalse($instance->parameters()->isEmpty());
2424

2525
self::assertSame($stringItem, $instance->get(0));
@@ -36,10 +36,9 @@ public function it_can_add_or_remove_elements(): void
3636
$stringItem = Item::from('helloWorld');
3737
$booleanItem = Item::from(true);
3838
$arrayParams = [$stringItem, $booleanItem];
39-
$instance = new InnerList($arrayParams);
39+
$instance = InnerList::fromElements($arrayParams);
4040

4141
self::assertCount(2, $instance);
42-
self::assertNotNull($instance->get(1));
4342
self::assertTrue($instance->has(1));
4443
self::assertTrue($instance->parameters()->isEmpty());
4544

@@ -52,7 +51,6 @@ public function it_can_add_or_remove_elements(): void
5251
$instance->insert(1, );
5352
$element = $instance->get(1);
5453
self::assertCount(2, $instance);
55-
self::assertInstanceOf(Item::class, $element);
5654
self::assertIsString($element->value());
5755
self::assertStringContainsString('BarBaz', $element->value());
5856

@@ -76,7 +74,7 @@ public function it_fails_to_instantiate_with_wrong_parameters_in_field(): void
7674
*/
7775
public function it_can_unshift_insert_and_replace(): void
7876
{
79-
$container = new InnerList();
77+
$container = InnerList::fromElements();
8078
$container->unshift('42');
8179
$container->push(42);
8280
$container->insert(1, 42.0);
@@ -94,7 +92,7 @@ public function it_fails_to_replace_invalid_index(): void
9492
{
9593
$this->expectException(InvalidOffset::class);
9694

97-
$container = new InnerList();
95+
$container = InnerList::fromElements();
9896
$container->replace(0, ByteSequence::fromDecoded('Hello World'));
9997
}
10098

@@ -105,7 +103,7 @@ public function it_fails_to_insert_at_an_invalid_index(): void
105103
{
106104
$this->expectException(InvalidOffset::class);
107105

108-
$container = new InnerList();
106+
$container = InnerList::fromElements();
109107
$container->insert(3, ByteSequence::fromDecoded('Hello World'));
110108
}
111109

@@ -116,7 +114,7 @@ public function it_fails_to_return_an_member_with_invalid_index(): void
116114
{
117115
$this->expectException(InvalidOffset::class);
118116

119-
$instance = new InnerList();
117+
$instance = InnerList::fromElements();
120118
self::assertFalse($instance->has(3));
121119

122120
$instance->get(3);
@@ -127,10 +125,10 @@ public function it_fails_to_return_an_member_with_invalid_index(): void
127125
*/
128126
public function it_can_merge_one_or_more_instances(): void
129127
{
130-
$instance1 = new InnerList([false], ['foo' => 'bar']);
131-
$instance2 = new InnerList([true]);
132-
$instance3 = new InnerList([42], ['foo' => 'baz']);
133-
$expected = new InnerList([false, true, 42], ['foo' => 'baz']);
128+
$instance1 = InnerList::fromElements([false], ['foo' => 'bar']);
129+
$instance2 = InnerList::fromElements([true]);
130+
$instance3 = InnerList::fromElements([42], ['foo' => 'baz']);
131+
$expected = InnerList::fromElements([false, true, 42], ['foo' => 'baz']);
134132

135133
$instance1->merge($instance2, $instance3);
136134

@@ -143,7 +141,7 @@ public function it_can_merge_one_or_more_instances(): void
143141
*/
144142
public function it_can_merge_without_argument_and_not_throw(): void
145143
{
146-
$instance = new InnerList([false]);
144+
$instance = InnerList::fromElements([false]);
147145
$instance->merge();
148146
self::assertCount(1, $instance);
149147
}
@@ -153,10 +151,10 @@ public function it_can_merge_without_argument_and_not_throw(): void
153151
*/
154152
public function it_can_merge_two_or_more_instances_to_yield_different_result(): void
155153
{
156-
$instance1 = new InnerList([false], ['foo' => 'bar']);
157-
$instance2 = new InnerList([true]);
158-
$instance3 = new InnerList([42], ['foo' => 'baz']);
159-
$expected = new InnerList([42, true, false], ['foo' => 'bar']);
154+
$instance1 = InnerList::fromElements([false], ['foo' => 'bar']);
155+
$instance2 = InnerList::fromElements([true]);
156+
$instance3 = InnerList::fromElements([42], ['foo' => 'baz']);
157+
$expected = InnerList::fromElements([42, true, false], ['foo' => 'bar']);
160158

161159
$instance3->merge($instance2, $instance1);
162160

src/Item.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ final class Item implements StructuredField, SupportsParameters
88
{
99
private function __construct(
1010
private Token|ByteSequence|int|float|string|bool $value,
11-
private Parameters $parameters,
11+
private Parameters $parameters
1212
) {
1313
}
1414

@@ -17,14 +17,14 @@ private function __construct(
1717
*/
1818
public static function from(
1919
Token|ByteSequence|int|float|string|bool $value,
20-
iterable $parameters = [],
20+
iterable $parameters = []
2121
): self {
2222
return new self(match (true) {
2323
is_integer($value) => self::filterInteger($value),
2424
is_float($value) => self::filterDecimal($value),
2525
is_string($value) => self::filterString($value),
2626
default => $value,
27-
}, $parameters instanceof Parameters ? $parameters : Parameters::fromAssociative($parameters));
27+
}, Parameters::fromAssociative($parameters));
2828
}
2929

3030
public static function filterDecimal(float $value): float

src/OrderedList.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,22 @@ final class OrderedList implements Countable, IteratorAggregate, StructuredField
1616
/** @var array<Item|InnerList> */
1717
private array $elements;
1818

19+
public function __construct(Item|InnerList ...$elements)
20+
{
21+
$this->elements = $elements;
22+
}
23+
1924
/**
2025
* @param iterable<InnerList|Item|ByteSequence|Token|bool|int|float|string> $elements
2126
*/
22-
public function __construct(iterable $elements = [])
27+
public static function fromElements(iterable $elements = []): self
2328
{
24-
$this->elements = [];
29+
$newElements = [];
2530
foreach ($elements as $element) {
26-
$this->push($element);
31+
$newElements[] = self::filterElement($element);
2732
}
33+
34+
return new self(...$newElements);
2835
}
2936

3037
public static function fromHttpValue(string $httpValue): self

src/OrderedListTest.php

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public function it_can_be_instantiated_with_an_collection_of_item(): void
2323
$stringItem = Item::from('helloWorld');
2424
$booleanItem = Item::from(true);
2525
$arrayParams = [$stringItem, $booleanItem];
26-
$instance = new OrderedList($arrayParams);
26+
$instance = OrderedList::fromElements($arrayParams);
2727

2828
self::assertSame($stringItem, $instance->get(0));
2929
self::assertFalse($instance->isEmpty());
@@ -39,7 +39,7 @@ public function it_can_add_or_remove_elements(): void
3939
$stringItem = Item::from('helloWorld');
4040
$booleanItem = Item::from(true);
4141
$arrayParams = [$stringItem, $booleanItem];
42-
$instance = new OrderedList($arrayParams);
42+
$instance = OrderedList::fromElements($arrayParams);
4343

4444
self::assertCount(2, $instance);
4545
self::assertSame($booleanItem, $instance->get(1));
@@ -67,7 +67,7 @@ public function it_can_add_or_remove_elements(): void
6767
*/
6868
public function it_can_unshift_insert_and_replace(): void
6969
{
70-
$container = new OrderedList();
70+
$container = OrderedList::fromElements();
7171
$container->unshift(Item::from('42'));
7272
$container->push(Item::from(42));
7373
$container->insert(1, Item::from(42.0));
@@ -85,7 +85,7 @@ public function it_fails_to_replace_invalid_index(): void
8585
{
8686
$this->expectException(InvalidOffset::class);
8787

88-
$container = new OrderedList();
88+
$container = OrderedList::fromElements();
8989
$container->replace(0, Item::from(ByteSequence::fromDecoded('Hello World')));
9090
}
9191

@@ -96,7 +96,7 @@ public function it_fails_to_insert_at_an_invalid_index(): void
9696
{
9797
$this->expectException(InvalidOffset::class);
9898

99-
$container = new OrderedList();
99+
$container = OrderedList::fromElements();
100100
$container->insert(3, Item::from(ByteSequence::fromDecoded('Hello World')));
101101
}
102102

@@ -107,7 +107,7 @@ public function it_fails_to_return_an_member_with_invalid_index(): void
107107
{
108108
$this->expectException(InvalidOffset::class);
109109

110-
$instance = new OrderedList();
110+
$instance = OrderedList::fromElements();
111111
self::assertFalse($instance->has(3));
112112

113113
$instance->get(3);
@@ -118,10 +118,10 @@ public function it_fails_to_return_an_member_with_invalid_index(): void
118118
*/
119119
public function it_can_merge_one_or_more_instances(): void
120120
{
121-
$instance1 = new OrderedList([Item::from(false)]);
122-
$instance2 = new OrderedList([Item::from(true)]);
123-
$instance3 = new OrderedList([Item::from(42)]);
124-
$expected = new OrderedList([Item::from(false), Item::from(true), Item::from(42)]);
121+
$instance1 = OrderedList::fromElements([Item::from(false)]);
122+
$instance2 = OrderedList::fromElements([Item::from(true)]);
123+
$instance3 = OrderedList::fromElements([Item::from(42)]);
124+
$expected = OrderedList::fromElements([Item::from(false), Item::from(true), Item::from(42)]);
125125

126126
$instance1->merge($instance2, $instance3);
127127

@@ -134,10 +134,10 @@ public function it_can_merge_one_or_more_instances(): void
134134
*/
135135
public function it_can_merge_two_or_more_instances_to_yield_different_result(): void
136136
{
137-
$instance1 = new OrderedList([Item::from(false)]);
138-
$instance2 = new OrderedList([Item::from(true)]);
139-
$instance3 = new OrderedList([Item::from(42)]);
140-
$expected = new OrderedList([Item::from(42), Item::from(true), Item::from(false)]);
137+
$instance1 = OrderedList::fromElements([Item::from(false)]);
138+
$instance2 = OrderedList::fromElements([Item::from(true)]);
139+
$instance3 = OrderedList::fromElements([Item::from(42)]);
140+
$expected = OrderedList::fromElements([Item::from(42), Item::from(true), Item::from(false)]);
141141

142142
$instance3->merge($instance2, $instance1);
143143

@@ -150,7 +150,7 @@ public function it_can_merge_two_or_more_instances_to_yield_different_result():
150150
*/
151151
public function it_can_merge_without_argument_and_not_throw(): void
152152
{
153-
$instance = new OrderedList([Item::from(false)]);
153+
$instance = OrderedList::fromElements([Item::from(false)]);
154154
$instance->merge();
155155
self::assertCount(1, $instance);
156156
}

0 commit comments

Comments
 (0)