Skip to content

Commit cef2318

Browse files
committed
[#566] Replace vendor response headers container
1 parent bb12b3c commit cef2318

4 files changed

Lines changed: 176 additions & 6 deletions

File tree

src/HttpClient/Adapters/CurlAdapter.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
namespace Quantum\HttpClient\Adapters;
1212

1313
use Quantum\HttpClient\Contracts\CurlAdapterInterface;
14-
use Curl\CaseInsensitiveArray;
14+
use Quantum\HttpClient\ResponseHeaders;
1515
use JsonSerializable;
1616
use RuntimeException;
1717
use CurlHandle;
@@ -46,7 +46,7 @@ class CurlAdapter implements CurlAdapterInterface
4646
*/
4747
private $response;
4848

49-
private CaseInsensitiveArray $responseHeaders;
49+
private ResponseHeaders $responseHeaders;
5050

5151
/**
5252
* @var array<string, mixed>
@@ -74,7 +74,7 @@ public function __construct(?Curl $client = null)
7474
}
7575

7676
$this->handle = $handle;
77-
$this->responseHeaders = new CaseInsensitiveArray();
77+
$this->responseHeaders = new ResponseHeaders();
7878
$this->applyOption(CURLOPT_RETURNTRANSFER, true);
7979
$this->applyOption(CURLOPT_HEADER, false);
8080
$this->applyOption(CURLOPT_HEADERFUNCTION, function ($handle, string $header): int {
@@ -356,7 +356,7 @@ private function resetResponseState(): void
356356
{
357357
$this->rawResponseHeaders = '';
358358
$this->response = null;
359-
$this->responseHeaders = new CaseInsensitiveArray();
359+
$this->responseHeaders = new ResponseHeaders();
360360
$this->responseCookies = [];
361361
$this->error = false;
362362
$this->errorCode = 0;
@@ -370,7 +370,7 @@ private function parseCookieHeader(string $header): void
370370
}
371371
}
372372

373-
private function parseResponseHeaders(string $rawHeaders): CaseInsensitiveArray
373+
private function parseResponseHeaders(string $rawHeaders): ResponseHeaders
374374
{
375375
$headerBlocks = explode("\r\n\r\n", trim($rawHeaders));
376376
$responseHeader = '';
@@ -382,7 +382,7 @@ private function parseResponseHeaders(string $rawHeaders): CaseInsensitiveArray
382382
}
383383
}
384384

385-
$headers = new CaseInsensitiveArray();
385+
$headers = new ResponseHeaders();
386386
$rawLines = preg_split('/\r\n/', $responseHeader, -1, PREG_SPLIT_NO_EMPTY);
387387

388388
if ($rawLines === false || $rawLines === []) {

src/HttpClient/ResponseHeaders.php

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Quantum PHP Framework
7+
* An open-source software development framework for PHP
8+
* @link https://quantumphp.io
9+
*/
10+
11+
namespace Quantum\HttpClient;
12+
13+
use ArrayAccess;
14+
use Countable;
15+
use Iterator;
16+
17+
/**
18+
* Class ResponseHeaders
19+
* @package Quantum\HttpClient
20+
* @implements ArrayAccess<int|string, mixed>
21+
* @implements Iterator<int|string, mixed>
22+
*/
23+
class ResponseHeaders implements ArrayAccess, Countable, Iterator
24+
{
25+
/**
26+
* @var array<int|string, mixed>
27+
*/
28+
private array $data = [];
29+
30+
/**
31+
* @var array<string, int|string>
32+
*/
33+
private array $keys = [];
34+
35+
/**
36+
* @param array<int|string, mixed>|null $headers
37+
*/
38+
public function __construct(?array $headers = null)
39+
{
40+
if ($headers !== null) {
41+
foreach ($headers as $key => $value) {
42+
$this->offsetSet($key, $value);
43+
}
44+
}
45+
}
46+
47+
/**
48+
* @param int|string|null $offset
49+
* @param mixed $value
50+
*/
51+
public function offsetSet($offset, $value): void
52+
{
53+
if ($offset === null) {
54+
$this->data[] = $value;
55+
return;
56+
}
57+
58+
$normalizedOffset = strtolower((string) $offset);
59+
$this->data[$normalizedOffset] = $value;
60+
$this->keys[$normalizedOffset] = $offset;
61+
}
62+
63+
/**
64+
* @param int|string $offset
65+
*/
66+
public function offsetExists($offset): bool
67+
{
68+
return array_key_exists(strtolower((string) $offset), $this->data);
69+
}
70+
71+
/**
72+
* @param int|string $offset
73+
*/
74+
public function offsetUnset($offset): void
75+
{
76+
$normalizedOffset = strtolower((string) $offset);
77+
78+
unset($this->data[$normalizedOffset]);
79+
unset($this->keys[$normalizedOffset]);
80+
}
81+
82+
/**
83+
* @param int|string $offset
84+
* @return mixed|null
85+
*/
86+
public function offsetGet($offset)
87+
{
88+
return $this->data[strtolower((string) $offset)] ?? null;
89+
}
90+
91+
public function count(): int
92+
{
93+
return count($this->data);
94+
}
95+
96+
/**
97+
* @return mixed
98+
*/
99+
public function current()
100+
{
101+
return current($this->data);
102+
}
103+
104+
public function next(): void
105+
{
106+
next($this->data);
107+
}
108+
109+
/**
110+
* @return int|string|null
111+
*/
112+
public function key()
113+
{
114+
$key = key($this->data);
115+
116+
if ($key === null) {
117+
return null;
118+
}
119+
120+
return $this->keys[$key] ?? $key;
121+
}
122+
123+
public function valid(): bool
124+
{
125+
return key($this->data) !== null;
126+
}
127+
128+
public function rewind(): void
129+
{
130+
reset($this->data);
131+
}
132+
}

tests/Unit/HttpClient/Adapters/CurlAdapterTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Quantum\Tests\Unit\HttpClient\Adapters;
44

55
use Quantum\HttpClient\Adapters\CurlAdapter;
6+
use Quantum\HttpClient\ResponseHeaders;
67
use Quantum\Tests\Unit\AppTestCase;
78
use Curl\CaseInsensitiveArray;
89
use Curl\Curl;
@@ -72,6 +73,7 @@ public function testCurlAdapterParsesNativeResponseHeaders(): void
7273
"HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\nContent-Type: application/json\r\nMalformed Header\r\nX-Test: one\r\nX-Test: two\r\n\r\n",
7374
]);
7475

76+
$this->assertInstanceOf(ResponseHeaders::class, $headers);
7577
$this->assertSame('HTTP/1.1 200 OK', $headers['status-line']);
7678
$this->assertSame('application/json', $headers['content-type']);
7779
$this->assertSame('one,two', $headers['x-test']);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Quantum\Tests\Unit\HttpClient;
4+
5+
use Quantum\HttpClient\ResponseHeaders;
6+
use Quantum\Tests\Unit\AppTestCase;
7+
8+
class ResponseHeadersTest extends AppTestCase
9+
{
10+
public function testResponseHeadersProvidesCaseInsensitiveAccess(): void
11+
{
12+
$headers = new ResponseHeaders(['Content-Type' => 'application/json']);
13+
14+
$this->assertSame('application/json', $headers['content-type']);
15+
$this->assertSame('application/json', $headers['CONTENT-TYPE']);
16+
$this->assertTrue(isset($headers['Content-Type']));
17+
}
18+
19+
public function testResponseHeadersPreservesLatestOriginalKeyWhenIterating(): void
20+
{
21+
$headers = new ResponseHeaders(['Content-Type' => 'application/json']);
22+
$headers['content-type'] = 'text/plain';
23+
24+
$this->assertSame(['content-type' => 'text/plain'], iterator_to_array($headers));
25+
}
26+
27+
public function testResponseHeadersCanUnsetCaseInsensitively(): void
28+
{
29+
$headers = new ResponseHeaders(['Content-Type' => 'application/json']);
30+
31+
unset($headers['content-type']);
32+
33+
$this->assertCount(0, $headers);
34+
$this->assertNull($headers['Content-Type']);
35+
}
36+
}

0 commit comments

Comments
 (0)