Index

GitHubAPI

MMO\git · 1 symbol · generated 5 Oct 2026

class

GitHubAPI

GitHubAPI.php:29

A thin client for the GitHub REST API, for the things a repository cannot answer on its own: pull requests, issues, releases and the state of a branch on the server. It reads all of them, and writes issues, and the labels and milestones they use (IssueBoard shows them as a kanban board).

class MMO\git\GitHubAPI extends MMO\core\classes\aModule
Namespace
MMO\git
Extends
aModule
### Configuration ```php GitHubAPI::Configure(['token' => '<a personal access token>', 'repository' => 'owner/name']); ``` Without a token only public, unauthenticated endpoints work, and GitHub rate-limits them hard (60 requests per hour, per IP). `repository` is the default `owner/name` the shorthand methods act on. > WARNING: this is a singleton -- use `GitHubAPI::Shared()`, never `new GitHubAPI()`.
GitHubAPI::Configure(['token' => $token, 'repository' => 'MarcMouret/MMO-git']); foreach (GitHubAPI::Shared()->getPullRequests() as $pr) {echo $pr['number'], ': ', $pr['title'], "\n";}

Index

API_VERSION
repository
token
url
user
userAgent
assertRepository($repository)
createComment($number, $body, $repository)
createIssue($fields, $repository)Open an issue. Needs a token allowed to write the repository's issues.
createLabel($name, $color, $repository)
createMilestone($title, $repository)
DefaultConfig() static
deleteComment($id, $repository)
exec($path, $post, $method)Call an API endpoint.
get($path, $post, $method)Call an API endpoint and decode its JSON body.
getAll($path, $limit)Read a list endpoint page after page: GitHub answers 30 items per call unless asked, and 100 at most.
getAssignees($repository)
getBranch($branch, $repository)
getBranchHash($branch, $repository)
getComment($id, $repository)
getComments($number, $repository)
getHeaders()
getIssue($number, $repository)
getIssues($state, $repository, $limit)
getLabels($repository)
getMilestones($state, $repository)
getPullRequests($state, $repository)
getRateLimit()
getReleases($repository)
getRepository()
getRepositoryInfo($repository)
getTokenFingerprint()
getUser()
graphql($query, $variables)Run a GraphQL query or mutation: what the REST API does not cover, GitHub Projects first of all.
isAuthenticated()
mutate($name, $input, $select)Run one GraphQL mutation, whose input type is named after it, as GitHub's all are.
RepositoryName($repo, $remote) staticDerive the owner/name of a repository from one of its remotes, so a local Repository can be matched to its GitHub project without repeating the name in the configuration.
setRepository($repository)
setToken($token)
updateComment($id, $body, $repository)
updateIssue($number, $fields, $repository)Change an issue: only the fields given change.
updateLabel($name, $fields, $repository)
__wakeup()

Constants1

API_VERSION

const API_VERSION = '2022-11-28'

Properties5

repository

protected ?string $repository = null
Type
null | string

token

protected ?string $token = null
Type
null | string

url

protected string $url = 'https://api.github.com'
Type
string

user

#[MMO\core\attributes\Serialize(false)] protected ?array $user = null
Type
null | array

userAgent

protected string $userAgent = 'mmo.git'
Type
string

Methods37

assertRepository($repository)

GitHubAPI.php:201
function assertRepository(?string $repository = null): string
$repository = nullnull | stringowner/name, or null for the configured one
returnsstring
throwswhen neither is set

createComment($number, $body, $repository)

GitHubAPI.php:297
function createComment(int $number, string $body, ?string $repository = null): array
$numberintthe issue's
$bodystringmarkdown
$repository = nullnull | string
returnsarraythe new comment
GitHubAPI::Shared()->createComment(12, 'Fixed in **2.2**.');

createIssue($fields, $repository)

GitHubAPI.php:261

Open an issue. Needs a token allowed to write the repository's issues.

function createIssue(array $fields, ?string $repository = null): array
$fieldsarraytitle (required), and body, labels (names), assignees (logins), milestone (number)
$repository = nullnull | string
returnsarraythe new issue, number included
GitHubAPI::Shared()->createIssue(['title' => 'Ship 2.2', 'labels' => ['release']]);

createLabel($name, $color, $repository)

GitHubAPI.php:320
function createLabel(string $name, string $color, ?string $repository = null): array
$namestring
$colorstringrrggbb, without #
$repository = nullnull | string
returnsarraythe new label

createMilestone($title, $repository)

GitHubAPI.php:351
function createMilestone(string $title, ?string $repository = null): array
$titlestring
$repository = nullnull | string
returnsarraythe new milestone, number included

exec($path, $post, $method)

GitHubAPI.php:119

Call an API endpoint.

function exec(string $path, ?array $post = null, ?string $method = null): MMO\core\server\StreamResponse
$pathstringthe path after the api root, e.g. repos/owner/name/pulls
$post = nullnull | arraya body to send as JSON (null: a GET)
$method = nullnull | stringoverride the HTTP method
returnsStreamResponse

get($path, $post, $method)

GitHubAPI.php:140

Call an API endpoint and decode its JSON body.

function get(string $path, ?array $post = null, ?string $method = null): array
$pathstring
$post = nullnull | array
$method = nullnull | string
returnsarray
throwswhen GitHub answered with an error, or with something that is not JSON

getAll($path, $limit)

GitHubAPI.php:228

Read a list endpoint page after page: GitHub answers 30 items per call unless asked, and 100 at most.

function getAll(string $path, ?int $limit = null): array
$pathstringthe path, with or without a query string
$limit = nullnull | intthe number of items to read at most, null for every page
returnsarraythe items of every page read, in order
GitHubAPI::Shared()->getAll('repos/owner/name/labels');

getAssignees($repository)

GitHubAPI.php:335
function getAssignees(?string $repository = null): array
$repository = nullnull | string
returnsarraywho issues can be assigned to: {login, avatar_url, html_url} each.

getBranch($branch, $repository)

GitHubAPI.php:361
function getBranch(string $branch, ?string $repository = null): array
$branchstring
$repository = nullnull | string
returnsarraythe branch, including the commit it currently points at.

getBranchHash($branch, $repository)

GitHubAPI.php:366
function getBranchHash(string $branch, ?string $repository = null): string
$branchstring
$repository = nullnull | string
returnsstringthe hash the branch points at on the server.

getComment($id, $repository)

GitHubAPI.php:287
function getComment(int $id, ?string $repository = null): array
$idint
$repository = nullnull | string
returnsarrayone comment of an issue, issue_url included.

getComments($number, $repository)

GitHubAPI.php:282
function getComments(int $number, ?string $repository = null): array
$numberintthe issue's (or pull request's: its conversation, not its review comments)
$repository = nullnull | string
returnsarrayits comments, oldest first: {id, user: {login, avatar_url, html_url}, author_association, body, created_at, updated_at, html_url} each

getHeaders()

GitHubAPI.php:102
protected function getHeaders(): array
returnsarraythe headers every call sends, including the token when one is configured.

getIssue($number, $repository)

GitHubAPI.php:251
function getIssue(int $number, ?string $repository = null): array
$numberint
$repository = nullnull | string
returnsarrayone issue (or pull request: GitHub numbers both alike).

getIssues($state, $repository, $limit)

GitHubAPI.php:245
function getIssues(string $state = 'open', ?string $repository = null, ?int $limit = null): array
$state = 'open'stringopen, closed or all
$repository = nullnull | string
$limit = nullnull | intthe number of issues to read at most (the most recently created first), null for all
returnsarraythe issues. GitHub includes pull requests here, so entries carrying a pull_request key are dropped -- getPullRequests lists those. They still count in $limit.

getLabels($repository)

GitHubAPI.php:311
function getLabels(?string $repository = null): array
$repository = nullnull | string
returnsarrayevery label of the repository, {name, color, description} each.

getMilestones($state, $repository)

GitHubAPI.php:343
function getMilestones(string $state = 'open', ?string $repository = null): array
$state = 'open'stringopen, closed or all
$repository = nullnull | string
returnsarraythe milestones, {number, title, state, due_on} each.

getPullRequests($state, $repository)

GitHubAPI.php:217
function getPullRequests(string $state = 'open', ?string $repository = null): array
$state = 'open'stringopen, closed or all
$repository = nullnull | string
returnsarraythe pull requests

getRateLimit()

GitHubAPI.php:380
function getRateLimit(): array
returnsarraythe rate limit left for this client, as {limit, remaining, reset} -- worth checking before a loop of calls, because an exhausted limit answers 403 rather than 429.

getReleases($repository)

GitHubAPI.php:356
function getReleases(?string $repository = null): array
$repository = nullnull | string
returnsarraythe releases, most recent first.

getRepositoryInfo($repository)

GitHubAPI.php:209
function getRepositoryInfo(?string $repository = null): array
$repository = nullnull | string
returnsarraythe repository's metadata.

getTokenFingerprint()

GitHubAPI.php:78
function getTokenFingerprint(): string
returnsstringwhat tells this client's token from another one, without telling the token: two tokens may see different things (a private repository), so a cache of what GitHub answered keys on it

getUser()

GitHubAPI.php:371
function getUser(): array
returnsarraythe authenticated user, asked once. @throws eRuntime when no token is configured.

graphql($query, $variables)

GitHubAPI.php:168

Run a GraphQL query or mutation: what the REST API does not cover, GitHub Projects first of all. GraphQL always needs a token.

function graphql(string $query, array $variables = []): array
$querystring
$variables = []array
returnsarraythe answer's data
throwswhen there is no token, or GitHub answered with errors (which it does with a 200)
GitHubAPI::Shared()->graphql('query($login: String!) {user(login: $login) {id}}', ['login' => 'octocat']);

mutate($name, $input, $select)

GitHubAPI.php:189

Run one GraphQL mutation, whose input type is named after it, as GitHub's all are.

function mutate(string $name, array $input, string $select = 'clientMutationId'): array
$namestringe.g. addProjectV2DraftIssue
$inputarray
$select = 'clientMutationId'stringwhat to read from its payload
returnsarraythe payload
GitHubAPI::Shared()->mutate('deleteProjectV2Item', ['projectId' => $project, 'itemId' => $item], 'deletedItemId');

RepositoryName($repo, $remote)

staticGitHubAPI.php:90

Derive the owner/name of a repository from one of its remotes, so a local Repository can be matched to its GitHub project without repeating the name in the configuration.

static function RepositoryName(MMO\git\helpers\Repository $repo, string $remote = ''): ?string
$repoRepository
$remote = ''stringthe remote to read, or '' for the first one
returnsnull | stringnull when no remote points at github.com
GitHubAPI::RepositoryName($repo); // 'MarcMouret/MMO-git'

updateComment($id, $body, $repository)

GitHubAPI.php:302
function updateComment(int $id, string $body, ?string $repository = null): array
$idint
$bodystring
$repository = nullnull | string
returnsarraythe comment, as it is now.

updateIssue($number, $fields, $repository)

GitHubAPI.php:273

Change an issue: only the fields given change. A label named here and unknown to the repository is created.

function updateIssue(int $number, array $fields, ?string $repository = null): array
$numberint
$fieldsarrayas createIssue, plus state (open, closed) and state_reason (completed, not_planned, reopened); milestone null takes it out of its milestone
$repository = nullnull | string
returnsarraythe issue as it is now
GitHubAPI::Shared()->updateIssue(12, ['state' => 'closed', 'state_reason' => 'completed']);

updateLabel($name, $fields, $repository)

GitHubAPI.php:330
function updateLabel(string $name, array $fields, ?string $repository = null): array
$namestringthe label's name now
$fieldsarraynew_name, color (rrggbb), description
$repository = nullnull | string
returnsarraythe label, as it is now
GitHubAPI::Shared()->updateLabel('bug', ['color' => 'cf222e']);
Inherited members (47)
ConstantsDeclared byDescription
ALERTiLogLevelAlert: Action must be taken immediately.
ALLiLogLevelMask for all
ALL_PLUSiLogLeveldebug everything
BACKTRACEiLogLevel
CRITICALiLogLevelCritical: Critical conditions.
DEBUGiLogLevelDebug: Detailed debug information.
DEEPiLogLeveldebug everything with arguments
EMERGENCYiLogLevelEmergency: System is unusable.
ERRORiLogLevelError: Runtime errors that do not require immediate action but should typically be logged and monitored.
ERROR_PLUSiLogLevelMask for all error
FATALiLogLevelMask for all fatal error
INFOiLogLevelInfo: Interesting events.
INFO_PLUSiLogLevelMask for all errors/warning/notices/info
MEMORYiLogLevelinclude backtrace object
NOTICEiLogLevelNotice: Normal but significant events.
NOTICE_PLUSiLogLevelMask for all errors/warning/notices/info
TRACEiLogLevelinclude backtrace as origins
WARNINGiLogLevelWarning: Exceptional occurrences that are not errors.
WARNING_PLUSiLogLevelMask for all errors and warnings
PropertiesDeclared byDescription
verbosityaModule
MethodsDeclared byDescription
alert($message, $context)aModuleAction must be taken immediately.
backtraces($message, $context)aModuleForce-log a message at INFO level together with a full stack trace/memory snapshot (iLogLevel::BACKTRACE), bypassing the verbosity mask entirely (any non-zero verbosity is enough to trigger it).
Configure($config)aModuleMerge $config into this module's configuration in the shared Configuration.
critical($message, $context)aModuleCritical conditions. No-op if CRITICAL is currently filtered out by the verbosity mask.
debug($message, $context)aModuleDetailed debug information.
debugName()aModuleHuman-readable identifier used as the default {origin} in this instance's log messages.
emergency($message, $context)aModuleSystem is unusable. No-op if EMERGENCY is currently filtered out by the verbosity mask.
error($message, $context)aModuleRuntime errors that do not require immediate action but should typically be logged and monitored.
getVerbosity()aModule
info($message, $context)aModuleInteresting events. No-op if INFO is currently filtered out by the verbosity mask.
Instantiate($config)aModule(Re)create the shared instance immediately, configured with $config merged into the class's existing config.
IsShared()aModule
log($level, $message, $context)aModuleLogs with an arbitrary level.
notice($message, $context)aModuleNormal but significant events.
offsetExists($key)aModule\ArrayAccess::offsetExists -- tells whether $key is a declared property at all, regardless of whether it is currently readable/writable through array access.
offsetGet($key)aModule\ArrayAccess::offsetGet -- reads a property directly, unless it has a #[Serialize(serialize: 'methodName')] attribute, in which case that zero-argument method is called instead (e.g.
offsetSet($key, $value)aModule\ArrayAccess::offsetSet -- sets a property directly, unless it has a #[Serialize(unserialize: 'methodName')] attribute, in which case that method is called with $value instead (e.g.
offsetUnset($key)aModule\ArrayAccess::offsetUnset -- equivalent to $module[$key] = null.
serialize($path, $serializer)aModule\MMO\core\interfaces\iSerializable::serialize -- exports every property (via
setVerbosity($verb)aModuleSet the current verbosity mask, replacing whatever it was before.
Shared()aModuleGet the shared instance, creating (and configuring) it on first call.
testVerbosity($level)aModuleTest whether a given level (or set of levels) is currently enabled by this instance's verbosity mask.
traitUnshare($serialize)aModuleRemove the shared instance, so the next Shared() call creates a fresh one.
unserialize($data, $path, $instances)aModule\MMO\core\interfaces\iSerializable::unserialize -- merges $data onto DefaultConfig, runs it through the (possibly overridden) constructor, then applies each resulting value to its property (via
Unshare($serialize)aModuleLike tSingleton::Unshare, but also disconnects this module's instance from every event it was listening to (via Event::DisconnectListeners) before dropping it.
warning($message, $context)aModuleExceptional occurrences that are not errors.
__construct($config)aModuleprotected constructor, so it is not possible to do new Module(...) called by the serialization process just before being injected with data.