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.
153 lines
5.1 KiB
PHP
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]);
|
|
}
|
|
}
|