Files
git-contribution-graph/src/Service/ContributionStore.php
T
haylan 9c028aaf5e docs(phpdoc): document public methods across src/
Add professional PHPDoc (summary + @param/@return) to the remaining
public methods that had none or only partial coverage: GraphController's
actions, ContributionStore's accessors, SvgRenderer's render() and
private helpers (with concrete array-shape annotations), ProbeTrait,
ProviderHealthChecker, ProviderStatus, and the Contribution entity.
Also adds the matching @return shapes on SvgRendererTest's data
providers.
2026-07-12 23:43:31 +02:00

153 lines
5.1 KiB
PHP

<?php
declare(strict_types=1);
namespace GitContributionGraph\Service;
use GitContributionGraph\Entity\Contribution;
use GitContributionGraph\Entity\ContributionCollection;
use PDO;
class ContributionStore
{
private PDO $pdo;
private const int DAY_IN_SECONDS = 86400;
/**
* Opens (creating if needed) the SQLite store at $dbPath and ensures the schema exists.
*
* @param string $dbPath filesystem path to the SQLite database file
* @param ?int $retentionDays days of history to keep; null or 0 keeps rows forever
*/
public function __construct(
private readonly string $dbPath = "%kernel.project_dir%/var/data/contributions.db",
private readonly ?int $retentionDays = null,
) {
$dir = \dirname($this->dbPath);
if (!is_dir($dir)) {
mkdir($dir, 0755, recursive: true);
}
$this->pdo = new PDO('sqlite:' . $this->dbPath);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->prepareSchema();
}
private function prepareSchema(): void
{
$this->pdo->exec('
CREATE TABLE IF NOT EXISTS contributions (
provider TEXT NOT NULL,
date INTEGER NOT NULL,
count INTEGER NOT NULL CHECK (count >= 0),
PRIMARY KEY (provider, date)
) WITHOUT ROWID, STRICT
');
}
/**
* Inserts a contribution count, overwriting any existing count for the same provider/date.
*
* @param string $provider provider identifier, e.g. "github"
* @param int $unixtime day of the contribution, as a unix timestamp
* @param int $count contribution count for that day
*/
public function add(string $provider, int $unixtime, int $count): void
{
$stmt = $this->pdo->prepare('
INSERT INTO contributions (provider, date, count)
VALUES (?, ?, ?)
ON CONFLICT(provider, date) DO UPDATE SET count = excluded.count
');
$stmt->execute([$provider, $unixtime, $count]);
}
/**
* Deletes the contribution row for the given provider/date, if any.
*
* @param string $provider provider identifier, e.g. "github"
* @param int $unixtime day to delete, as a unix timestamp
*/
public function remove(string $provider, int $unixtime): void
{
$stmt = $this->pdo->prepare('
DELETE FROM contributions
WHERE provider = ? AND date = ?
');
$stmt->execute([$provider, $unixtime]);
}
/**
* Upserts a batch of date => count pairs for a provider via repeated add() calls.
*
* @param string $provider provider identifier, e.g. "github"
* @param array<int, int> $dateCounts unix timestamp => count
*/
public function merge(string $provider, array $dateCounts): void
{
foreach ($dateCounts as $unixtime => $count) {
$this->add($provider, $unixtime, $count);
}
}
/**
* Returns the most recent stored unix timestamp for a provider, or null if it has no rows.
*
* @param string $provider provider identifier, e.g. "github"
* @return ?int unix timestamp of the latest stored day, or null if none stored yet
*/
public function latestDate(string $provider): ?int
{
$stmt = $this->pdo->prepare('SELECT MAX(date) FROM contributions WHERE provider = ?');
$stmt->execute([$provider]);
$result = $stmt->fetchColumn();
return $result !== null ? (int) $result : null;
}
/**
* Returns all stored contributions for a provider, optionally limited to the last $sinceDays days.
*
* @param string $provider provider identifier, e.g. "github"
* @param ?int $sinceDays if set, only rows from the last N days are returned
* @return ContributionCollection
*/
public function all(string $provider, ?int $sinceDays = null): ContributionCollection
{
$sql = 'SELECT provider, date, count FROM contributions WHERE provider = ?';
$params = [$provider];
if ($sinceDays !== null) {
$sql .= ' AND date >= ?';
$params[] = time() - $sinceDays * self::DAY_IN_SECONDS;
}
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
/** @var array<int, array{provider: string, date: int|string, count: int|string}> $rows */
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$contributions = array_map(
static fn(array $row): Contribution => new Contribution($row['provider'], (int) $row['date'], (int) $row['count']),
$rows,
);
return new ContributionCollection(...$contributions);
}
/**
* Deletes rows older than the configured retention window; a no-op if retention is unset or 0.
*/
public function prune(): void
{
if ($this->retentionDays === null || $this->retentionDays === 0) {
return;
}
$cutoff = time() - $this->retentionDays * self::DAY_IN_SECONDS;
$stmt = $this->pdo->prepare('DELETE FROM contributions WHERE date < ?');
$stmt->execute([$cutoff]);
}
}