Files
git-contribution-graph/src/Service/Provider/GitLabProvider.php
T
haylan 78e7a72ee9 fix(provider): normalize baseUrl before assigning readonly property
Promoted readonly properties can't be reassigned in the constructor
body, so GitLabProvider and GiteaProvider threw on every instantiation.
Normalize the plain parameter first, then assign to a separately
declared readonly property.
2026-07-11 16:46:04 +02:00

130 lines
4.1 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Service\Provider;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
/**
* Fetches the last 365 days of push/merge events from the GitLab REST API.
*
* Required token scopes: read_user, read_api
* Works with both gitlab.com and self-hosted instances.
*/
final class GitLabProvider implements ProviderInterface
{
use ProbeTrait;
private readonly string $baseUrl;
public function __construct(
private readonly HttpClientInterface $client,
private readonly string $username,
private readonly string $token,
private readonly LoggerInterface $logger,
string $baseUrl = '',
) {
$this->baseUrl = rtrim($baseUrl !== '' ? $baseUrl : 'https://gitlab.com', '/');
}
public function getName(): string
{
return 'gitlab';
}
public function isConfigured(): bool
{
return $this->username !== '' && $this->token !== '';
}
public function ping(): void
{
$this->client->request('GET', "$this->baseUrl/api/v4/user", [
'headers' => ['PRIVATE-TOKEN' => $this->token],
])->getContent();
}
/**
* @return array{baseUrl: string, userId: int, after: string, page: int, response: ResponseInterface}
*
* ponytail: the user-id lookup and event pagination stay sequential within
* this one provider (each page depends on the previous). Parallelism here
* only spans across providers; revisit only if GitLab pagination itself
* becomes the bottleneck.
*/
public function startFetch(): array
{
$this->logger->debug('GitLabProvider: fetching contributions', ['user' => $this->username, 'url' => $this->baseUrl]);
$userResponse = $this->client->request('GET', "$this->baseUrl/api/v4/users", [
'headers' => ['PRIVATE-TOKEN' => $this->token],
'query' => ['username' => $this->username],
]);
$users = $userResponse->toArray();
if (empty($users)) {
throw new NotFoundHttpException("GitLab: user '{$this->username}' not found on $this->baseUrl");
}
$userId = $users[0]['id'];
$after = (new \DateTimeImmutable('-365 days'))->format('Y-m-d');
$response = $this->client->request('GET', "$this->baseUrl/api/v4/users/$userId/events", [
'headers' => ['PRIVATE-TOKEN' => $this->token],
'query' => [
'after' => $after,
'per_page' => 100,
'page' => 1,
],
]);
return ['baseUrl' => $this->baseUrl, 'userId' => $userId, 'after' => $after, 'page' => 1, 'response' => $response];
}
/**
* @return array<string, int> date (Y-m-d) => event count
*/
public function resolveFetch(mixed $handle): array
{
['baseUrl' => $baseUrl, 'userId' => $userId, 'after' => $after, 'page' => $page, 'response' => $response] = $handle;
$result = [];
do {
$events = $response->toArray();
foreach ($events as $event) {
$date = substr($event['created_at'], 0, 10);
$result[$date] = ($result[$date] ?? 0) + 1;
}
$page++;
if (count($events) === 100) {
$response = $this->client->request('GET', "$baseUrl/api/v4/users/$userId/events", [
'headers' => ['PRIVATE-TOKEN' => $this->token],
'query' => [
'after' => $after,
'per_page' => 100,
'page' => $page,
],
]);
}
} while (count($events) === 100);
$this->logger->info('GitLabProvider: fetched contributions', [
'user' => $this->username,
'pages' => $page - 1,
'days' => count($result),
'total' => array_sum($result),
]);
return $result;
}
}