Skip to content

Commit f8f07bf

Browse files
Merge pull request #4 from InitPHP/2.x
Update configurations, tests, and immutability checks in multiple files
2 parents 75b053c + 9e0aaa1 commit f8f07bf

26 files changed

Lines changed: 835 additions & 21 deletions

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
/.vscode/
33
/.vs/
44
/vendor/
5-
/composer.lock
5+
/composer.lock
6+
/.phpunit.cache/
7+
/.phpunit.result.cache

composer.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@
1111
"src/Helpers.php"
1212
]
1313
},
14+
"autoload-dev": {
15+
"psr-4": {
16+
"InitPHP\\HTTP\\Tests\\": "tests/"
17+
}
18+
},
19+
"scripts": {
20+
"test": "phpunit"
21+
},
1422
"authors": [
1523
{
1624
"name": "Muhammet ŞAFAK",
@@ -26,5 +34,10 @@
2634
"psr/http-message": "^1.0",
2735
"psr/http-factory": "^1.0",
2836
"psr/http-client": "^1.0"
37+
},
38+
"require-dev": {
39+
"phpunit/phpunit": "^10.5",
40+
"php-http/psr7-integration-tests": "^1.3",
41+
"http-interop/http-factory-tests": "^2.2"
2942
}
3043
}

phpunit.xml.dist

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
colors="true"
6+
failOnRisky="false"
7+
failOnWarning="false"
8+
cacheDirectory=".phpunit.cache">
9+
<testsuites>
10+
<testsuite name="PSR-7 Integration">
11+
<directory>tests/Psr7</directory>
12+
</testsuite>
13+
<testsuite name="PSR-17 Integration">
14+
<directory>tests/Psr17</directory>
15+
</testsuite>
16+
<testsuite name="PSR-18 Integration">
17+
<directory>tests/Psr18</directory>
18+
</testsuite>
19+
<testsuite name="Immutability">
20+
<directory>tests/Immutability</directory>
21+
</testsuite>
22+
</testsuites>
23+
<php>
24+
<const name="URI_FACTORY" value="InitPHP\HTTP\Factory\Factory"/>
25+
<const name="STREAM_FACTORY" value="InitPHP\HTTP\Factory\Factory"/>
26+
<const name="UPLOADED_FILE_FACTORY" value="InitPHP\HTTP\Factory\Factory"/>
27+
</php>
28+
</phpunit>

src/Client/Client.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ private function prepareCurlOptions(RequestInterface $request, array &$response)
204204
$options[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1;
205205
}
206206

207-
if ($method === 'HEAD') {
207+
if (\strtoupper($method) === 'HEAD') {
208208
$options[\CURLOPT_NOBODY] = true;
209209
} elseif ($body !== '') {
210210
$options[\CURLOPT_POSTFIELDS] = $body;

src/Message/Request.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,24 @@ class Request implements \InitPHP\HTTP\Message\Interfaces\RequestInterface
3838

3939
private static Request $requestImmutable;
4040

41+
/**
42+
* PSR-7 immutability: clone'da body ve URI'yi de derinleştir; aksi halde
43+
* `$cloned->getBody()->write(...)` veya `$cloned->getUri()->setHost(...)`
44+
* orijinali mutasyona uğratır.
45+
*/
46+
public function __clone()
47+
{
48+
if (isset($this->stream)) {
49+
$this->stream = clone $this->stream;
50+
}
51+
if (isset($this->uri)) {
52+
$this->uri = clone $this->uri;
53+
}
54+
if (isset($this->_objParameters)) {
55+
$this->_objParameters = clone $this->_objParameters;
56+
}
57+
}
58+
4159
public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1')
4260
{
4361
if(!($uri instanceof UriInterface)) {

src/Message/Response.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,17 @@ class Response implements ResponseInterface
109109
* @param string $version
110110
* @param string|null $reason
111111
*/
112+
/**
113+
* PSR-7 immutability: clone'da body'i de derinleştir ki
114+
* `$cloned->getBody()->write(...)` orijinali bozmasın.
115+
*/
116+
public function __clone()
117+
{
118+
if (isset($this->stream)) {
119+
$this->stream = clone $this->stream;
120+
}
121+
}
122+
112123
public function __construct(int $status = 200, array $headers = [], $body = null, string $version = '1.1', ?string $reason = null)
113124
{
114125
if(!in_array($version, ['1.0', '1.1', '2.0'], true)){

src/Message/ServerRequest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@ class ServerRequest implements ServerRequestInterface
4343
protected array $uploadedFiles = [];
4444

4545

46+
/**
47+
* PSR-7 immutability: clone'da body + URI'yi derinleştir.
48+
* uploadedFiles ve attributes ham PHP array'i; copy-on-write zaten kopyalar.
49+
*/
50+
public function __clone()
51+
{
52+
if (isset($this->stream)) {
53+
$this->stream = clone $this->stream;
54+
}
55+
if (isset($this->uri)) {
56+
$this->uri = clone $this->uri;
57+
}
58+
}
59+
4660
public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1', array $serverParams = [])
4761
{
4862
$this->serverParams = $serverParams;

src/Message/Stream.php

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,55 @@ public function __destruct()
134134
$this->close();
135135
}
136136

137+
/**
138+
* PSR-7 immutability: clone yapıldığında resource'u derinleştir; aksi halde
139+
* iki Stream aynı resource handle'ını paylaşır, withBody dışı tüm withX
140+
* çağrıları orijinal mesajın body'sini de değiştirir.
141+
*/
142+
public function __clone()
143+
{
144+
if (!isset($this->stream)) {
145+
return;
146+
}
147+
$this->uri = null;
148+
if (is_string($this->stream)) {
149+
// String backend zaten PHP copy-on-write; ek iş gerekmiyor.
150+
return;
151+
}
152+
if (!is_resource($this->stream)) {
153+
return;
154+
}
155+
$originalPosition = @ftell($this->stream);
156+
if ($this->seekable) {
157+
@rewind($this->stream);
158+
}
159+
$contents = @stream_get_contents($this->stream);
160+
if ($contents === false) {
161+
$contents = '';
162+
}
163+
if ($this->seekable && is_int($originalPosition)) {
164+
@fseek($this->stream, $originalPosition);
165+
}
166+
$copy = @fopen('php://temp', 'w+b');
167+
if ($copy === false) {
168+
return;
169+
}
170+
if ($contents !== '') {
171+
fwrite($copy, $contents);
172+
}
173+
// Orijinal stream'in pozisyonunu koru
174+
if (is_int($originalPosition)) {
175+
fseek($copy, $originalPosition);
176+
} else {
177+
rewind($copy);
178+
}
179+
$this->stream = $copy;
180+
$this->size = strlen($contents);
181+
$this->seekable = true;
182+
$this->readable = true;
183+
$this->writable = true;
184+
}
185+
137186
/**
138187
* @param null|string|resource|StreamInterface $body
139188
* @param string|null $target <p>["php://temp"|"php://memory"|NULL]</p>
@@ -155,6 +204,15 @@ public function init($body = null, ?string $target = 'php://temp'): StreamInterf
155204
}
156205
$body = $body->getContents();
157206
}
207+
// Resource'lar target'tan bağımsız her zaman kabul edilir
208+
if(is_resource($body)){
209+
$this->stream = $body;
210+
$meta = stream_get_meta_data($this->stream);
211+
$this->seekable = $meta['seekable'] && fseek($this->stream, 0, SEEK_CUR) === 0;
212+
$this->readable = isset(self::READ_WRITE_HASH['read'][$meta['mode']]);
213+
$this->writable = isset(self::READ_WRITE_HASH['write'][$meta['mode']]);
214+
return $this;
215+
}
158216
if($this->target === null){
159217
if(!is_scalar($body)){
160218
throw new InvalidArgumentException("The parameter \$body must be a string.");
@@ -176,10 +234,7 @@ public function init($body = null, ?string $target = 'php://temp'): StreamInterf
176234
fwrite($resource, $body);
177235
fseek($resource, 0);
178236
}
179-
$body = $resource;
180-
}
181-
if(is_resource($body)){
182-
$this->stream = $body;
237+
$this->stream = $resource;
183238
$meta = stream_get_meta_data($this->stream);
184239
$this->seekable = $meta['seekable'] && fseek($this->stream, 0, SEEK_CUR) === 0;
185240
$this->readable = isset(self::READ_WRITE_HASH['read'][$meta['mode']]);

src/Message/Traits/MessageTrait.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ public function outHeader($name): self
154154
return $this;
155155
}
156156

157-
$name = $this->headerNames[$lowercase];
158-
unset($this->headerNames[$name], $this->headerNames[$lowercase]);
157+
$original = $this->headerNames[$lowercase];
158+
unset($this->headers[$original], $this->headerNames[$lowercase]);
159159

160160
return $this;
161161
}

src/Message/Traits/RequestTrait.php

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use function is_string;
2323
use function preg_match;
2424
use function strtoupper;
25+
use function array_map;
2526

2627
trait RequestTrait
2728
{
@@ -37,55 +38,55 @@ trait RequestTrait
3738
*/
3839
public function isGet(): bool
3940
{
40-
return $this->getMethod() === 'GET';
41+
return strtoupper($this->getMethod()) === 'GET';
4142
}
4243

4344
/**
4445
* @inheritDoc
4546
*/
4647
public function isPost(): bool
4748
{
48-
return $this->getMethod() === 'POST';
49+
return strtoupper($this->getMethod()) === 'POST';
4950
}
5051

5152
/**
5253
* @inheritDoc
5354
*/
5455
public function isPut(): bool
5556
{
56-
return $this->getMethod() === 'PUT';
57+
return strtoupper($this->getMethod()) === 'PUT';
5758
}
5859

5960
/**
6061
* @inheritDoc
6162
*/
6263
public function isDelete(): bool
6364
{
64-
return $this->getMethod() === 'DELETE';
65+
return strtoupper($this->getMethod()) === 'DELETE';
6566
}
6667

6768
/**
6869
* @inheritDoc
6970
*/
7071
public function isHead(): bool
7172
{
72-
return $this->getMethod() === 'HEAD';
73+
return strtoupper($this->getMethod()) === 'HEAD';
7374
}
7475

7576
/**
7677
* @inheritDoc
7778
*/
7879
public function isPatch(): bool
7980
{
80-
return $this->getMethod() === 'PATCH';
81+
return strtoupper($this->getMethod()) === 'PATCH';
8182
}
8283

8384
/**
8485
* @inheritDoc
8586
*/
8687
public function isMethod(string ...$method): bool
8788
{
88-
return in_array($this->getMethod(), $method);
89+
return in_array(strtoupper($this->getMethod()), array_map('strtoupper', $method), true);
8990
}
9091

9192
/**
@@ -143,7 +144,7 @@ public function setMethod($method): self
143144
if(!is_string($method)){
144145
throw new \InvalidArgumentException('Method must be a string.');
145146
}
146-
$this->method = strtoupper($method);
147+
$this->method = $method;
147148
return $this;
148149
}
149150

0 commit comments

Comments
 (0)