main • app/services/BlobStore.php
<?php
class BlobStore
{
private string $base;
public function __construct(string $storageBase)
{
$this->base = rtrim($storageBase, '/');
}
private function repoBase(int $repoId): string
{
return $this->base . "/repos/" . $repoId;
}
public function ensureRepoDirs(int $repoId): void
{
$paths = [
$this->repoBase($repoId),
$this->repoBase($repoId) . "/blobs",
$this->repoBase($repoId) . "/commits",
];
foreach ($paths as $p) {
if (!is_dir($p)) mkdir($p, 0750, true);
}
}
public function blobPath(int $repoId, string $sha): string
{
$a = substr($sha, 0, 2);
$b = substr($sha, 2, 2);
return $this->repoBase($repoId) . "/blobs/$a/$b/$sha";
}
public function putBlob(PDO $db, int $repoId, string $content): array
{
$sha = hash('sha256', $content);
$size = strlen($content);
$path = $this->blobPath($repoId, $sha);
$stmt = $db->prepare("SELECT id, storage_path FROM blobs WHERE repo_id=? AND sha256=? LIMIT 1");
$stmt->execute([$repoId, $sha]);
$row = $stmt->fetch();
if ($row) {
return ['blob_id' => (int)$row['id'], 'sha' => $sha, 'path' => $row['storage_path'], 'size' => $size];
}
$dir = dirname($path);
if (!is_dir($dir)) mkdir($dir, 0750, true);
file_put_contents($path, $content, LOCK_EX);
$stmt = $db->prepare("INSERT INTO blobs (repo_id, sha256, size_bytes, storage_path, created_at) VALUES (?,?,?,?,?)");
$stmt->execute([$repoId, $sha, $size, $path, now()]);
$blobId = (int)$db->lastInsertId();
return ['blob_id' => $blobId, 'sha' => $sha, 'path' => $path, 'size' => $size];
}
public function readBlobById(PDO $db, int $blobId): string
{
$stmt = $db->prepare("SELECT storage_path FROM blobs WHERE id=? LIMIT 1");
$stmt->execute([$blobId]);
$row = $stmt->fetch();
if (!$row) throw new RuntimeException("Blob not found");
$path = $row['storage_path'];
if (!is_file($path)) throw new RuntimeException("Blob file missing");
return file_get_contents($path);
}
}