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

jwilsson / spotify-web-api-php / 5713719944

pending completion
5713719944

push

github

jwilsson
Refactor Request class tests

Always mock the curl_* calls instead of relying on external HTTP services

743 of 753 relevant lines covered (98.67%)

10.45 hits per line

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

96.84
/src/Session.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace SpotifyWebAPI;
6

7
class Session
8
{
9
    protected string $accessToken = '';
10
    protected string $clientId = '';
11
    protected string $clientSecret = '';
12
    protected int $expirationTime = 0;
13
    protected string $redirectUri = '';
14
    protected string $refreshToken = '';
15
    protected string $scope = '';
16
    protected ?Request $request = null;
17

18
    /**
19
     * Constructor
20
     * Set up client credentials.
21
     *
22
     * @param string $clientId The client ID.
23
     * @param string $clientSecret Optional. The client secret.
24
     * @param string $redirectUri Optional. The redirect URI.
25
     * @param Request $request Optional. The Request object to use.
26
     */
27
    public function __construct(
28
        string $clientId,
29
        string $clientSecret = '',
30
        string $redirectUri = '',
31
        ?Request $request = null
32
    ) {
33
        $this->setClientId($clientId);
38✔
34
        $this->setClientSecret($clientSecret);
38✔
35
        $this->setRedirectUri($redirectUri);
38✔
36

37
        $this->request = $request ?? new Request();
38✔
38
    }
39

40
    /**
41
     * Generate a code challenge from a code verifier for use with the PKCE flow.
42
     *
43
     * @param string $codeVerifier The code verifier to create a challenge from.
44
     * @param string $hashAlgo Optional. The hash algorithm to use. Defaults to "sha256".
45
     *
46
     * @return string The code challenge.
47
     */
48
    public function generateCodeChallenge(string $codeVerifier, string $hashAlgo = 'sha256'): string
49
    {
50
        $challenge = hash($hashAlgo, $codeVerifier, true);
2✔
51
        $challenge = base64_encode($challenge);
2✔
52
        $challenge = strtr($challenge, '+/', '-_');
2✔
53
        $challenge = rtrim($challenge, '=');
2✔
54

55
        return $challenge;
2✔
56
    }
57

58
    /**
59
     * Generate a code verifier for use with the PKCE flow.
60
     *
61
     * @param int $length Optional. Code verifier length. Must be between 43 and 128 characters long, default is 128.
62
     *
63
     * @return string A code verifier string.
64
     */
65
    public function generateCodeVerifier(int $length = 128): string
66
    {
67
        return $this->generateState($length);
2✔
68
    }
69

70
    /**
71
     * Generate a random state value.
72
     *
73
     * @param int $length Optional. Length of the state. Default is 16 characters.
74
     *
75
     * @return string A random state value.
76
     */
77
    public function generateState(int $length = 16): string
78
    {
79
        // Length will be doubled when converting to hex
80
        return bin2hex(
4✔
81
            random_bytes($length / 2)
4✔
82
        );
4✔
83
    }
84

85
    /**
86
     * Get the authorization URL.
87
     *
88
     * @param array|object $options Optional. Options for the authorization URL.
89
     * - string code_challenge Optional. A PKCE code challenge.
90
     * - array scope Optional. Scope(s) to request from the user.
91
     * - boolean show_dialog Optional. Whether or not to force the user to always approve the app. Default is false.
92
     * - string state Optional. A CSRF token.
93
     *
94
     * @return string The authorization URL.
95
     */
96
    public function getAuthorizeUrl(array|object $options = []): string
97
    {
98
        $options = (array) $options;
4✔
99

100
        $parameters = [
4✔
101
            'client_id' => $this->getClientId(),
4✔
102
            'redirect_uri' => $this->getRedirectUri(),
4✔
103
            'response_type' => 'code',
4✔
104
            'scope' => isset($options['scope']) ? implode(' ', $options['scope']) : null,
4✔
105
            'show_dialog' => !empty($options['show_dialog']) ? 'true' : null,
4✔
106
            'state' => $options['state'] ?? null,
4✔
107
        ];
4✔
108

109
        // Set some extra parameters for PKCE flows
110
        if (isset($options['code_challenge'])) {
4✔
111
            $parameters['code_challenge'] = $options['code_challenge'];
2✔
112
            $parameters['code_challenge_method'] = $options['code_challenge_method'] ?? 'S256';
2✔
113
        }
114

115
        return Request::ACCOUNT_URL . '/authorize?' . http_build_query($parameters, '', '&');
4✔
116
    }
117

118
    /**
119
     * Get the access token.
120
     *
121
     * @return string The access token.
122
     */
123
    public function getAccessToken(): string
124
    {
125
        return $this->accessToken;
14✔
126
    }
127

128
    /**
129
     * Get the client ID.
130
     *
131
     * @return string The client ID.
132
     */
133
    public function getClientId(): string
134
    {
135
        return $this->clientId;
22✔
136
    }
137

138
    /**
139
     * Get the client secret.
140
     *
141
     * @return string The client secret.
142
     */
143
    public function getClientSecret(): string
144
    {
145
        return $this->clientSecret;
18✔
146
    }
147

148
    /**
149
     * Get the access token expiration time.
150
     *
151
     * @return int A Unix timestamp indicating the token expiration time.
152
     */
153
    public function getTokenExpiration(): int
154
    {
155
        return $this->expirationTime;
12✔
156
    }
157

158
    /**
159
     * Get the client's redirect URI.
160
     *
161
     * @return string The redirect URI.
162
     */
163
    public function getRedirectUri(): string
164
    {
165
        return $this->redirectUri;
12✔
166
    }
167

168
    /**
169
     * Get the refresh token.
170
     *
171
     * @return string The refresh token.
172
     */
173
    public function getRefreshToken(): string
174
    {
175
        return $this->refreshToken;
16✔
176
    }
177

178
    /**
179
     * Get the scope for the current access token
180
     *
181
     * @return array The scope for the current access token
182
     */
183
    public function getScope(): array
184
    {
185
        return explode(' ', $this->scope);
10✔
186
    }
187

188
    /**
189
     * Refresh an access token.
190
     *
191
     * @param string $refreshToken Optional. The refresh token to use.
192
     *
193
     * @return bool Whether the access token was successfully refreshed.
194
     */
195
    public function refreshAccessToken(?string $refreshToken = null): bool
196
    {
197
        $parameters = [
10✔
198
            'grant_type' => 'refresh_token',
10✔
199
            'refresh_token' => $refreshToken ?? $this->refreshToken,
10✔
200
        ];
10✔
201

202
        $headers = [];
10✔
203
        if ($this->getClientSecret()) {
10✔
204
            $payload = base64_encode($this->getClientId() . ':' . $this->getClientSecret());
8✔
205

206
            $headers = [
8✔
207
                'Authorization' => 'Basic ' . $payload,
8✔
208
            ];
8✔
209
        }
210

211
        ['body' => $response] = $this->request->account('POST', '/api/token', $parameters, $headers);
10✔
212

213
        if (isset($response->access_token)) {
10✔
214
            $this->accessToken = $response->access_token;
10✔
215
            $this->expirationTime = time() + $response->expires_in;
10✔
216
            $this->scope = $response->scope ?? $this->scope;
10✔
217

218
            if (isset($response->refresh_token)) {
10✔
219
                $this->refreshToken = $response->refresh_token;
6✔
220
            } elseif (empty($this->refreshToken)) {
4✔
221
                $this->refreshToken = $refreshToken;
2✔
222
            }
223

224
            return true;
10✔
225
        }
226

227
        return false;
×
228
    }
229

230
    /**
231
     * Request an access token given an authorization code.
232
     *
233
     * @param string $authorizationCode The authorization code from Spotify.
234
     * @param string $codeVerifier Optional. A previously generated code verifier. Will assume a PKCE flow if passed.
235
     *
236
     * @return bool True when the access token was successfully granted, false otherwise.
237
     */
238
    public function requestAccessToken(string $authorizationCode, string $codeVerifier = ''): bool
239
    {
240
        $parameters = [
4✔
241
            'client_id' => $this->getClientId(),
4✔
242
            'code' => $authorizationCode,
4✔
243
            'grant_type' => 'authorization_code',
4✔
244
            'redirect_uri' => $this->getRedirectUri(),
4✔
245
        ];
4✔
246

247
        // Send a code verifier when PKCE, client secret otherwise
248
        if ($codeVerifier) {
4✔
249
            $parameters['code_verifier'] = $codeVerifier;
2✔
250
        } else {
251
            $parameters['client_secret'] = $this->getClientSecret();
2✔
252
        }
253

254
        ['body' => $response] = $this->request->account('POST', '/api/token', $parameters, []);
4✔
255

256
        if (isset($response->refresh_token) && isset($response->access_token)) {
4✔
257
            $this->refreshToken = $response->refresh_token;
4✔
258
            $this->accessToken = $response->access_token;
4✔
259
            $this->expirationTime = time() + $response->expires_in;
4✔
260
            $this->scope = $response->scope ?? $this->scope;
4✔
261

262
            return true;
4✔
263
        }
264

265
        return false;
×
266
    }
267

268
    /**
269
     * Request an access token using the Client Credentials Flow.
270
     *
271
     * @return bool True when an access token was successfully granted, false otherwise.
272
     */
273
    public function requestCredentialsToken(): bool
274
    {
275
        $payload = base64_encode($this->getClientId() . ':' . $this->getClientSecret());
2✔
276

277
        $parameters = [
2✔
278
            'grant_type' => 'client_credentials',
2✔
279
        ];
2✔
280

281
        $headers = [
2✔
282
            'Authorization' => 'Basic ' . $payload,
2✔
283
        ];
2✔
284

285
        ['body' => $response] = $this->request->account('POST', '/api/token', $parameters, $headers);
2✔
286

287
        if (isset($response->access_token)) {
2✔
288
            $this->accessToken = $response->access_token;
2✔
289
            $this->expirationTime = time() + $response->expires_in;
2✔
290
            $this->scope = $response->scope ?? $this->scope;
2✔
291

292
            return true;
2✔
293
        }
294

295
        return false;
×
296
    }
297

298
    /**
299
     * Set the access token.
300
     *
301
     * @param string $accessToken The access token
302
     *
303
     * @return self
304
     */
305
    public function setAccessToken(string $accessToken): self
306
    {
307
        $this->accessToken = $accessToken;
2✔
308

309
        return $this;
2✔
310
    }
311

312
    /**
313
     * Set the client ID.
314
     *
315
     * @param string $clientId The client ID.
316
     *
317
     * @return self
318
     */
319
    public function setClientId(string $clientId): self
320
    {
321
        $this->clientId = $clientId;
38✔
322

323
        return $this;
38✔
324
    }
325

326
    /**
327
     * Set the client secret.
328
     *
329
     * @param string $clientSecret The client secret.
330
     *
331
     * @return self
332
     */
333
    public function setClientSecret(string $clientSecret): self
334
    {
335
        $this->clientSecret = $clientSecret;
38✔
336

337
        return $this;
38✔
338
    }
339

340
    /**
341
     * Set the client's redirect URI.
342
     *
343
     * @param string $redirectUri The redirect URI.
344
     *
345
     * @return self
346
     */
347
    public function setRedirectUri(string $redirectUri): self
348
    {
349
        $this->redirectUri = $redirectUri;
38✔
350

351
        return $this;
38✔
352
    }
353

354
    /**
355
     * Set the session's refresh token.
356
     *
357
     * @param string $refreshToken The refresh token.
358
     *
359
     * @return self
360
     */
361
    public function setRefreshToken(string $refreshToken): self
362
    {
363
        $this->refreshToken = $refreshToken;
6✔
364

365
        return $this;
6✔
366
    }
367
}
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

© 2026 Coveralls, Inc