Skip to content

GitHub adapter

Wraps the knplabs/github-api SDK. Currently focused on issues (not PRs, releases, etc.).

Part of the pocketarc/laravel-integrations-adapters package.

Installation

bash
composer require pocketarc/laravel-integrations-adapters

Setup

php
// config/integrations.php
'providers' => [
    'github' => \Integrations\Adapters\GitHub\GitHubProvider::class,
],
php
$integration = Integration::create([
    'provider' => 'github',
    'name' => 'My Repo',
    'credentials' => ['token' => 'ghp_...'],
    'metadata' => ['owner' => 'acme', 'repo' => 'widgets'],
]);
CredentialsMetadata
token (string) - GitHub personal access tokenowner (string) - repository owner
repo (string) - repository name

Resources

php
$client = new GitHubClient($integration);
ResourceMethodDescription
$client->issues()->create($title, $body, $labels, $idempotencyKey?)Create an issue. Returns GitHubIssueData.
->get($number)Get a single issue by number.
->since($since, $callback)Iterate issues updated since a timestamp. Skips PRs.
->close($number, $stateReason, $idempotencyKey?)Close an issue. Optional state reason (completed, not_planned, duplicate).
->reopen($number, $idempotencyKey?)Reopen a closed issue.
->timeline($number, $callback)Iterate timeline events (labels, assignments, etc.).
$client->comments()->list($number, $callback)Iterate all comments on an issue.
->add($number, $body, $idempotencyKey?)Add a comment to an issue. Returns ?GitHubCommentData.
$client->assets()->download($url)Download an asset with token auth for GitHub-hosted URLs.

All methods go through Integration::request() / requestAs() internally, so every API call is logged, health-tracked, and rate-limited. The provider implements CustomizesRetry so the core handles retry for GitHub SDK exceptions (rate limits, server errors, connection failures) with method-aware defaults (GET = 3 attempts, non-GET = 1).

Sync

The adapter syncs issues via $client->issues()->since(). For each issue it calls $session->dispatch() with a GitHubIssueSynced event. The framework wraps each one in a queued job, runs your listeners, and advances sync_cursor once every issue's job has succeeded. Listeners for GitHubIssueSynced must not implement ShouldQueue (see Scheduled syncs for the per-item model).

First sync (null cursor) fetches all issues from timestamp 0. Set sync_cursor on the integration to control the starting point:

php
$integration->updateSyncCursor('2024-05-01T00:00:00+00:00');

Every incremental sync subtracts a 1-hour buffer from the cursor to catch issues updated between runs. The framework's cursor advance is monotonic, so re-presenting items inside that window can't regress progress. Consumers should still use upsertByExternalId() in their listeners since overlap is expected.

Defaults: 5-minute sync interval, a 5,000-requests/hour rate limit (GitHub's authenticated budget), enforced as a fixed window. The adapter also feeds the adaptive rate limiter from GitHub's X-RateLimit-Remaining / X-RateLimit-Reset headers, so when a token's hourly bucket runs out, subsequent requests are suppressed until the reset window passes.

Provider request IDs

GitHub's X-GitHub-Request-Id is captured on integration_requests.provider_request_id for every call. Useful when filing issues against the SDK or GitHub's own bug tracker. Asset downloads (raw Http:: calls outside the SDK) capture the same header.

Idempotency

All GitHubIssues write methods (create(), close(), reopen()) and GitHubComments::add() accept an optional $idempotencyKey. Pass a stable, application-meaningful value (e.g. "open-issue:order-{$order->id}", "close-issue:{$issueNumber}") when you need at-most-once execution: the package writes a row in integration_idempotency_keys before the call fires, throws Integrations\Exceptions\IdempotencyConflict on a second call with the same key. The exception carries the prior response on $e->priorResponse so you can recover the original result without re-fetching. GitHub itself doesn't natively dedupe by header (GitHubProvider doesn't implement SupportsIdempotency), so the local row is the only protection here. Pass null (the default) to skip idempotency entirely. See Idempotency for the full picture.

Data classes

ClassDescription
GitHubIssueDataIssue with body, state, user, labels, assignees, attachments (extracted from body HTML via prepareForPipeline()). Stores original API response.
GitHubCommentDataComment with body, user, attachments (extracted from body HTML via prepareForPipeline()). Stores original API response.
GitHubEventDataTimeline event (label, assignment, close, etc.) with formatted descriptions. Handles cross-reference ID synthesis via prepareForPipeline().
GitHubUserDataUser with login, avatar, name, email.
GitHubAttachmentDataAttachment URL extracted from issue/comment HTML body.

Enums

EnumValues
GitHubEventType~50 timeline event types with human-readable descriptions.
GitHubIssueStateReasonCompleted, NotPlanned, Duplicate, Reopened