Skip to content

Commit 134d422

Browse files
committed
Refactor for type safety and improved maintainability.
Replaced static return types with concrete class names and improved type safety across headers, JSON, and middleware handling. Updated exception and error handling methods for better clarity and compliance with modern PHP conventions. Simplified redundant checks and replaced deprecated behaviors with more streamlined implementations.
1 parent b26ad9b commit 134d422

26 files changed

+318
-221
lines changed

psalm.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<?xml version="1.0"?>
22
<psalm
33
errorLevel="1"
4-
resolveFromConfigFile="true"
54
totallyTyped="true"
65
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
76
xmlns="https://getpsalm.org/schema/config"
@@ -14,8 +13,8 @@
1413
<!-- Project files and directories -->
1514
<projectFiles>
1615
<directory name="src"/>
16+
<directory name="tests"/>
1717
<ignoreFiles>
18-
<directory name="tests"/>
1918
<directory name="vendor"/>
2019
</ignoreFiles>
2120
</projectFiles>

src/Contracts/Http/Server/Factory/RequestHandlerFactoryInterface.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ interface RequestHandlerFactoryInterface
1313
/**
1414
* @throws NotFoundException
1515
* @throws ContainerException
16+
* @psalm-suppress PossiblyUnusedReturnValue
1617
*/
1718
public function create(): MiddlewarePipeline;
1819
}

src/ErrorHandling/Http/ErrorHandler.php

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ public function register(): void
4040

4141
$this->registered = true;
4242

43-
set_error_handler($this->createErrorHandler());
44-
set_exception_handler($this->createExceptionHandler());
43+
set_error_handler([$this, 'handleError']);
44+
set_exception_handler([$this, 'handleException']);
4545
register_shutdown_function([$this, 'shutdownFunction']);
4646
}
4747

@@ -72,29 +72,29 @@ private function handleFallbackException(
7272
return $this->formatter->format($fallback, $request);
7373
}
7474

75-
private function createErrorHandler(): callable
76-
{
77-
return function (
78-
int $severity,
79-
string $message,
80-
string $file,
81-
int $line
82-
): bool {
83-
try {
84-
throw new ErrorException($message, 0, $severity, $file, $line);
85-
} catch (Throwable $e) {
86-
$this->logger?->critical('Error converted to Exception', ['exception' => $e]);
87-
}
88-
89-
return true;
90-
};
75+
/**
76+
* Handles PHP errors and converts them into exceptions.
77+
*
78+
* @psalm-suppress UnusedReturnValue
79+
*/
80+
public function handleError(
81+
int $severity,
82+
string $message,
83+
string $file,
84+
int $line
85+
): bool {
86+
try {
87+
throw new ErrorException($message, 0, $severity, $file, $line);
88+
} catch (Throwable $e) {
89+
$this->logger?->critical('Error converted to Exception', ['exception' => $e]);
90+
}
91+
92+
return true;
9193
}
9294

93-
private function createExceptionHandler(): callable
95+
public function handleException(Throwable $e): void
9496
{
95-
return function (Throwable $e): void {
96-
$this->logger?->critical('Unhandled throwable', ['exception' => $e]);
97-
};
97+
$this->logger?->critical('Unhandled throwable', ['exception' => $e]);
9898
}
9999

100100
/**

src/ErrorHandling/Http/ExceptionDispatcher.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
final class ExceptionDispatcher implements ExceptionDispatcherInterface
1818
{
1919
/**
20-
* @var array<class-string<Throwable>, list<ErrorHandler>>
20+
* @var array<class-string<Throwable>, list<callable(Throwable, ServerRequestInterface): ?ResponseInterface>>
2121
*/
2222
private array $map = [];
2323

src/Http/Message/Factory/ServerRequestFactory.php

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,20 @@ public static function fromGlobals(): ServerRequestInterface
5555

5656
private static function createUriFromGlobals(): UriInterface
5757
{
58-
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
58+
$scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
5959
$host = $_SERVER['HTTP_HOST'] ?? ($_SERVER['SERVER_NAME'] ?? 'localhost');
6060
$uri = $_SERVER['REQUEST_URI'] ?? '/';
6161

6262
return new Uri("$scheme://$host$uri");
6363
}
6464

65+
/**
66+
* @return array<string, string|string[]>
67+
*/
6568
private static function getAllHeaders(): array
6669
{
6770
if (function_exists('getallHeaders')) {
71+
/** @var array<string, string>|false $headers */
6872
$headers = getallHeaders();
6973

7074
if ($headers !== false) {
@@ -81,50 +85,76 @@ private static function getAllHeaders(): array
8185
}
8286

8387
/**
84-
* Parses server array to header array.
85-
* Used as fallback when getallheaders() is unavailable.
88+
* Parses a server array to a header array.
89+
* Used as a fallback when getallheaders() is unavailable.
90+
*
91+
* @param array<array-key, mixed> $server
92+
* @return array<string, string|string[]>
8693
*/
8794
public static function parseServerHeaders(array $server): array
8895
{
8996
$headers = [];
9097

98+
/**
99+
* @var scalar $value
100+
*/
91101
foreach ($server as $key => $value) {
92-
if (str_starts_with($key, 'HTTP_')) {
93-
$header = strtolower(str_replace('_', '-', substr($key, 5)));
94-
$headers[$header] = [$value];
95-
} elseif (in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH'], true)) {
96-
$header = strtolower(str_replace('_', '-', $key));
97-
$headers[$header] = [$value];
102+
if (is_string($key) && is_string($value)) {
103+
if (str_starts_with($key, 'HTTP_')) {
104+
$header = strtolower(str_replace('_', '-', substr($key, 5)));
105+
$headers[$header] = [$value];
106+
} elseif (in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH'], true)) {
107+
$header = strtolower(str_replace('_', '-', $key));
108+
$headers[$header] = [$value];
109+
}
98110
}
99111
}
100112

101113
return $headers;
102114
}
103115

104116
/**
105-
* Normalizes $_FILES to UploadedFileInterface instances
117+
* Normalizes $_FILES to UploadedFileInterface instances.
118+
*
119+
* @param array<array-key, mixed> $files
120+
* @return array<array-key, UploadedFileInterface|array<array-key, UploadedFileInterface>>
106121
*/
107122
private static function normalizeUploadedFiles(array $files): array
108123
{
124+
/** @var array<string, UploadedFileInterface|array<array-key, UploadedFileInterface>> $normalized */
109125
$normalized = [];
110126

111127
foreach ($files as $field => $value) {
112128
if ($value instanceof UploadedFileInterface) {
113129
$normalized[$field] = $value;
114-
} elseif (isset($value['tmp_name'])) {
115-
if (is_array($value['tmp_name'])) {
116-
foreach ($value['tmp_name'] as $idx => $tmpName) {
117-
$normalized[$field][$idx] = self::createUploadedFile([
118-
'tmp_name' => $tmpName,
119-
'name' => $value['name'][$idx] ?? null,
120-
'type' => $value['type'][$idx] ?? null,
121-
'size' => $value['size'][$idx] ?? 0,
122-
'error' => $value['error'][$idx] ?? 0,
123-
]);
130+
continue;
131+
}
132+
133+
if (!is_array($value) || !isset($value['tmp_name'])) {
134+
continue;
135+
}
136+
137+
if (is_array($value['tmp_name'])) {
138+
/** @var array<array-key, string> $tmpNames */
139+
$tmpNames = $value['tmp_name'];
140+
141+
foreach ($tmpNames as $idx => $tmpName) {
142+
if (!isset($normalized[$field]) || !is_array($normalized[$field])) {
143+
/** @var array<array-key, UploadedFileInterface> $normalizedField */
144+
$normalized[$field] = [];
124145
}
125-
} else {
126-
$normalized[$field] = self::createUploadedFile($value);
146+
147+
$normalized[$field][$idx] = self::createUploadedFile([
148+
'tmp_name' => $tmpName,
149+
'name' => isset($value['name'][$idx]) ? (string) $value['name'][$idx] : null,
150+
'type' => isset($value['type'][$idx]) ? (string) $value['type'][$idx] : null,
151+
'size' => (int) ($value['size'][$idx] ?? 0),
152+
'error' => (int) ($value['error'][$idx] ?? 0),
153+
]);
127154
}
155+
} else {
156+
/** @var array{tmp_name: string, name?: string, type?: string, size?: int, error?: int} $value */
157+
$normalized[$field] = self::createUploadedFile($value);
128158
}
129159
}
130160

@@ -133,28 +163,30 @@ private static function normalizeUploadedFiles(array $files): array
133163

134164
private static function createUploadedFile(array $file): UploadedFileInterface
135165
{
136-
$resource = @fopen($file['tmp_name'], 'rb');
166+
$tmpName = isset($file['tmp_name']) ? (string) $file['tmp_name'] : '';
167+
$resource = @fopen($tmpName, 'rb');
137168
if ($resource === false) {
138169
// @codeCoverageIgnoreStart
139170
// This can only fail under OS-level conditions (missing file, permissions),
140171
// which cannot be reliably simulated in unit tests.
141-
throw new RuntimeException('Failed to open uploaded file: ' . $file['tmp_name']);
172+
throw new RuntimeException('Failed to open uploaded file: ' . $tmpName);
142173
// @codeCoverageIgnoreEnd
143174
}
144175

145176
$stream = new Stream($resource);
177+
146178
return new UploadedFile(
147179
$stream,
148180
(int) $file['size'],
149181
(int) $file['error'],
150-
$file['name'] ?? null,
151-
$file['type'] ?? null
182+
isset($file['name']) ? (string) $file['name'] : null,
183+
isset($file['type']) ? (string) $file['type'] : null
152184
);
153185
}
154186

155187
private static function getProtocolVersion(): string
156188
{
157-
if (!empty($_SERVER['SERVER_PROTOCOL']) && str_starts_with($_SERVER['SERVER_PROTOCOL'], 'HTTP/')) {
189+
if (isset($_SERVER['SERVER_PROTOCOL']) && str_starts_with($_SERVER['SERVER_PROTOCOL'], 'HTTP/')) {
158190
return substr($_SERVER['SERVER_PROTOCOL'], 5);
159191
}
160192

0 commit comments

Comments
 (0)