Client   F
last analyzed

Complexity

Total Complexity 61

Size/Duplication

Total Lines 373
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 103
dl 0
loc 373
rs 3.52
c 0
b 0
f 0
wmc 61

51 Methods

Rating   Name   Duplication   Size   Complexity  
A secureProject() 0 3 1
A createContent() 0 6 1
A deleteColumn() 0 3 1
A __construct() 0 10 2
A updateProject() 0 3 1
A listProject() 0 3 1
A authMe() 0 3 1
A setProjectPassword() 0 3 1
A listRow() 0 3 1
A createBlock() 0 3 1
A getToken() 0 3 1
A authenticate() 0 12 2
A cloneRow() 0 3 1
A getApiUrl() 0 3 1
A moveBlockBackward() 0 3 1
A request() 0 16 3
A setApiUrl() 0 5 1
A listColumn() 0 3 1
A listProjects() 0 12 2
A revertProjectAcceptance() 0 3 1
A deleteProject() 0 3 1
A listRows() 0 3 1
A updateRow() 0 3 1
A viewProjectAndNotify() 0 3 1
A createProjectFromTemplate() 0 3 1
A deleteContent() 0 3 1
A listBlocks() 0 3 1
A listTemplates() 0 5 1
A createProject() 0 3 1
A updateColumn() 0 3 1
A publishProject() 0 3 1
A createRow() 0 3 1
A setToken() 0 5 1
A listContents() 0 3 1
A confirmProjectAcceptance() 0 3 1
A createColumn() 0 3 1
A acceptProject() 0 3 1
A moveBlockForward() 0 3 1
A getRequestHeaders() 0 13 2
A listContent() 0 3 1
A updateContent() 0 6 1
A listColumns() 0 3 1
A isSuccessfulResponse() 0 7 2
A deleteBlock() 0 3 1
A cloneBlock() 0 3 1
A generateProjectCover() 0 3 1
A handleRequestError() 0 15 4
A updateBlock() 0 3 1
A listBlock() 0 3 1
A deleteRow() 0 3 1
A cloneProject() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Client often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Client, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace ProposalPage\Sdk;
6
7
use Exception;
8
use GuzzleHttp\Client as HttpClient;
9
use ProposalPage\Sdk\Exception\FailedActionException;
10
use ProposalPage\Sdk\Exception\NotFoundException;
11
use ProposalPage\Sdk\Exception\ValidationException;
12
use Psr\Http\Message\ResponseInterface;
13
14
final class Client
15
{
16
    private $apiUrl = 'https://api.proposalpage.com';
17
18
    private $token = null;
19
20
    private $httpClient;
21
22
    public function __construct(string $apiUrl = '')
23
    {
24
        if ($apiUrl) {
25
            $this->apiUrl = $apiUrl;
26
        }
27
28
        $this->httpClient = new HttpClient([
29
            'base_uri' => $this->getApiUrl(),
30
            'http_errors' => false,
31
            'timeout' => 30.0,
32
        ]);
33
    }
34
35
    // Auth
36
    public function authenticate(string $username, string $password, bool $setToken = true)
37
    {
38
        $response = $this->request('POST', '/accounts/auth/token', [
39
            'username' => $username,
40
            'password' => $password,
41
        ]);
42
43
        if ($setToken) {
44
            $this->token = $response->json['token'];
45
        }
46
47
        return $response;
48
    }
49
50
    public function authMe()
51
    {
52
        return $this->request('GET', '/accounts/auth/me');
53
    }
54
55
    // Templates
56
    public function listTemplates($page = 1, $itemsPerPage = 6)
57
    {
58
        return $this->request('GET', '/projects/templates', [], [
59
            'page' => $page,
60
            'itemsPerPage' => $itemsPerPage,
61
        ]);
62
    }
63
64
    // Projects
65
    public function createProject(array $params)
66
    {
67
        return $this->request('POST', '/projects', $params);
68
    }
69
70
    public function createProjectFromTemplate(string $templateId)
71
    {
72
        return $this->request('POST', "/projects/${templateId}/copy");
73
    }
74
75
    public function listProjects($page = 1, $itemsPerPage = 6, $title = null)
76
    {
77
        $query = [
78
            'page' => $page,
79
            'itemsPerPage' => $itemsPerPage,
80
        ];
81
82
        if ($title) {
83
            $query['title'] = $title;
84
        }
85
86
        return $this->request('GET', '/projects', [], $query);
87
    }
88
89
    public function listProject($projectId)
90
    {
91
        return $this->request('GET', "/projects/{$projectId}");
92
    }
93
94
    public function updateProject($projectId, array $params)
95
    {
96
        return $this->request('PUT', "/projects/{$projectId}", $params);
97
    }
98
99
    public function deleteProject($projectId)
100
    {
101
        return $this->request('DELETE', "/projects/{$projectId}");
102
    }
103
104
    public function cloneProject($projectId)
105
    {
106
        return $this->request('POST', "/projects/{$projectId}/clone");
107
    }
108
109
    public function setProjectPassword($projectId, string $password)
110
    {
111
        return $this->request('POST', "/projects/{$projectId}/password", ['password' => $password]);
112
    }
113
114
    public function publishProject($projectId)
115
    {
116
        return $this->request('POST', "/projects/{$projectId}/publish");
117
    }
118
119
    public function secureProject($projectId)
120
    {
121
        return $this->request('POST', "/projects/{$projectId}/secure");
122
    }
123
124
    public function generateProjectCover($projectId)
125
    {
126
        return $this->request('GET', "/projects/{$projectId}/screenshot");
127
    }
128
129
    public function viewProjectAndNotify($projectId)
130
    {
131
        return $this->request('PUT', "/projects/{$projectId}/view-and-notify");
132
    }
133
134
    public function acceptProject($projectId)
135
    {
136
        return $this->request('POST', "/projects/{$projectId}/accept");
137
    }
138
139
    public function confirmProjectAcceptance($acceptToken)
140
    {
141
        return $this->request('POST', "/projects/accept/{$acceptToken}");
142
    }
143
144
    public function revertProjectAcceptance($acceptReversionToken)
145
    {
146
        return $this->request('POST', "/projects/revert-accept/{$acceptReversionToken}");
147
    }
148
149
    // Blocks
150
    public function createBlock($projectId, array $params)
151
    {
152
        return $this->request('POST', "/projects/{$projectId}/blocks", $params);
153
    }
154
155
    public function listBlocks($projectId)
156
    {
157
        return $this->request('GET', "/projects/{$projectId}/blocks");
158
    }
159
160
    public function listBlock($projectId, $blockId)
161
    {
162
        return $this->request('GET', "/projects/{$projectId}/blocks/{$blockId}");
163
    }
164
165
    public function updateBlock($projectId, $blockId, array $params)
166
    {
167
        return $this->request('PUT', "/projects/{$projectId}/blocks/{$blockId}", $params);
168
    }
169
170
    public function moveBlockForward($projectId, $blockId)
171
    {
172
        return $this->request('POST', "/projects/{$projectId}/blocks/{$blockId}/forward");
173
    }
174
175
    public function moveBlockBackward($projectId, $blockId)
176
    {
177
        return $this->request('POST', "/projects/{$projectId}/blocks/{$blockId}/backward");
178
    }
179
180
    public function cloneBlock($projectId, $blockId, string $position = '')
181
    {
182
        return $this->request('POST', "/projects/{$projectId}/blocks/{$blockId}/clone/{$position}");
183
    }
184
185
    public function deleteBlock($projectId, $blockId)
186
    {
187
        return $this->request('DELETE', "/projects/{$projectId}/blocks/{$blockId}");
188
    }
189
190
    // Rows
191
    public function createRow($projectId, $blockId, array $params)
192
    {
193
        return $this->request('POST', "/projects/{$projectId}/blocks/{$blockId}/rows", $params);
194
    }
195
196
    public function listRows($projectId, $blockId)
197
    {
198
        return $this->request('GET', "/projects/{$projectId}/blocks/{$blockId}/rows");
199
    }
200
201
    public function listRow($projectId, $blockId, $rowId)
202
    {
203
        return $this->request('GET', "/projects/{$projectId}/blocks/{$blockId}/rows/{$rowId}");
204
    }
205
206
    public function updateRow($projectId, $blockId, $rowId, $params)
207
    {
208
        return $this->request('PUT', "/projects/{$projectId}/blocks/{$blockId}/rows/{$rowId}", $params);
209
    }
210
211
    public function cloneRow($projectId, $blockId, $rowId, string $position = '')
212
    {
213
        return $this->request('POST', "/projects/{$projectId}/blocks/{$blockId}/rows/{$rowId}/clone/${position}");
214
    }
215
216
    public function deleteRow($projectId, $blockId, $rowId)
217
    {
218
        return $this->request('DELETE', "/projects/{$projectId}/blocks/{$blockId}/rows/{$rowId}");
219
    }
220
221
    // Columns
222
    public function createColumn($projectId, $blockId, $rowId, array $params)
223
    {
224
        return $this->request('POST', "/projects/{$projectId}/blocks/{$blockId}/rows/{$rowId}/columns", $params);
225
    }
226
227
    public function listColumns($projectId, $blockId, $rowId)
228
    {
229
        return $this->request('GET', "/projects/{$projectId}/blocks/{$blockId}/rows/{$rowId}/columns");
230
    }
231
232
    public function listColumn($projectId, $blockId, $rowId, $columnId)
233
    {
234
        return $this->request('GET', "/projects/{$projectId}/blocks/{$blockId}/rows/{$rowId}/columns/{$columnId}");
235
    }
236
237
    public function updateColumn($projectId, $blockId, $rowId, $columnId, array $params)
238
    {
239
        return $this->request('PUT', "/projects/{$projectId}/blocks/{$blockId}/rows/{$rowId}/columns/{$columnId}", $params);
240
    }
241
242
    public function deleteColumn($projectId, $blockId, $rowId, $columnId)
243
    {
244
        return $this->request('DELETE', "/projects/{$projectId}/blocks/{$blockId}/rows/{$rowId}/columns/{$columnId}");
245
    }
246
247
    // Contents
248
    public function createContent($projectId, $blockId, $rowId, $columnId, array $params)
249
    {
250
        return $this->request(
251
            'POST',
252
            "/projects/{$projectId}/blocks/{$blockId}/rows/{$rowId}/columns/{$columnId}/contents",
253
            $params
254
        );
255
    }
256
257
    public function listContents($projectId, $blockId, $rowId, $columnId)
258
    {
259
        return $this->request('GET', "/projects/{$projectId}/blocks/{$blockId}/rows/{$rowId}/columns/{$columnId}/contents");
260
    }
261
262
    public function listContent($projectId, $blockId, $rowId, $columnId, $contentId)
263
    {
264
        return $this->request('GET', "/projects/{$projectId}/blocks/{$blockId}/rows/{$rowId}/columns/{$columnId}/contents/{$contentId}");
265
    }
266
267
    public function updateContent($projectId, $blockId, $rowId, $columnId, $contentId, array $params)
268
    {
269
        return $this->request(
270
            'PUT',
271
            "/projects/{$projectId}/blocks/{$blockId}/rows/{$rowId}/columns/{$columnId}/contents/{$contentId}",
272
            $params
273
        );
274
    }
275
276
    public function deleteContent($projectId, $blockId, $rowId, $columnId, $contentId)
277
    {
278
        return $this->request('DELETE', "/projects/{$projectId}/blocks/{$blockId}/rows/{$rowId}/columns/{$columnId}/contents/{$contentId}");
279
    }
280
281
    // Getters / Setters
282
    public function getApiUrl()
283
    {
284
        return $this->apiUrl;
285
    }
286
287
    public function setApiUrl(string $apiUrl)
288
    {
289
        $this->apiUrl = $apiUrl;
290
291
        return $this;
292
    }
293
294
    public function getToken()
295
    {
296
        return $this->token;
297
    }
298
299
    public function setToken(string $token)
300
    {
301
        $this->token = $token;
302
303
        return $this;
304
    }
305
306
    /**
307
     * @param string $method
308
     * @param string $path
309
     * @param array $params
310
     * @param array $queryStringParams
311
     * @return mixed|ResponseInterface
312
     *
313
     * @throws FailedActionException
314
     * @throws NotFoundException
315
     * @throws ValidationException
316
     * @throws \GuzzleHttp\Exception\GuzzleException
317
     */
318
    private function request(string $method, string $path, array $params = [], array $queryStringParams = [])
319
    {
320
        $response = $this->httpClient->request($method, $path, [
321
            'json' => $params,
322
            'query' => $queryStringParams,
323
            'headers' => $this->getRequestHeaders(),
324
        ]);
325
326
        if (! $this->isSuccessfulResponse($response)) {
327
            $this->handleRequestError($response);
328
        }
329
330
        $responseBody = (string) $response->getBody();
331
        $response->json = json_decode($responseBody, true) ?: $responseBody;
332
333
        return $response;
334
    }
335
336
    /**
337
     * @return array
338
     */
339
    private function getRequestHeaders()
340
    {
341
        if (! $this->token) {
342
            return [
343
                'Accept' => 'application/json',
344
                'Content-Type' => 'application/json',
345
            ];
346
        }
347
348
        return [
349
            'Authorization' => "Bearer {$this->token}",
350
            'Accept' => 'application/json',
351
            'Content-Type' => 'application/json',
352
        ];
353
    }
354
355
    private function isSuccessfulResponse($response)
356
    {
357
        if (! $response) {
358
            return false;
359
        }
360
361
        return (int) substr($response->getStatusCode(), 0, 1) === 2;
362
    }
363
364
    /**
365
     * @param ResponseInterface $response
366
     *
367
     * @throws FailedActionException
368
     * @throws NotFoundException
369
     * @throws ValidationException
370
     * @throws Exception
371
     */
372
    private function handleRequestError(ResponseInterface $response)
373
    {
374
        if ($response->getStatusCode() === 422) {
375
            throw new ValidationException(json_decode((string) $response->getBody(), true));
376
        }
377
378
        if ($response->getStatusCode() === 404) {
379
            throw new NotFoundException();
380
        }
381
382
        if ($response->getStatusCode() === 400) {
383
            throw new FailedActionException((string) $response->getBody());
384
        }
385
386
        throw new Exception((string) $response->getBody());
387
    }
388
}
389