implemented FileStorage (gemini) as step 02

This commit is contained in:
2026-06-12 16:01:33 +02:00
parent a334da7231
commit 3849e0572c

View File

@ -0,0 +1,106 @@
<?php
namespace App\Services;
use Exception;
class FileStorage
{
private string $basePath;
public function __construct(?string $basePath = null)
{
// Default base path is the 'data' directory in the project root
if ($basePath === null) {
$this->basePath = realpath(__DIR__ . '/../../data');
} else {
$this->basePath = realpath($basePath);
}
if (!$this->basePath || !is_dir($this->basePath)) {
throw new Exception("Invalid base path for FileStorage.");
}
}
/**
* Safely resolves and validates a path within the base directory.
* Prevents path traversal attacks.
*/
private function resolvePath(string $path): string
{
$fullPath = $this->basePath . DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR);
$realPath = realpath(dirname($fullPath));
if ($realPath === false || strpos($realPath, $this->basePath) !== 0) {
throw new Exception("Security Alert: Path traversal attempt detected or invalid path: " . $path);
}
return $fullPath;
}
/**
* Saves an array as a JSON file.
*/
public function put(string $path, array $data): bool
{
$fullPath = $this->resolvePath($path);
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
if (file_put_contents($fullPath, $json) === false) {
return false;
}
return true;
}
/**
* Reads a JSON file and returns it as an associative array.
*/
public function get(string $path): ?array
{
$fullPath = $this->resolvePath($path);
if (!file_exists($fullPath)) {
return null;
}
try {
$content = file_get_contents($fullPath);
if ($content === false) {
return null;
}
return json_decode($content, true, 512, JSON_THROW_ON_ERROR);
} catch (Exception $e) {
return null;
}
}
/**
* Checks if a file exists.
*/
public function exists(string $path): bool
{
try {
$fullPath = $this->resolvePath($path);
return file_exists($fullPath);
} catch (Exception $e) {
return false;
}
}
/**
* Deletes a file.
*/
public function delete(string $path): bool
{
$fullPath = $this->resolvePath($path);
if (file_exists($fullPath)) {
return unlink($fullPath);
}
return false;
}
}