Passed
Push — master ( 3497fb...435ff5 )
by Gianluca
01:19
created

Client   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 356
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 58
eloc 100
dl 0
loc 356
rs 4.5599
c 0
b 0
f 0

48 Methods

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