Update: you may prefer my post about doing this in Laravel.
GitHub’s new Projects are not accessible via the older REST API. Working with them programmatically involves learning some GraphQL, which can be a headache, the first time you encounter it. Here’s my approach, using PHP.
Set up cURL
You can certainly use an HTTP request library, but sometimes it’s easiest to get your hands dirty with cURL. Here’s the setup:
1 2 3 4 5 6 7 8 9 10 11 12 13 | $ch = curl_init(); curl_setopt( $ch , CURLOPT_RETURNTRANSFER, 1); curl_setopt( $ch , CURLOPT_HTTPHEADER, array ( 'Accept: application/vnd.github+json' , 'Authorization: Bearer ' . $_ENV [ 'GH_TOKEN' ] )); // Fake user agent, to keep GitHub happy curl_setopt( $ch , CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0' ); // Dev settings (PHP built-in server can't validate) curl_setopt( $ch , CURLOPT_SSL_VERIFYHOST, false); curl_setopt( $ch , CURLOPT_SSL_VERIFYPEER, false); |
The “dev settings” are there since I’ve been using PHP’s built-in webserver during development. Don’t take those into production!
Generic API class
This class provides a simple wrapper around REST API and GraphQL calls. I’m passing in the cURL handler and calling most methods statically, since I’m not yet modelling the underlying objects.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | class GitHub { /** * API * Make request to GitHub using the standard REST API. * * @param [CurlHandle Object] $ch - curl handle * @param [string] $endpoint - the REST endpoint * @return array|stdClass */ public static function api(\CurlHandle $ch , string $endpoint ) { curl_setopt( $ch , CURLOPT_CUSTOMREQUEST, 'GET' ); $content = curl_exec( $ch ); if (curl_errno( $ch )) { die ( "Error:" .curl_error( $ch )); } return json_decode( $content ); } /** * GraphQL * Make request to GitHub using the newer GraphQL API. * GraphQL Explorer: https://docs.github.com/en/graphql/overview/explorer * GraphQL Formatter: https://jsonformatter.org/graphql-formatter * * @param CurlHandle $ch * @param string $query - a GraphQL query string * @param array $variables - an array of GraphQL variables * @return StdClass */ public static function graphQL(\CurlHandle $ch , string $query , array $variables ) { curl_setopt( $ch , CURLOPT_CUSTOMREQUEST, 'POST' ); curl_setopt( $ch , CURLOPT_POSTFIELDS, json_encode([ 'query' => $query , 'variables' => $variables ]) ); $content = curl_exec( $ch ); if (curl_errno( $ch )) { die ( "Error:" .curl_error( $ch )); } return json_decode( $content ); } } |
Projects class
Various simple methods for obtaining information about projects. I say “simple” but the GraphQL queries take some mastering.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | class GitHubProjects { /** * getFirstN - get first N projects for the GitHub organisation, ordered by * title * * @param \CurlHandle $ch * @param integer $count - number of projects to return * @return array */ public static function getFirstN(\CurlHandle $ch , int $count = 20): array { $projects = GitHub::graphQL( $ch , <<<EOD query getFirstN(\ $count : Int) { organization(login: "MyOrg" ) { projectsV2(first: \ $count , orderBy: {field: TITLE, direction: ASC}) { nodes { number id title } } } } EOD, [ "count" => $count ] ); return $projects ->data->organization->projectsV2->nodes; } /** * getFirstNIssues * * @param \CurlHandle $ch * @param integer $projectNumber * @param integer $count * @return array */ public static function getFirstNIssues(\CurlHandle $ch , int $projectNumber = 1, int $count = 20): array { $projectId = GitHubProjects::getIdByNumber( $ch , $projectNumber ); $issues = GitHub::graphQL( $ch , <<<EOD # Exclamation mark since projectId is required query getFirstNIssues(\ $projectId : ID!, \ $count : Int) { node(id: \ $projectId ) { ... on ProjectV2 { items(first: \ $count ) { nodes { id fieldValues(first: 8) { nodes { ... on ProjectV2ItemFieldTextValue { text field { ... on ProjectV2FieldCommon { name } } } ... on ProjectV2ItemFieldDateValue { date field { ... on ProjectV2FieldCommon { name } } } ... on ProjectV2ItemFieldSingleSelectValue { name field { ... on ProjectV2FieldCommon { name } } } } } content { ... on DraftIssue { title body } ... on Issue { title assignees(first: 10) { nodes { login } } } ... on PullRequest { title assignees(first: 10) { nodes { login } } } } } } } } } EOD, [ "projectId" => $projectId , "count" => $count ] ); return $issues ->data->node->items->nodes; } /** * getByTitle - search for projects by title * * @param \CurlHandle $ch * @param string $title - search for * @return array */ public static function getByTitle(\CurlHandle $ch , string $title ): array { $projects = GitHub::graphQL( $ch , <<<EOD query getByName(\ $title : String!) { organization(login: "MyOrg" ) { projectsV2( first: 20 orderBy: { field: TITLE, direction: ASC } query: \ $title ) { nodes { number id title } } } } EOD, [ "title" => $title ] ); return $projects ->data->organization->projectsV2->nodes; } /** * getIdByNumber * * @param \CurlHandle $ch * @param integer $number - the integer identifier of the project * @return string - the internal GitHub project ID (e.g. PVT_kwDOBWQiz84AH53W) */ private static function getIdByNumber(\CurlHandle $ch , int $number = 1) { $project = GitHub::graphQL( $ch , <<<EOD # Exclamation mark since number is required query getIdByNumber(\ $number : Int!) { organization(login: "MyOrg" ) { projectV2(number: \ $number ) { id } } } EOD, [ 'number' => $number ] ); return $project ->data->organization->projectV2->id; } } |
Using it
I have not included namespacing or autoloading here. In broad terms, it’s simple to use the Projects class:
1 2 3 4 5 | $projects = Model\GitHubProjects::getByTitle( $ch , 'MyProject' ); if ( is_array ( $projects ) && ! empty ( $projects )) { $project = $projects [0]; var_dump( $project ); } |