• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eliashaeussler / composer-package-template / 12861990819

20 Jan 2025 01:05AM UTC coverage: 99.502%. Remained the same
12861990819

push

github

web-flow
[TASK] Update all dependencies

400 of 402 relevant lines covered (99.5%)

3.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.96
/src/Service/GitHubService.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the Composer package "eliashaeussler/composer-package-template".
7
 *
8
 * Copyright (C) 2023-2025 Elias Häußler <elias@haeussler.dev>
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 */
23

24
namespace EliasHaeussler\ComposerPackageTemplate\Service;
25

26
use CPSIT\ProjectBuilder;
27
use EliasHaeussler\ComposerPackageTemplate\Enums;
28
use EliasHaeussler\ComposerPackageTemplate\Helper;
29
use EliasHaeussler\ComposerPackageTemplate\Resource;
30
use EliasHaeussler\ComposerPackageTemplate\ValueObject;
31
use Nyholm\Psr7;
32
use Psr\Http\Client;
33
use Psr\Http\Message;
34

35
use function json_encode;
36
use function sprintf;
37

38
/**
39
 * GitHubService.
40
 *
41
 * @author Elias Häußler <elias@haeussler.dev>
42
 * @license GPL-3.0-or-later
43
 */
44
final class GitHubService
45
{
46
    private const API_VERSION = '2022-11-28';
47
    private const API_TOKEN_URL = 'https://github.com/settings/tokens/new?scopes=repo';
48

49
    private readonly Message\UriInterface $baseUrl;
50

51
    public function __construct(
10✔
52
        private readonly Client\ClientInterface $client,
53
        private readonly ProjectBuilder\IO\InputReader $inputReader,
54
        private readonly ProjectBuilder\IO\Messenger $messenger,
55
        private readonly Resource\ProcessFactory $processFactory,
56
        private readonly Resource\TokenStorage $tokenStorage,
57
    ) {
58
        $this->baseUrl = new Psr7\Uri('https://api.github.com');
10✔
59
    }
60

61
    public function createRepository(ValueObject\GitHubRepository $repository): Enums\CreateRepositoryResponse
5✔
62
    {
63
        if ($this->repositoryExists($repository)) {
5✔
64
            return Enums\CreateRepositoryResponse::AlreadyExists;
1✔
65
        }
66

67
        if ($this->processFactory->isExecutable('gh')) {
4✔
68
            return $this->createRepositoryUsingBinary($repository);
2✔
69
        }
70

71
        return $this->createRepositoryUsingApi($repository);
2✔
72
    }
73

74
    public function repositoryExists(ValueObject\GitHubRepository $repository): bool
10✔
75
    {
76
        if ($this->processFactory->isExecutable('gh')) {
10✔
77
            return $this->checkIfRepositoryExistsUsingBinary($repository);
3✔
78
        }
79

80
        return $this->checkIfRepositoryExistsUsingApi($repository);
7✔
81
    }
82

83
    private function createRepositoryUsingBinary(ValueObject\GitHubRepository $repository): Enums\CreateRepositoryResponse
2✔
84
    {
85
        $createProcess = $this->processFactory->create([
2✔
86
            'gh',
2✔
87
            'repo',
2✔
88
            'create',
2✔
89
            $repository->getName(),
2✔
90
            '--description',
2✔
91
            $repository->getDescription(),
2✔
92
            $repository->isPrivate() ? '--private' : '--public',
2✔
93
        ]);
2✔
94
        $createProcess->run();
2✔
95

96
        if ($createProcess->isSuccessful()) {
2✔
97
            return Enums\CreateRepositoryResponse::Created;
1✔
98
        }
99

100
        return Enums\CreateRepositoryResponse::Failed;
1✔
101
    }
102

103
    /**
104
     * @see https://docs.github.com/en/rest/repos/repos#create-a-repository-for-the-authenticated-user
105
     */
106
    private function createRepositoryUsingApi(ValueObject\GitHubRepository $repository): Enums\CreateRepositoryResponse
2✔
107
    {
108
        $response = $this->sendPostRequest(
2✔
109
            '/user/repos',
2✔
110
            [
2✔
111
                'name' => $repository->getName(),
2✔
112
                'description' => $repository->getDescription(),
2✔
113
                'private' => $repository->isPrivate(),
2✔
114
            ],
2✔
115
        );
2✔
116

117
        return match ($response->getStatusCode()) {
2✔
118
            201 => Enums\CreateRepositoryResponse::Created,
1✔
119
            304 => Enums\CreateRepositoryResponse::AlreadyExists,
×
120
            default => Enums\CreateRepositoryResponse::Failed,
2✔
121
        };
2✔
122
    }
123

124
    private function checkIfRepositoryExistsUsingBinary(ValueObject\GitHubRepository $repository): bool
3✔
125
    {
126
        $viewProcess = $this->processFactory->create([
3✔
127
            'gh',
3✔
128
            'repo',
3✔
129
            'view',
3✔
130
            $repository->getName(),
3✔
131
        ]);
3✔
132
        $viewProcess->run();
3✔
133

134
        return $viewProcess->isSuccessful();
3✔
135
    }
136

137
    private function checkIfRepositoryExistsUsingApi(ValueObject\GitHubRepository $repository): bool
7✔
138
    {
139
        $response = $this->sendGetRequest(
7✔
140
            '/repos/{owner}/{repo}',
7✔
141
            [
7✔
142
                'owner' => $repository->getOwner(),
7✔
143
                'repo' => $repository->getName(),
7✔
144
            ],
7✔
145
        );
7✔
146

147
        return 200 === $response->getStatusCode();
7✔
148
    }
149

150
    /**
151
     * @param non-empty-string     $path
152
     * @param array<string, mixed> $json
153
     */
154
    private function sendPostRequest(string $path, array $json): Message\ResponseInterface
2✔
155
    {
156
        $request = new Psr7\Request(
2✔
157
            'POST',
2✔
158
            Helper\UriHelper::mergePath($this->baseUrl, $path),
2✔
159
            $this->getRequestHeaders(),
2✔
160
        );
2✔
161
        $request->getBody()->write(json_encode($json, JSON_THROW_ON_ERROR));
2✔
162
        $request->getBody()->rewind();
2✔
163

164
        return $this->client->sendRequest($request);
2✔
165
    }
166

167
    /**
168
     * @param non-empty-string                          $path
169
     * @param array<non-empty-string, non-empty-string> $parameters
170
     */
171
    private function sendGetRequest(string $path, array $parameters = []): Message\ResponseInterface
7✔
172
    {
173
        $request = new Psr7\Request(
7✔
174
            'GET',
7✔
175
            Helper\UriHelper::mergePath($this->baseUrl, $path, $parameters),
7✔
176
            $this->getRequestHeaders(),
7✔
177
        );
7✔
178

179
        return $this->client->sendRequest($request);
7✔
180
    }
181

182
    /**
183
     * @return array{
184
     *     Accept: non-empty-string,
185
     *     Authorization: non-empty-string,
186
     *     X-GitHub-Api-Version: non-empty-string,
187
     * }
188
     */
189
    private function getRequestHeaders(): array
7✔
190
    {
191
        return [
7✔
192
            'Accept' => 'application/vnd.github+json',
7✔
193
            // see https://docs.github.com/en/rest/overview/authenticating-to-the-rest-api#authenticating-with-a-personal-access-token
194
            'Authorization' => 'Bearer '.$this->getAccessToken(),
7✔
195
            'X-GitHub-Api-Version' => self::API_VERSION,
7✔
196
        ];
7✔
197
    }
198

199
    private function getAccessToken(): string
7✔
200
    {
201
        $token = $this->tokenStorage->get(Enums\TokenIdentifier::GitHub);
7✔
202

203
        if (null !== $token) {
7✔
204
            return $token;
6✔
205
        }
206

207
        $this->messenger->write([
1✔
208
            'Requests to GitHub API must be authorized by an access token.',
1✔
209
            'When creating a private repository, the token must have scope <comment>repo</comment>, otherwise <comment>public_repo</comment>.',
1✔
210
            sprintf('Please create your token at <href=%1$s>%1$s</>.', self::API_TOKEN_URL),
1✔
211
        ]);
1✔
212
        $this->messenger->newLine();
1✔
213

214
        $token = $this->inputReader->staticValue(
1✔
215
            'Please insert your access token',
1✔
216
            required: true,
1✔
217
        );
1✔
218

219
        $this->tokenStorage->set(Enums\TokenIdentifier::GitHub, $token);
1✔
220

221
        return $token;
1✔
222
    }
223
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc