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

jwilsson / spotify-web-api-php / 29599087836

17 Jul 2026 05:11PM UTC coverage: 99.308% (+0.004%) from 99.304%
29599087836

push

github

jwilsson
Fix lint error

1 of 1 new or added line in 1 file covered. (100.0%)

3 existing lines in 1 file now uncovered.

718 of 723 relevant lines covered (99.31%)

19.28 hits per line

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

96.88
/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);
60✔
34
        $this->setClientSecret($clientSecret);
60✔
35
        $this->setRedirectUri($redirectUri);
60✔
36

37
        $this->request = $request ?? new Request();
60✔
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);
3✔
51
        $challenge = base64_encode($challenge);
3✔
52
        $challenge = strtr($challenge, '+/', '-_');
3✔
53
        $challenge = rtrim($challenge, '=');
3✔
54

55
        return $challenge;
3✔
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);
6✔
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
        $state = bin2hex(
9✔
81
            random_bytes((int) ceil($length / 2)),
9✔
82
        );
9✔
83

84
        return substr($state, 0, $length);
9✔
85
    }
86

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

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

111
        // Set some extra parameters for PKCE flows
112
        if (isset($options['code_challenge'])) {
6✔
113
            $parameters['code_challenge'] = $options['code_challenge'];
3✔
114
            $parameters['code_challenge_method'] = $options['code_challenge_method'] ?? 'S256';
3✔
115
        }
116

117
        return Request::ACCOUNT_URL . '/authorize?' . http_build_query($parameters, '', '&');
6✔
118
    }
119

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

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

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

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

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

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

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

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

204
        $headers = [];
15✔
205
        if ($this->getClientSecret()) {
15✔
206
            $payload = base64_encode($this->getClientId() . ':' . $this->getClientSecret());
12✔
207

208
            $headers = [
12✔
209
                'Authorization' => 'Basic ' . $payload,
12✔
210
            ];
12✔
211
        }
212

213
        ['body' => $response] = $this->request->account('POST', '/api/token', $parameters, $headers);
15✔
214

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

220
            if (isset($response->refresh_token)) {
15✔
221
                $this->refreshToken = $response->refresh_token;
9✔
222
            } elseif (empty($this->refreshToken)) {
6✔
223
                $this->refreshToken = $refreshToken;
3✔
224
            }
225

226
            return true;
15✔
227
        }
228

UNCOV
229
        return false;
×
230
    }
231

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

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

256
        ['body' => $response] = $this->request->account('POST', '/api/token', $parameters, []);
6✔
257

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

264
            return true;
6✔
265
        }
266

UNCOV
267
        return false;
×
268
    }
269

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

279
        $parameters = [
3✔
280
            'grant_type' => 'client_credentials',
3✔
281
        ];
3✔
282

283
        $headers = [
3✔
284
            'Authorization' => 'Basic ' . $payload,
3✔
285
        ];
3✔
286

287
        ['body' => $response] = $this->request->account('POST', '/api/token', $parameters, $headers);
3✔
288

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

294
            return true;
3✔
295
        }
296

UNCOV
297
        return false;
×
298
    }
299

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

311
        return $this;
3✔
312
    }
313

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

325
        return $this;
60✔
326
    }
327

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

339
        return $this;
60✔
340
    }
341

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

353
        return $this;
60✔
354
    }
355

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

367
        return $this;
9✔
368
    }
369
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc