Skip to content

Commit aa63045

Browse files
committed
Add PHP 8.4 support, PHPStan level 8 analysis, and security hardening
- Add PHP 8.4 to CI test matrix - Add PHPStan at level 8 for static analysis with composer analyse command - Add Laravel Pint code formatting with PSR-12 preset and strict_types - Add CRLF injection prevention in RequestBuilder headers - Add sensitive header redaction in HttpException for security - Fix JsonSerializer false return handling and object-to-array conversion - Fix PHPStan level 8 violations across the codebase - Update tests for new functionality and API changes
1 parent 19a1014 commit aa63045

48 files changed

Lines changed: 579 additions & 199 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/run-tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
fail-fast: true
1010
matrix:
1111
os: [ubuntu-latest, windows-latest]
12-
php: [8.3, 8.2, 8.1]
12+
php: [8.4, 8.3, 8.2, 8.1]
1313
stability: [prefer-lowest, prefer-stable]
1414

1515
name: P${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }}
@@ -40,4 +40,4 @@ jobs:
4040
uses: codecov/codecov-action@v5
4141
with:
4242
file: ./coverage.xml
43-
token: ${{ secrets.CODECOV_TOKEN }}
43+
token: ${{ secrets.CODECOV_TOKEN }}

composer.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@
4848
"scripts": {
4949
"test": "vendor/bin/pest",
5050
"test-coverage": "vendor/bin/pest --coverage",
51-
"format": "vendor/bin/pint"
51+
"format": "vendor/bin/pint",
52+
"analyse": "vendor/bin/phpstan analyse",
53+
"check": [
54+
"@analyse",
55+
"@test"
56+
]
5257
},
5358
"config": {
5459
"sort-packages": true,

examples/advanced-retry.php

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
/**
46
* Advanced Retry Logic Example
57
*
@@ -61,13 +63,22 @@
6163
echo "3. Custom Retry Conditions\n";
6264
echo str_repeat('-', 50)."\n";
6365

66+
$retryableStatusCodes = [408, 429, 500, 502, 503, 504];
67+
6468
$transport3 = TransportBuilder::make()
6569
->withBaseUri('https://jsonplaceholder.typicode.com')
6670
->withRetries(
6771
maxRetries: 5,
68-
strategy: new ExponentialBackoffStrategy,
72+
strategy: new ExponentialBackoffStrategy(),
6973
condition: RetryCondition::default()
70-
->onStatusCodes([408, 429, 500, 502, 503, 504]) // Retry on specific status codes
74+
->when(function (\Throwable $exception, \Farzai\Transport\Retry\RetryContext $context) use ($retryableStatusCodes): bool {
75+
// Retry on specific status codes from HTTP exceptions
76+
if ($exception instanceof \Farzai\Transport\Exceptions\HttpException && $exception->hasResponse()) {
77+
return in_array($exception->getResponse()->getStatusCode(), $retryableStatusCodes, true);
78+
}
79+
80+
return false;
81+
})
7182
)
7283
->build();
7384

@@ -83,34 +94,36 @@
8394
echo "4. Retry with Custom Condition Callback\n";
8495
echo str_repeat('-', 50)."\n";
8596

86-
$transport4 = TransportBuilder::make()
87-
->withBaseUri('https://jsonplaceholder.typicode.com')
88-
->withRetries(
89-
maxRetries: 3,
90-
strategy: new ExponentialBackoffStrategy,
91-
condition: RetryCondition::fromCallback(
92-
function (\Throwable $exception, \Farzai\Transport\Retry\RetryContext $context): bool {
93-
echo " Retry attempt {$context->attempt}/{$context->maxAttempts}\n";
94-
echo " Exception: {$exception->getMessage()}\n";
97+
$customCondition = (new RetryCondition())->when(
98+
function (\Throwable $exception, \Farzai\Transport\Retry\RetryContext $context): bool {
99+
echo " Retry attempt {$context->attempt}/{$context->maxAttempts}\n";
100+
echo " Exception: {$exception->getMessage()}\n";
95101

96-
// Retry only on network errors or 5xx responses
97-
if ($exception instanceof \Farzai\Transport\Exceptions\ServerException) {
98-
echo " Decision: RETRY (Server error)\n\n";
102+
// Retry only on network errors or 5xx responses
103+
if ($exception instanceof \Farzai\Transport\Exceptions\ServerException) {
104+
echo " Decision: RETRY (Server error)\n\n";
99105

100-
return true;
101-
}
106+
return true;
107+
}
102108

103-
if ($exception instanceof \Farzai\Transport\Exceptions\NetworkException) {
104-
echo " Decision: RETRY (Network error)\n\n";
109+
if ($exception instanceof \Farzai\Transport\Exceptions\NetworkException) {
110+
echo " Decision: RETRY (Network error)\n\n";
105111

106-
return true;
107-
}
112+
return true;
113+
}
108114

109-
echo " Decision: DO NOT RETRY\n\n";
115+
echo " Decision: DO NOT RETRY\n\n";
110116

111-
return false;
112-
}
113-
)
117+
return false;
118+
}
119+
);
120+
121+
$transport4 = TransportBuilder::make()
122+
->withBaseUri('https://jsonplaceholder.typicode.com')
123+
->withRetries(
124+
maxRetries: 3,
125+
strategy: new ExponentialBackoffStrategy(),
126+
condition: $customCondition
114127
)
115128
->build();
116129

examples/basic-usage.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
/**
46
* Basic Usage Example
57
*

examples/cookie-session.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
/**
46
* Cookie Session Example
57
*
@@ -50,7 +52,7 @@
5052
echo str_repeat('-', 50)."\n";
5153

5254
try {
53-
$cookieJar = new CookieJar;
55+
$cookieJar = new CookieJar();
5456

5557
$transport = TransportBuilder::make()
5658
->withBaseUri('https://httpbin.org')
@@ -84,7 +86,7 @@
8486
echo str_repeat('-', 50)."\n";
8587

8688
try {
87-
$cookieJar = new CookieJar;
89+
$cookieJar = new CookieJar();
8890

8991
// Manually create and add cookies
9092
$sessionCookie = new Cookie(
@@ -129,7 +131,7 @@
129131
echo str_repeat('-', 50)."\n";
130132

131133
try {
132-
$cookieJar = new CookieJar;
134+
$cookieJar = new CookieJar();
133135

134136
// Add various cookies
135137
$cookieJar->setCookie(new Cookie('cookie1', 'value1', null, 'example.com', '/'));
@@ -167,7 +169,7 @@
167169

168170
try {
169171
// Create jar and add cookies
170-
$jar1 = new CookieJar;
172+
$jar1 = new CookieJar();
171173
$jar1->setCookie(new Cookie('persistent', 'data', time() + 86400, 'example.com'));
172174
$jar1->setCookie(new Cookie('preferences', 'dark_mode=true', time() + 2592000, 'example.com'));
173175

@@ -181,7 +183,7 @@
181183
echo "Cookies saved to: {$cookieFile}\n";
182184

183185
// Later... Import cookies
184-
$jar2 = new CookieJar;
186+
$jar2 = new CookieJar();
185187
$imported = json_decode(file_get_contents($cookieFile), true);
186188
$jar2->fromArray($imported);
187189

@@ -199,7 +201,7 @@
199201
echo str_repeat('-', 50)."\n";
200202

201203
try {
202-
$cookieJar = new CookieJar;
204+
$cookieJar = new CookieJar();
203205

204206
// Add mix of cookies
205207
$cookieJar->setCookie(new Cookie('keep', 'value', time() + 3600));
@@ -224,7 +226,7 @@
224226
echo str_repeat('-', 50)."\n";
225227

226228
try {
227-
$cookieJar = new CookieJar;
229+
$cookieJar = new CookieJar();
228230

229231
// Create transport for scraping
230232
$scraper = TransportBuilder::make()
@@ -262,7 +264,7 @@
262264

263265
try {
264266
// Without session persistence (default)
265-
$regularJar = new CookieJar;
267+
$regularJar = new CookieJar();
266268
$regularJar->setCookie(new Cookie('session', 'value')); // Session cookie
267269
$regularJar->setCookie(new Cookie('persistent', 'value', time() + 3600)); // Persistent
268270

examples/custom-client.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
/**
46
* Custom HTTP Client Example
57
*
@@ -116,8 +118,7 @@
116118

117119
try {
118120
// Create a simple logger
119-
$logger = new class extends \Psr\Log\AbstractLogger
120-
{
121+
$logger = new class () extends \Psr\Log\AbstractLogger {
121122
public function log($level, $message, array $context = []): void
122123
{
123124
$contextStr = ! empty($context) ? ' '.json_encode($context) : '';

examples/file-upload.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
/**
46
* File Upload Example
57
*
@@ -134,7 +136,7 @@
134136

135137
try {
136138
// Create builder
137-
$builder = new MultipartStreamBuilder;
139+
$builder = new MultipartStreamBuilder();
138140

139141
// Add various fields
140142
$builder->addField('user_id', '12345')

examples/middleware-example.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
/**
46
* Custom Middleware Example
57
*
@@ -25,7 +27,8 @@ class CustomHeaderMiddleware implements MiddlewareInterface
2527
public function __construct(
2628
private readonly string $headerName,
2729
private readonly string $headerValue
28-
) {}
30+
) {
31+
}
2932

3033
public function handle(RequestInterface $request, callable $next): ResponseInterface
3134
{
@@ -87,7 +90,7 @@ public function handle(RequestInterface $request, callable $next): ResponseInter
8790
$transport2 = TransportBuilder::make()
8891
->withBaseUri('https://jsonplaceholder.typicode.com')
8992
->withoutDefaultMiddlewares() // Disable default logging
90-
->withMiddleware(new DetailedLoggingMiddleware)
93+
->withMiddleware(new DetailedLoggingMiddleware())
9194
->build();
9295

9396
try {
@@ -105,7 +108,8 @@ class ApiKeyAuthMiddleware implements MiddlewareInterface
105108
public function __construct(
106109
private readonly string $apiKey,
107110
private readonly string $headerName = 'X-API-Key'
108-
) {}
111+
) {
112+
}
109113

110114
public function handle(RequestInterface $request, callable $next): ResponseInterface
111115
{
@@ -138,7 +142,8 @@ class SimpleCacheMiddleware implements MiddlewareInterface
138142

139143
public function __construct(
140144
private readonly int $ttlSeconds = 60
141-
) {}
145+
) {
146+
}
142147

143148
public function handle(RequestInterface $request, callable $next): ResponseInterface
144149
{
@@ -204,7 +209,8 @@ class RateLimitMiddleware implements MiddlewareInterface
204209
public function __construct(
205210
private readonly int $maxRequests = 10,
206211
private readonly int $perSeconds = 60
207-
) {}
212+
) {
213+
}
208214

209215
public function handle(RequestInterface $request, callable $next): ResponseInterface
210216
{

phpstan.neon

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
parameters:
2+
level: 8
3+
paths:
4+
- src
5+
excludePaths:
6+
- vendor
7+
checkGenericClassInNonGenericObjectType: true
8+
reportUnmatchedIgnoredErrors: false
9+
ignoreErrors:
10+
-
11+
identifier: missingType.iterableValue

pint.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"preset": "psr12",
3+
"rules": {
4+
"array_syntax": {
5+
"syntax": "short"
6+
},
7+
"binary_operator_spaces": {
8+
"default": "single_space"
9+
},
10+
"blank_line_after_opening_tag": true,
11+
"declare_strict_types": true,
12+
"no_unused_imports": true,
13+
"ordered_imports": {
14+
"sort_algorithm": "alpha"
15+
},
16+
"single_quote": true,
17+
"trailing_comma_in_multiline": {
18+
"elements": ["arrays"]
19+
}
20+
}
21+
}

0 commit comments

Comments
 (0)