44
55namespace Bakame \Http \StructuredFields ;
66
7+ use ArrayAccess ;
78use DateTimeInterface ;
89use Iterator ;
10+ use LogicException ;
911use Stringable ;
1012use function array_key_exists ;
1113use function array_keys ;
1517use function is_array ;
1618
1719/**
20+ * @see https://www.rfc-editor.org/rfc/rfc8941.html#section-3.2
21+ *
1822 * @implements MemberOrderedMap<string, Value|InnerList<int, Value>>
23+ * @implements ArrayAccess<string, Value|InnerList<int, Value>>
1924 * @phpstan-import-type DataType from Item
2025 */
21- final class Dictionary implements MemberOrderedMap
26+ final class Dictionary implements MemberOrderedMap, ArrayAccess
2227{
2328 /** @var array<string, Value|InnerList<int, Value>> */
2429 private array $ members = [];
2530
2631 /**
27- * @param iterable<string, InnerList<int, Value>|Value|DataType> $members
32+ * @param iterable<string, InnerList<int, Value>|iterable<Value|DataType>| Value|DataType> $members
2833 */
2934 private function __construct (iterable $ members = [])
3035 {
3136 foreach ($ members as $ key => $ member ) {
32- $ this ->set ($ key, $ member );
37+ $ this ->members [MapKey:: fromString ($ key)-> value ] = self :: filterMember ( $ member );
3338 }
3439 }
3540
41+ /**
42+ * @param StructuredField|iterable<Value|DataType>|DataType $member
43+ */
44+ private static function filterMember (iterable |StructuredField |Token |ByteSequence |DateTimeInterface |Stringable |string |int |float |bool $ member ): InnerList |Value
45+ {
46+ return match (true ) {
47+ $ member instanceof InnerList, $ member instanceof Value => $ member ,
48+ is_iterable ($ member ) => InnerList::fromList ($ member ),
49+ default => Item::from ($ member ),
50+ };
51+ }
52+
3653 /**
3754 * Returns a new instance.
3855 */
@@ -47,7 +64,7 @@ public static function create(): self
4764 * its keys represent the dictionary entry key
4865 * its values represent the dictionary entry value
4966 *
50- * @param iterable<string, InnerList<int, Value>|Value|DataType> $members
67+ * @param iterable<string, InnerList<int, Value>|list<Value|DataType>| Value|DataType> $members
5168 */
5269 public static function fromAssociative (iterable $ members ): self
5370 {
@@ -61,20 +78,19 @@ public static function fromAssociative(iterable $members): self
6178 * the first member represents the instance entry key
6279 * the second member represents the instance entry value
6380 *
64- * @param MemberOrderedMap<string, Value|InnerList<int, Value>>|iterable<array{0:string, 1:InnerList<int, Value>|Value|DataType}> $pairs
81+ * @param MemberOrderedMap<string, Value|InnerList<int, Value>>|iterable<array{0:string, 1:InnerList<int, Value>|list<Value|DataType>| Value|DataType}> $pairs
6582 */
66- public static function fromPairs (MemberOrderedMap | iterable $ pairs ): self
83+ public static function fromPairs (iterable $ pairs ): self
6784 {
6885 if ($ pairs instanceof MemberOrderedMap) {
6986 $ pairs = $ pairs ->toPairs ();
7087 }
7188
72- $ instance = new self ();
73- foreach ($ pairs as $ pair ) {
74- $ instance ->set (...$ pair );
75- }
76-
77- return $ instance ;
89+ return new self ((function (iterable $ pairs ) {
90+ foreach ($ pairs as [$ key , $ member ]) {
91+ yield $ key => $ member ;
92+ }
93+ })($ pairs ));
7894 }
7995
8096 /**
@@ -84,12 +100,11 @@ public static function fromPairs(MemberOrderedMap|iterable $pairs): self
84100 */
85101 public static function fromHttpValue (Stringable |string $ httpValue ): self
86102 {
87- $ instance = new self ();
88- foreach (Parser::parseDictionary ($ httpValue ) as $ key => $ value ) {
89- $ instance ->set ($ key , is_array ($ value ) ? InnerList::fromList (...$ value ) : $ value );
90- }
91-
92- return $ instance ;
103+ return new self ((function (iterable $ pairs ) {
104+ foreach ($ pairs as $ key => $ value ) {
105+ yield $ key => is_array ($ value ) ? InnerList::fromList (...$ value ) : $ value ;
106+ }
107+ })(Parser::parseDictionary ($ httpValue )));
93108 }
94109
95110 public function toHttpValue (): string
@@ -206,79 +221,64 @@ public function pair(int $index): array
206221 // @codeCoverageIgnoreEnd
207222 }
208223
209- /**
210- * @throws SyntaxError If the string key is not a valid
211- */
212- public function set (string $ key , StructuredField |Token |ByteSequence |DateTimeInterface |Stringable |string |int |float |bool $ member ): static
224+ public function add (string $ key , StructuredField |Token |ByteSequence |DateTimeInterface |Stringable |string |int |float |bool $ member ): static
213225 {
214- $ this ->members [MapKey::fromString ($ key )->value ] = self ::filterMember ($ member );
226+ $ members = $ this ->members ;
227+ $ members [MapKey::fromString ($ key )->value ] = self ::filterMember ($ member );
215228
216- return $ this ;
217- }
218-
219- private static function filterMember (StructuredField |Token |ByteSequence |DateTimeInterface |Stringable |string |int |float |bool $ member ): InnerList |Value
220- {
221- return match (true ) {
222- $ member instanceof InnerList, $ member instanceof Value => $ member ,
223- $ member instanceof StructuredField => throw new InvalidArgument ('Expecting a " ' .Value::class.'" or a " ' .InnerList::class.'" instance; received a " ' .$ member ::class.'" instead. ' ),
224- default => Item::from ($ member ),
225- };
229+ return new self ($ members );
226230 }
227231
228- public function delete (string ...$ keys ): static
232+ public function remove (string ...$ keys ): static
229233 {
234+ $ members = $ this ->members ;
230235 foreach ($ keys as $ key ) {
231- unset($ this -> members [$ key ]);
236+ unset($ members [$ key ]);
232237 }
233238
234- return $ this ;
235- }
236-
237- public function clear (): static
238- {
239- $ this ->members = [];
240-
241- return $ this ;
239+ return new self ($ members );
242240 }
243241
244242 public function append (string $ key , StructuredField |Token |ByteSequence |DateTimeInterface |Stringable |string |int |float |bool $ member ): static
245243 {
246- unset($ this ->members [$ key ]);
244+ $ members = $ this ->members ;
245+ unset($ members [$ key ]);
247246
248- return $ this -> set ( $ key , $ member );
247+ return new self ([... $ members , $ key => self :: filterMember ( $ member)] );
249248 }
250249
251250 public function prepend (string $ key , StructuredField |Token |ByteSequence |DateTimeInterface |Stringable |string |int |float |bool $ member ): static
252251 {
253- unset($ this ->members [$ key ]);
252+ $ members = $ this ->members ;
253+ unset($ members [$ key ]);
254254
255- $ this ->members = [...[MapKey::fromString ($ key )->value => self ::filterMember ($ member )], ...$ this ->members ];
256-
257- return $ this ;
255+ return new self ([$ key => self ::filterMember ($ member ), ...$ members ]);
258256 }
259257
260258 /**
261259 * @param iterable<string, InnerList<int, Value>|Value|DataType> ...$others
262260 */
263261 public function mergeAssociative (iterable ...$ others ): static
264262 {
263+ $ members = $ this ->members ;
265264 foreach ($ others as $ other ) {
266- $ this -> members = [...$ this -> members , ...self ::fromAssociative ($ other )->members ];
265+ $ members = [...$ members , ...self ::fromAssociative ($ other )->members ];
267266 }
268267
269- return $ this ;
268+ return new self ( $ members ) ;
270269 }
271270
272271 /**
273272 * @param MemberOrderedMap<string, Value|InnerList<int, Value>>|iterable<array{0:string, 1:InnerList<int, Value>|Value|DataType}> ...$others
274273 */
275274 public function mergePairs (MemberOrderedMap |iterable ...$ others ): static
276275 {
276+ $ members = $ this ->members ;
277277 foreach ($ others as $ other ) {
278- $ this -> members = [...$ this -> members , ...self ::fromPairs ($ other )->members ];
278+ $ members = [...$ members , ...self ::fromPairs ($ other )->members ];
279279 }
280280
281- return $ this ;
281+ return new self ( $ members ) ;
282282 }
283283
284284 /**
@@ -299,23 +299,13 @@ public function offsetGet(mixed $offset): InnerList|Value
299299 return $ this ->get ($ offset );
300300 }
301301
302- /**
303- * @param string $offset
304- */
305302 public function offsetUnset (mixed $ offset ): void
306303 {
307- $ this -> delete ( $ offset );
304+ throw new LogicException ( self ::class. ' instance can not be updated using ' .ArrayAccess::class. ' methods. ' );
308305 }
309306
310- /**
311- * @param InnerList<int, Value>|Value|DataType $value
312- */
313307 public function offsetSet (mixed $ offset , mixed $ value ): void
314308 {
315- if (!is_string ($ offset )) {
316- throw new SyntaxError ('The offset for a dictionary member is expected to be a string; " ' .gettype ($ offset ).'" given. ' );
317- }
318-
319- $ this ->set ($ offset , $ value );
309+ throw new LogicException (self ::class.' instance can not be updated using ' .ArrayAccess::class.' methods. ' );
320310 }
321311}
0 commit comments