GitHub APIでIssueを操作する with PHP

GitHubへ一度に大量のIssueの登録をしなければならない、プロジェクト開始時などよくあるシチュエーションかと思います。10件や20件程度なら手作業でも良いのですが、数十個を超えてくるとさすがに人間でやるのは大変すぎるので機械にお願いしたいところ。

今回はGitHub APIをPHPから利用し、Issueの登録と更新、一覧の取得を行いたいと思います。

GitHub API

GitHubには開発者用に各種APIが整備されており、RESTful APIGraphQLを用いて様々な操作を行うことができます。

おとこらしくcurlなどで実装してもよいのですが、すでに公開されているライブラリの中から好みの物を用いてIssueの操作を行います。本来の目的は楽をするはずなのに、ここで苦労してはよくわからないことになりますしねw developer.github.com

準備

ライブラリ

今回はGitHub PHP Clientを使用します。 github.com

ダウンロードするか、git cloneしてやります。

$ git clone https://github.com/tan-tan-kanarek/github-php-client.git

アカウントとリポジトリ

このあと出てきますが、以下の情報が必要になるのでこちらもメモする、必要であれば新規に作成します。

  • GitHubアカウント
    • ログインID
    • ログインパスワード
  • リポジトリ
    • オーナー名 (URLに入っている文字列)
    • リポジトリ名 (URLに入っている文字列)
  • チケットに付加したい情報
    • 担当者 (assignees)
    • マイルストーン (milestone)
    • ラベル (labels)

Issueを新規登録する

サンプルコード

実際のコードは以下になります。 インスタンスを作成し、所定のメソッドを実行するだけと非常に簡単に実装できます。

<?php
/**
 * Create an Issue for GitHub
 */

//------------------------------------------------------
// 準備
//------------------------------------------------------
// 定数定義
define('GITHUB_USER',  'foobar');          //ログインID
define('GITHUB_PW',    'xxxxxxxxxxxxx');   //ログインパスワード
define('GITHUB_OWNER', 'katsube');         //リポジトリのオーナー名
define('GITHUB_REPOS', 'test');            //リポジトリ名

// ライブラリ
require_once('github-php-client/client/GitHubClient.php');

//------------------------------------------------------
// Issueを追加
//------------------------------------------------------
$client = new GitHubClient();
$client->setCredentials(GITHUB_USER, GITHUB_PW);    //ログイン情報をセット

//投稿する
try{
  $client->issues->createAnIssue(
      GITHUB_OWNER
    , GITHUB_REPOS
    , 'タイトル'
    , '本文'
  );
}
catch( GitHubClientException $e ){
  echo $e->getMessage();
}

なお本文はオプションですので、未指定(=null)でも大丈夫です。

実行する

先ほどのサンプルコードをライブラリがあるフォルダと同じ階層にcreate.phpというファイル名で保存し、コマンドラインなどから叩いてやります。もちろんWebサーバ内での実行もOK。

$ ls
create.php  github-php-client/

$ php create.php

本文の指定方法

やってみればわかるのですが、改行やMarkdown絵文字の利用も可能です。

<?php
//------------------------------------------------------
// 準備
//------------------------------------------------------
$title = '無慈悲なチャーハン作るよ';
$body = <<< EOS
緊急献立会議し、チャーハンを作るよう指示しました :rage:

## 食材の仕入れ
* 食材を仕入れるよう指示した。 
* 食材を仕入れる準備に入った。 
* 食材の仕入れが完了し調理待機状態に突入した。 

## 下ごしらえ
* 食材の下拵えを指示した。 
* 鋼鉄の包丁がかつてない程の切れ味で食材を切り裂くだろうと発表した。 
* 鋼鉄の包丁が待機状態に入ったと発表した。 
* 食材の下拵えが終了したと発表した。 

つづきは [まとめサイト](https://yukawanet.com/archives/5204547.html)を参照。
EOS;

//------------------------------------------------------
// 作成
//------------------------------------------------------
$client->issues->createAnIssue(
      GITHUB_OWNER
    , GITHUB_REPOS
    , $title
    , $body
);

実行すると以下のようなIssueが作成されます。

Markdownや絵文字は以下のページを参照ください。 guides.github.com www.webpagefx.com

assignees, milestone, labelsを指定する

引数を追加することでassignees, milestone, labelsを指定することが可能です。いずれも必要ない箇所はnullを指定します。

<?php
//------------------------------------------------------
// 準備
//------------------------------------------------------
// 担当者
$assignees = ['katsube'];  //配列で指定。複数可。

// マイルストーン
$milestone = 1;  //マイルストーンを作成した順番に割り振られる数値を指定

// ラベル
$labels = ['bug', 'question'];  //配列で指定。複数可。

//------------------------------------------------------
// 作成
//------------------------------------------------------
$client->issues->createAnIssue(
      GITHUB_OWNER
    , GITHUB_REPOS
    , 'タイトル'
    , '本文'
    , $assignees
    , $milestone
    , $labels
);

マイルストーンの数値の確認方法

リポジトリのページにあるIssuesタブをクリック、検索ボックス横にあるMilestonesボタンをクリック、マイルストーン名をクリックすると詳細ページに遷移します。このページのURLのお尻にある数値を指定すればOK。

Issueを更新する

editAnIssue()メソッドで行います。

サンプルコード

様々な項目の更新が行えますが、項目で更新したくない箇所はnullを指定します。

<?php
// 定数定義
define('GITHUB_USER',  'foobar');          //ログインID
define('GITHUB_PW',    'xxxxxxxxxxxxx');   //ログインパスワード
define('GITHUB_OWNER', 'katsube');         //リポジトリのオーナー名
define('GITHUB_REPOS', 'test');            //リポジトリ名

// ライブラリ
require_once('github-php-client/client/GitHubClient.php');

// インスタンス生成
$client = new GitHubClient();
$client->setCredentials(GITHUB_USER, GITHUB_PW);    //ログイン情報をセット

try{
    $client->issues->editAnIssue(
          GITHUB_OWNER    // [必須]
        , GITHUB_REPOS    // [必須]
        , $title          // [任意] タイトル
        , $number         // [必須] Issue番号
        , $body           // [任意] 本文
        , $assignees      // [任意] 担当者(作成時と同様に配列でユーザー名)
        , $state          // [任意] Issueの状態("open" or "close")
        . $milestone      // [任意] マイルストーン(番号を指定)
        , $labels         // [任意] ラベル(作成時と同様に配列で指定)
    );
}
catch( GitHubClientException $e ){
    echo $e->getMessage();
}

このメソッド、なぜか必須のIssue番号($number)が、任意のタイトル($title)よりも後に来るという気持ち悪い作りになってますねw 歴史的な経緯なのでしょうがぜひ修正してほしいですね(;´∀`)

Issueを削除する

残念ながらGitHubの仕様でIssueの削除はできません。不要なIssueはCloseするしかないため、API経由の場合はIssueを更新するからステータスを変更してください。

Issueの詳細情報を取得する

サンプルコード

getIssue()メソッドにリポジトリ情報と共に、取得したいIssue番号を指定することで詳細情報が取得できます。

<?php
// 定数定義
define('GITHUB_USER',  'foobar');          //ログインID
define('GITHUB_PW',    'xxxxxxxxxxxxx');   //ログインパスワード
define('GITHUB_OWNER', 'katsube');         //リポジトリのオーナー名
define('GITHUB_REPOS', 'test');            //リポジトリ名

// ライブラリ
require_once('github-php-client/client/GitHubClient.php');

// インスタンス生成
$client = new GitHubClient();
$client->setCredentials(GITHUB_USER, GITHUB_PW);    //ログイン情報をセット

try{
  $issue = $client->issues->getIssue(
              GITHUB_OWNER
            , GITHUB_REPOS
            , 10             //Issue番号
  );

  var_dump(
    $issue->getUrl(),
    $issue->getHtmlUrl(),
    $issue->getNumber(),
    $issue->getState(),
    $issue->getTitle(),
    $issue->getBody(),
    $issue->getUser(),
    $issue->getAssignee(),
    $issue->getMilestone(),
    $issue->getComments(),
    $issue->getClosedAt(),
    $issue->getCreatedAt(),
    $issue->getUpdatedAt(),
    $issue->getPullRequest()
  );
}
catch( GitHubClientException $e ){
  echo $e->getMessage();
}

取得できる項目

各項目の詳細は以下になります。

メソッド レスポンスのサンプル
$issue->getUrl() "https://api.github.com/repos/katsube/repos/issues/10"
$issue->getHtmlUrl() "https://github.com/katsube/repos/issues/10"
$issue->getNumber() 10
$issue->getState() "open"
$issue->getTitle() "チャーハンを提供する"
$issue->getBody() "本文の文字列"
$issue->getUser() GitHubUserクラスのオブジェクト
$issue->getAssignee() GitHubUserクラスのオブジェクト
$issue->getMilestone() GitHubMilestoneクラスのオブジェクト
$issue->getComments() 1
$issue->getClosedAt() "2018-12-09T15:38:00Z"
$issue->getCreatedAt() "2018-12-09T13:20:49Z"
$issue->getUpdatedAt() "2018-12-09T15:36:10Z"
$issue->getPullRequest() ※後日

Issueの一覧を取得する

サンプルコード

<?php
//------------------------------------------------------
// 準備
//------------------------------------------------------
// 定数定義
define('GITHUB_OWNER', 'katsube');         //リポジトリのオーナー名
define('GITHUB_REPOS', 'test');            //リポジトリ名

// ライブラリ
require_once('github-php-client/client/GitHubClient.php');

//------------------------------------------------------
// Issueの一覧を取得
//------------------------------------------------------
$client = new GitHubClient();
$client->setPage();

try{
    $issues = $client->issues->listIssues(GITHUB_OWNER, GITHUB_REPOS);
}
catch( GitHubClientException $e ){
    echo $e->getMessage();
}

foreach ($issues as $issue){
    printf("%02d: %s\n", $issue->getNumber(), $issue->getTitle());
}

実行する

先ほどのサンプルコードをライブラリがあるフォルダと同じ階層にlist.phpというファイル名で保存し、コマンドラインなどから叩いてやります。もちろんWebサーバ内での実行もOK。

$ ls
create.php  github-php-client/  list.php

$ php list.php
10: チャーハンを提供する
09: チャーハンを盛り付ける
08: 無慈悲なチャーハン作るよ

デフォルトでは、OpenしているIssueのみ取得されます。またIssue番号の降順で配列には入ってくるようです。

非公開リポジトリのIssue一覧を取得する

先ほどのサンプルコードで非公開リポジトリを覗こうとすると、404が返されてしまいます。まぁAPI経由ならのぞき放題なわけないですよねw

$ php list.php
Expected status [200], actual status [404], URL [/repos/katsube/secret/issues]

非公開リポジトリを対象とする場合には、あらかじめ権限を付与したアカウント(もしくはオーナー)のログイン情報をsetCredentials()メソッドでセットする必要があります。

<?php
define('GITHUB_USER',  'foobar');          //ログインID
define('GITHUB_PW',    'xxxxxxxxxxxxx');   //ログインパスワード
define('GITHUB_OWNER', 'katsube');         //リポジトリのオーナー名
define('GITHUB_REPOS', 'secret');          //リポジトリ名

$client = new GitHubClient();
$client->setCredentials(GITHUB_USER, GITHUB_PW);    //ログイン情報をセット
$client->setPage();

try{
    $issues = $client->issues->listIssues(GITHUB_OWNER, GITHUB_REPOS);
}
catch( GitHubClientException $e ){
    echo $e->getMessage();
}

絞り込み条件を設定する

listIssues()メソッドは対象とするIssueの条件を細かく指定できます。必要がない項目はnullを指定します。

<?php
$client->issues->listIssues(
    GITHUB_OWNER     //[必須]
  , GITHUB_REPOS     //[必須]
  , $milestone       //[任意] マイルストーンを指定
  , $state           //[任意] "open", "closed", "all"を指定。デフォルトは"open"
  , $assignee        //[任意] 担当者のユーザー名を指定
  , $creator         //[任意] Issue作成者のユーザー名を指定
  , $mentioned       //[任意] 指定ユーザーの返信(メンション)があるIssueを絞り込み。ユーザー名を指定。
  , $labels          //[任意] ラベルをカンマ区切りの文字列で指定。例えば"bug,question"
  , $sort            //[任意] ソート項目を指定。"created", "updated", "comments"のいずれか。デフォルトは"created"
  , $direction       //[任意] ソート順を指定。"asc", "desc"。デフォルトは"desc"。
  , $since           //[任意] 指定時刻以降に更新されたIssueのみを返却。YYYY-MM-DDTHH:MM:SSZ形式(ISO 8601)
);

ページング

setPage()メソッドで何ページ目か、setPageSize()メソッドで一度に取得する件数を指定できます。

<?php
$client = new GitHubClient();

try{
    $client->setPageSize(3);      // 一度に取得するIssue数。デフォルトは100。
    $page = 1;                    // ページ番号の初期値

    do{
      // ページ番号。デフォルトは1。
      $client->setPage($page++);

      // issueを取得
      $issues = $client->issues->listIssues(GITHUB_OWNER, GITHUB_REPOS);

      //表示
      printIssue($issues);
    }
    while( count($issues) > 0 );
}
catch( GitHubClientException $e ){
    echo $e->getMessage();
}

/**
 * issueを表示する
 */
function printIssue($issues){
    foreach ($issues as $issue){
        printf("%02d: %s\n", $issue->getNumber(), $issue->getTitle());
    }
}

1回分余計なリクエストが発生するのが気になりますが、一応動きますね。 当初、$clent->hasNextPage()とかあるので期待をしたのですが、常にtrueが返ってくるというね…。

今後はGraphQLが主流に?

開発者向けのドキュメントによると最新のVersion4ではGraphQLのみの対応のようですね。

Why is GitHub using GraphQL? GitHub chose GraphQL for our API v4 because it offers significantly more flexibility for our integrators. The ability to define precisely the data you want—and only the data you want—is a powerful advantage over the REST API v3 endpoints. GraphQL lets you replace multiple REST requests with a single call to fetch the data you specify.

https://developer.github.com/v4/ より

しばらくはVersion3も稼働するのでしょうが、折を見て移行するのが良さそうです。

書籍

GitHub実践入門 ~Pull Requestによる開発の変革 (WEB+DB PRESS plus)
大塚 弘記
技術評論社
売り上げランキング: 78,155

参考ページ

github.com developer.github.com