Skip to content

Commit 3e7340b

Browse files
committed
feat: melhorar tratamento de erros e logs em várias classes do sistema
1 parent 8e24820 commit 3e7340b

File tree

16 files changed

+165
-41
lines changed

16 files changed

+165
-41
lines changed

src/Cache/FileCache.php

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace PivotPHP\Core\Cache;
44

5+
use RuntimeException;
6+
57
/**
68
* Driver de cache em arquivo
79
*/
@@ -14,7 +16,9 @@ public function __construct(?string $cacheDir = null)
1416
$this->cacheDir = $cacheDir ?? sys_get_temp_dir() . '/express-cache';
1517

1618
if (!is_dir($this->cacheDir)) {
17-
mkdir($this->cacheDir, 0755, true);
19+
if (!@mkdir($this->cacheDir, 0755, true) && !is_dir($this->cacheDir)) {
20+
throw new RuntimeException("Cannot create cache directory: {$this->cacheDir}");
21+
}
1822
}
1923
}
2024

@@ -30,8 +34,10 @@ public function get(string $key, $default = null)
3034
return $default;
3135
}
3236

33-
$fileContents = file_get_contents($file);
37+
$fileContents = @file_get_contents($file);
3438
if ($fileContents === false) {
39+
$error = error_get_last();
40+
error_log("FileCache read failed for {$file}: " . ($error['message'] ?? 'Unknown error'));
3541
return $default;
3642
}
3743

@@ -75,7 +81,12 @@ public function set(string $key, $value, ?int $ttl = null): bool
7581
'expires' => $expires
7682
];
7783

78-
return file_put_contents($file, serialize($data)) !== false;
84+
$written = @file_put_contents($file, serialize($data), LOCK_EX);
85+
if ($written === false) {
86+
error_log("FileCache write failed for {$file}");
87+
return false;
88+
}
89+
return true;
7990
}
8091

8192
/**
@@ -121,14 +132,17 @@ public function has(string $key): bool
121132
return false;
122133
}
123134

124-
$fileContents = file_get_contents($file);
135+
$fileContents = @file_get_contents($file);
125136
if ($fileContents === false) {
137+
$error = error_get_last();
138+
error_log("FileCache read failed for {$file}: " . ($error['message'] ?? 'Unknown error'));
126139
return false;
127140
}
128141

129142
try {
130143
$data = unserialize($fileContents);
131144
} catch (\Throwable $e) {
145+
error_log("FileCache unserialize failed for {$key}: " . $e->getMessage());
132146
$this->delete($key);
133147
return false;
134148
}

src/Http/Request.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,13 @@ class Request implements ServerRequestInterface, AttributeInterface
9292
private function getCachedInput(): string
9393
{
9494
if (self::$cachedInput === null) {
95-
self::$cachedInput = file_get_contents('php://input') ?: '';
95+
$input = @file_get_contents('php://input');
96+
if ($input === false) {
97+
error_log('Failed to read from php://input stream');
98+
self::$cachedInput = '';
99+
} else {
100+
self::$cachedInput = $input;
101+
}
96102
}
97103
return self::$cachedInput;
98104
}
@@ -173,7 +179,12 @@ private function initializePsr7Request(): void
173179
$input = $this->getCachedInput();
174180
if ($input !== '') {
175181
$decoded = json_decode($input, true);
176-
$this->psr7Request = $this->psr7Request->withParsedBody($decoded ?: $_POST);
182+
if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
183+
error_log('JSON decode error in request body: ' . json_last_error_msg());
184+
$this->psr7Request = $this->psr7Request->withParsedBody($_POST);
185+
} else {
186+
$this->psr7Request = $this->psr7Request->withParsedBody($decoded ?: $_POST);
187+
}
177188
}
178189
}
179190

src/Http/Response.php

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ public function text(mixed $text): self
264264
$textString = is_string($text) ? $text : (
265265
is_scalar($text) || (is_object($text) && method_exists($text, '__toString'))
266266
? (string)$text
267-
: (json_encode($text) ?: '')
267+
: $this->encodeJsonSafely($text)
268268
);
269269
$this->body = $textString;
270270
if ($this->psr7Response !== null) {
@@ -288,7 +288,7 @@ public function html(mixed $html): self
288288
$htmlString = is_string($html) ? $html : (
289289
is_scalar($html) || (is_object($html) && method_exists($html, '__toString'))
290290
? (string)$html
291-
: (json_encode($html) ?: '')
291+
: $this->encodeJsonSafely($html)
292292
);
293293
$this->body = $htmlString;
294294
if ($this->psr7Response !== null) {
@@ -398,7 +398,7 @@ public function send(mixed $data = ''): self
398398
if (is_scalar($data) || (is_object($data) && method_exists($data, '__toString'))) {
399399
return $this->text((string)$data);
400400
}
401-
return $this->text(json_encode($data));
401+
return $this->text($this->encodeJsonSafely($data));
402402
}
403403

404404
// =============================================================================
@@ -601,8 +601,18 @@ public function streamFile(string $filePath, array $headers = []): self
601601
throw new InvalidArgumentException("File not found or not readable: {$filePath}");
602602
}
603603

604-
$fileSize = filesize($filePath);
605-
$mimeType = mime_content_type($filePath) ?: 'application/octet-stream';
604+
$fileSize = @filesize($filePath);
605+
if ($fileSize === false) {
606+
throw new InvalidArgumentException("Cannot determine file size: {$filePath}");
607+
}
608+
609+
$mimeType = 'application/octet-stream';
610+
if (function_exists('mime_content_type')) {
611+
$detected = @mime_content_type($filePath);
612+
if ($detected !== false) {
613+
$mimeType = $detected;
614+
}
615+
}
606616

607617
// Configurar cabeçalhos
608618
$this->header('Content-Type', $mimeType);
@@ -743,6 +753,19 @@ public function isStreaming(): bool
743753
// MÉTODOS UTILITÁRIOS
744754
// =============================================================================
745755

756+
/**
757+
* Encodes JSON safely with proper error handling
758+
*/
759+
private function encodeJsonSafely(mixed $data): string
760+
{
761+
$encoded = json_encode($data, self::JSON_ENCODE_FLAGS);
762+
if ($encoded === false) {
763+
error_log('JSON encoding failed: ' . json_last_error_msg());
764+
return '{}';
765+
}
766+
return $encoded;
767+
}
768+
746769
/**
747770
* Sanitiza dados para garantir codificação UTF-8 válida para JSON.
748771
*/

src/Json/Pool/JsonBuffer.php

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ public function append(string $data): void
5353

5454
if ($this->useStream && $this->stream !== null) {
5555
// Use stream for large buffers to avoid string reallocation
56-
fwrite($this->stream, $data);
56+
$bytesWritten = @fwrite($this->stream, $data);
57+
if ($bytesWritten === false) {
58+
throw new \RuntimeException('Failed to write to stream buffer');
59+
}
5760
} else {
5861
// Use string concatenation for small buffers
5962
$this->buffer .= $data;
@@ -83,8 +86,10 @@ public function finalize(): string
8386
if (!$this->finalized) {
8487
if ($this->useStream && $this->stream !== null) {
8588
// Read all content from stream
86-
rewind($this->stream);
87-
$content = stream_get_contents($this->stream);
89+
if (@rewind($this->stream) === false) {
90+
throw new \RuntimeException('Failed to rewind stream');
91+
}
92+
$content = @stream_get_contents($this->stream);
8893
if ($content === false) {
8994
throw new \RuntimeException('Failed to read from stream');
9095
}
@@ -107,8 +112,12 @@ public function reset(): void
107112

108113
if ($this->useStream && $this->stream !== null) {
109114
// Reset stream to beginning and truncate
110-
rewind($this->stream);
111-
ftruncate($this->stream, 0);
115+
if (@rewind($this->stream) === false) {
116+
error_log('Failed to rewind stream during reset');
117+
}
118+
if (@ftruncate($this->stream, 0) === false) {
119+
error_log('Failed to truncate stream during reset');
120+
}
112121
}
113122
}
114123

src/Logging/FileHandler.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace PivotPHP\Core\Logging;
44

5+
use RuntimeException;
6+
57
/**
68
* Handler para arquivo
79
*/
@@ -21,7 +23,9 @@ public function __construct(string $filePath, string $dateFormat = 'Y-m-d H:i:s'
2123
// Cria o diretório se não existir
2224
$dir = dirname($filePath);
2325
if (!is_dir($dir)) {
24-
mkdir($dir, 0755, true);
26+
if (!@mkdir($dir, 0755, true) && !is_dir($dir)) {
27+
throw new RuntimeException("Cannot create log directory: {$dir}");
28+
}
2529
}
2630
}
2731

@@ -31,7 +35,10 @@ public function __construct(string $filePath, string $dateFormat = 'Y-m-d H:i:s'
3135
public function handle(array $record): void
3236
{
3337
$message = $this->format($record);
34-
file_put_contents($this->filePath, $message . PHP_EOL, FILE_APPEND | LOCK_EX);
38+
$written = @file_put_contents($this->filePath, $message . PHP_EOL, FILE_APPEND | LOCK_EX);
39+
if ($written === false) {
40+
error_log('Failed to write to log file: ' . $this->filePath);
41+
}
3542
}
3643

3744
private function format(array $record): string

src/Middleware/Performance/CacheMiddleware.php

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use Psr\Http\Server\RequestHandlerInterface;
99
use Psr\Http\Message\ServerRequestInterface;
1010
use Psr\Http\Message\ResponseInterface;
11+
use RuntimeException;
12+
use Throwable;
1113

1214
/**
1315
* Cache Middleware
@@ -31,7 +33,9 @@ public function __construct(int $ttl = 300, string $cacheDir = '/tmp/expressphp_
3133
$this->ttl = $ttl;
3234
$this->cacheDir = $cacheDir;
3335
if (!is_dir($this->cacheDir)) {
34-
@mkdir($this->cacheDir, 0777, true);
36+
if (!@mkdir($this->cacheDir, 0777, true) && !is_dir($this->cacheDir)) {
37+
throw new RuntimeException("Cannot create cache directory: {$this->cacheDir}");
38+
}
3539
}
3640
}
3741

@@ -43,20 +47,44 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
4347
$key = $this->generateCacheKey($request);
4448
$cacheFile = $this->cacheDir . '/' . $key;
4549

46-
if (file_exists($cacheFile) && (filemtime($cacheFile) + $this->ttl) > time()) {
47-
$cached = file_get_contents($cacheFile);
48-
$response = unserialize((string)$cached);
49-
if ($response instanceof ResponseInterface) {
50-
return $response;
50+
// Try to read from cache
51+
try {
52+
if (file_exists($cacheFile) && (filemtime($cacheFile) + $this->ttl) > time()) {
53+
$cached = @file_get_contents($cacheFile);
54+
if ($cached !== false) {
55+
try {
56+
$response = unserialize($cached);
57+
if ($response instanceof ResponseInterface) {
58+
return $response;
59+
}
60+
} catch (Throwable $e) {
61+
// Delete corrupted cache file
62+
@unlink($cacheFile);
63+
error_log('Cache file corrupted, deleted: ' . $cacheFile);
64+
}
65+
}
5166
}
67+
} catch (Throwable $e) {
68+
error_log('Cache read error: ' . $e->getMessage());
5269
}
5370

5471
$response = $handler->handle($request);
72+
5573
// Adiciona header de cache-control
5674
if (method_exists($response, 'withHeader')) {
5775
$response = $response->withHeader('Cache-Control', 'public, max-age=' . $this->ttl);
5876
}
59-
file_put_contents($cacheFile, serialize($response));
77+
78+
// Try to write to cache
79+
try {
80+
$written = @file_put_contents($cacheFile, serialize($response), LOCK_EX);
81+
if ($written === false) {
82+
error_log('Failed to write cache file: ' . $cacheFile);
83+
}
84+
} catch (Throwable $e) {
85+
error_log('Cache write error: ' . $e->getMessage());
86+
}
87+
6088
return $response;
6189
}
6290

src/Middleware/Security/AuthMiddleware.php

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ private function tryBasic(ServerRequestInterface $request, RequestHandlerInterfa
115115
{
116116
$header = $request->getHeaderLine('Authorization');
117117
if (strpos($header, 'Basic ') === 0) {
118-
$decoded = base64_decode(substr($header, 6));
119-
if ($decoded && strpos($decoded, ':') !== false) {
118+
$decoded = base64_decode(substr($header, 6), true);
119+
if ($decoded !== false && strpos($decoded, ':') !== false) {
120120
[$username, $password] = explode(':', $decoded, 2);
121121
if (isset($this->config['basicAuthCallback']) && is_callable($this->config['basicAuthCallback'])) {
122122
$result = call_user_func($this->config['basicAuthCallback'], $username, $password);
@@ -202,11 +202,21 @@ private function validateToken(string $token): bool
202202
try {
203203
// Simple validation - in production, use a proper JWT library
204204
$parts = explode('.', $token);
205-
if (count($parts) !== 3) {
205+
if (count($parts) !== 3 || empty($parts[1])) {
206206
return false;
207207
}
208208

209-
$payload = json_decode(base64_decode($parts[1]), true);
209+
$decoded = base64_decode($parts[1], true);
210+
if ($decoded === false) {
211+
error_log('JWT token base64 decode failed');
212+
return false;
213+
}
214+
215+
$payload = json_decode($decoded, true);
216+
if ($payload === null && json_last_error() !== JSON_ERROR_NONE) {
217+
error_log('JWT token JSON decode failed: ' . json_last_error_msg());
218+
return false;
219+
}
210220

211221
if (!is_array($payload)) {
212222
return false;
@@ -219,10 +229,16 @@ private function validateToken(string $token): bool
219229

220230
// Validate signature (simplified)
221231
$expectedSignature = hash_hmac('sha256', $parts[0] . '.' . $parts[1], $this->config['secret'], true);
222-
$actualSignature = base64_decode(strtr($parts[2], '-_', '+/'));
232+
$actualSignature = base64_decode(strtr($parts[2], '-_', '+/'), true);
233+
234+
if ($actualSignature === false) {
235+
error_log('JWT signature base64 decode failed');
236+
return false;
237+
}
223238

224239
return hash_equals($expectedSignature, $actualSignature);
225240
} catch (\Exception $e) {
241+
error_log('JWT validation error: ' . $e->getMessage());
226242
return false;
227243
}
228244
}
@@ -231,9 +247,25 @@ private function decodeToken(string $token): ?array
231247
{
232248
try {
233249
$parts = explode('.', $token);
234-
$decoded = json_decode(base64_decode($parts[1]), true);
235-
return is_array($decoded) ? $decoded : null;
250+
if (count($parts) !== 3 || empty($parts[1])) {
251+
return null;
252+
}
253+
254+
$decoded = base64_decode($parts[1], true);
255+
if ($decoded === false) {
256+
error_log('Token base64 decode failed in decodeToken');
257+
return null;
258+
}
259+
260+
$payload = json_decode($decoded, true);
261+
if ($payload === null && json_last_error() !== JSON_ERROR_NONE) {
262+
error_log('Token JSON decode failed in decodeToken: ' . json_last_error_msg());
263+
return null;
264+
}
265+
266+
return is_array($payload) ? $payload : null;
236267
} catch (\Exception $e) {
268+
error_log('Token decode error: ' . $e->getMessage());
237269
return null;
238270
}
239271
}

tests/Http/Pool/SimplePoolManagerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace PivotPHP\Tests\Http\Pool;
5+
namespace PivotPHP\Core\Tests\Http\Pool;
66

77
use PHPUnit\Framework\TestCase;
88
use PivotPHP\Core\Http\Pool\SimplePoolManager;

0 commit comments

Comments
 (0)