Completed
Push — master ( 0db88d...bd745d )
by William Johnson S.
02:01
created

HttpApi   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 398
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 3.25%

Importance

Changes 5
Bugs 0 Features 1
Metric Value
wmc 40
c 5
b 0
f 1
lcom 1
cbo 3
dl 0
loc 398
ccs 4
cts 123
cp 0.0325
rs 8.2608

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A setCompanyId() 0 7 1
A setUsername() 0 7 1
A setPassword() 0 7 1
A doLogin() 0 18 3
A getPunchs() 0 13 4
A getEmployeeRole() 0 4 1
A getDepartment() 0 4 1
A executeLogin() 0 8 1
A checkAccessEnabled() 0 6 1
A checkLoginStatus() 0 10 3
A getResponseLoginStatus() 0 12 3
A setLoggedIn() 0 11 2
A companyUrl() 0 7 1
A loginUrl() 0 7 1
A punchsUrl() 0 8 1
A getPunchsPage() 0 4 1
A getPunchsFromPage() 0 12 2
B parsePunchsPage() 0 28 4
A getPunchsTableHtml() 0 7 1
A getPageTables() 0 13 2
A parsePunchs() 0 9 2
A createPunchesDate() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like HttpApi 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 HttpApi, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Katapoka\Ahgora;
4
5
use DateTime;
6
use DOMDocument;
7
use InvalidArgumentException;
8
use Katapoka\Ahgora\Contracts\IHttpClient;
9
use Katapoka\Ahgora\Contracts\IHttpResponse;
10
11
/**
12
 * Class responsible for getting the data from the Ahgora system.
13
 */
14
class HttpApi extends AbstractApi
15
{
16
    const AHGORA_BASE_URL = 'https://www.ahgora.com.br';
17
    const AHGORA_COMPANY_URL = '%s/externo/index/%s';
18
    const AHGORA_LOGIN_URL = '%s/externo/login';
19
    const AHGORA_PUNCHS_URL = '%s/externo/batidas/%d-%d';
20
21
    /** @var \Katapoka\Ahgora\Contracts\IHttpClient */
22
    private $httpClient;
23
    /** @var string */
24
    private $password;
25
    /** @var string */
26
    private $companyId;
27
    /** @var string */
28
    private $username;
29
    /** @var bool */
30
    private $loggedIn = false;
31
32
    /**
33
     * Api constructor.
34
     *
35
     * @param IHttpClient $httpClient
36
     */
37 1
    public function __construct(IHttpClient $httpClient)
38
    {
39 1
        $this->httpClient = $httpClient;
40 1
        $this->debug('Api instance created');
41 1
    }
42
43
    /**
44
     * Set the company id of the ahgora system.
45
     *
46
     * @param string $companyId
47
     *
48
     * @return $this
49
     */
50
    public function setCompanyId($companyId)
51
    {
52
        $this->companyId = $companyId;
53
        $this->debug('Company ID set', ['company_id' => $companyId]);
54
55
        return $this;
56
    }
57
58
    /**
59
     * Set the username of the employee, from the company set at the setCompanyId.
60
     *
61
     * @param string $username
62
     *
63
     * @return $this
64
     */
65
    public function setUsername($username)
66
    {
67
        $this->username = $username;
68
        $this->debug('Username set', ['username' => $username]);
69
70
        return $this;
71
    }
72
73
    /**
74
     * Set the password of the employee, from the company set at the setCompanyId.
75
     *
76
     * @param string $password
77
     *
78
     * @return $this
79
     */
80
    public function setPassword($password)
81
    {
82
        $this->password = $password;
83
        $this->debug('Password set', ['password' => $password]);
84
85
        return $this;
86
    }
87
88
    /**
89
     * Try to execute the login on the page.
90
     * To execute some actions the user needs to be loggedin.
91
     * After a successful login, the status loggedin is saved as true.
92
     *
93
     * @return bool Returns true if the login was successful and false otherwise
94
     */
95
    public function doLogin()
96
    {
97
        $hasLoggedIn = false;
98
        $this->debug('Started login proccess');
99
100
        $accessEnabled = $this->checkAccessEnabled();
101
102
        if ($accessEnabled) {
103
            $response = $this->executeLogin();
104
            $hasLoggedIn = $this->checkLoginStatus($response);
105
        }
106
107
        $this->debug($accessEnabled ? "Company has external access enabled" : "Company hasn't external access enabled");
108
109
        $this->setLoggedIn($hasLoggedIn);
110
111
        return $hasLoggedIn;
112
    }
113
114
    /**
115
     * Get the punchs at the given parameters.
116
     *
117
     * @param int|null $month The month you want to get the punchs - Must be between 01 and 12 (both included)
118
     * @param int|null $year  The year you want to get the punchs
119
     *
120
     * @return array
121
     */
122
    public function getPunchs($month = null, $year = null)
123
    {
124
        $month = $month !== null ? $month : (int) date('m');
125
        $year = $year !== null ? $year : (int) date('Y');
126
127
        if (!$this->isValidPeriod($month, $year)) {
128
            throw new InvalidArgumentException('Invalid period of time');
129
        }
130
131
        $punchsPageResponse = $this->getPunchsPage($month, $year);
132
133
        return $this->getPunchsFromPage($punchsPageResponse);
134
    }
135
136
    /**
137
     * Gets the employer name.
138
     *
139
     * @return string
140
     */
141
    public function getEmployeeRole()
142
    {
143
        return "NOT IMPLEMENTED YET";
144
    }
145
146
    /**
147
     * Get the employer department.
148
     *
149
     * @return string
150
     */
151
    public function getDepartment()
152
    {
153
        return "NOT IMPLEMENTED YET";
154
    }
155
156
    /**
157
     * Execute the login on the server and returns the server response.
158
     *
159
     * @return IHttpResponse
160
     */
161
    private function executeLogin()
162
    {
163
        return $this->httpClient->post($this->loginUrl(), [
164
            'empresa'   => $this->companyId,
165
            'matricula' => $this->username,
166
            'senha'     => $this->password,
167
        ]);
168
    }
169
170
    /**
171
     * Check if the company has external access on the Ahgora system.
172
     *
173
     * @return bool
174
     */
175
    private function checkAccessEnabled()
176
    {
177
        $response = $this->httpClient->get($this->companyUrl());
178
179
        return stripos($response->getBody(), 'Sua Empresa não liberou o acesso a essa ferramenta') === false;
180
    }
181
182
    /**
183
     * Check the return of the login action.
184
     * How it works: If statusCode 200 and no body, login ok, otherwise, login failed.
185
     * Should return a json with property "r" with "error" and "text" with the message
186
     *
187
     * @param IHttpResponse $response
188
     *
189
     * @return bool
190
     */
191
    private function checkLoginStatus(IHttpResponse $response)
192
    {
193
        try {
194
            return $response->getHttpStatus() === IHttpClient::HTTP_STATUS_OK && $this->getResponseLoginStatus($response);
195
        } catch (InvalidArgumentException $iaex) {
196
            $this->error('checkLoginStatus', ['expcetion' => $iaex]);
197
198
            return false;
199
        }
200
    }
201
202
    /**
203
     * Check if the response can be decoded as json, has the property r and r is 'success'.
204
     *
205
     * @param IHttpResponse $response
206
     *
207
     * @return bool
208
     */
209
    private function getResponseLoginStatus(IHttpResponse $response)
210
    {
211
        try {
212
            $json = $response->json();
213
214
            return array_key_exists('r', $json) && $json->r === 'success';
215
        } catch (InvalidArgumentException $iaex) {
216
            $this->debug('getResponseLoginStatus', ['exception', $iaex]);
217
218
            return false;
219
        }
220
    }
221
222
    /**
223
     * Safely set if the user is loggedin or not.
224
     * Did a separate method do eventually trigger events, if necessary.
225
     *
226
     * @param bool $loggedIn
227
     *
228
     * @return $this
229
     */
230
    private function setLoggedIn($loggedIn = true)
231
    {
232
        if (!is_bool($loggedIn)) {
233
            throw new InvalidArgumentException('LoggedIn parameter must be boolean');
234
        }
235
236
        $this->debug('setLoggedIn', ['logged_in' => $loggedIn]);
237
        $this->loggedIn = $loggedIn;
238
239
        return $this;
240
    }
241
242
    /**
243
     * Build the company url string.
244
     *
245
     * @return string
246
     */
247
    private function companyUrl()
248
    {
249
        $companyUrl = sprintf(self::AHGORA_COMPANY_URL, self::AHGORA_BASE_URL, $this->companyId);
250
        $this->debug('CompanyURL', ['company_url' => $companyUrl]);
251
252
        return $companyUrl;
253
    }
254
255
    /**
256
     * Build the login url.
257
     *
258
     * @return string
259
     */
260
    private function loginUrl()
261
    {
262
        $loginUrl = sprintf(self::AHGORA_LOGIN_URL, self::AHGORA_BASE_URL);
263
        $this->debug('loginUrl', ['login_url' => $loginUrl]);
264
265
        return $loginUrl;
266
    }
267
268
    /**
269
     * Get the built punchsUrl with the given month and year.
270
     *
271
     * @param int $month
272
     * @param int $year
273
     *
274
     * @return string
275
     */
276
    private function punchsUrl($month, $year)
277
    {
278
        $month = str_pad($month, 2, '0', STR_PAD_LEFT);
279
        $punchsUrl = sprintf(self::AHGORA_PUNCHS_URL, self::AHGORA_BASE_URL, $month, $year);
280
        $this->debug('punchsUrl', ['punchs_url' => $punchsUrl]);
281
282
        return $punchsUrl;
283
    }
284
285
    /**
286
     * Make the request to the punchs page of the requested time period.
287
     *
288
     * @param int $month
289
     * @param int $year
290
     *
291
     * @return IHttpResponse
292
     */
293
    private function getPunchsPage($month, $year)
294
    {
295
        return $this->httpClient->get($this->punchsUrl($month, $year));
296
    }
297
298
    /**
299
     * Get the punches from the given response of the punchs page.
300
     *
301
     * @param IHttpResponse $punchsPageResponse
302
     *
303
     * @return array
304
     */
305
    private function getPunchsFromPage(IHttpResponse $punchsPageResponse)
306
    {
307
        if ($punchsPageResponse->getHttpStatus() !== IHttpClient::HTTP_STATUS_OK) {
308
            throw new InvalidArgumentException('The request returned http status ' . $punchsPageResponse->getHttpStatus());
309
        }
310
311
        $punchs = $this->parsePunchsPage($punchsPageResponse);
312
313
        return [
314
            'punchs' => $punchs,
315
        ];
316
    }
317
318
    private function parsePunchsPage(IHttpResponse $punchsPageResponse)
319
    {
320
        $punchsTableHtml = $this->getPunchsTableHtml($punchsPageResponse);
321
322
        $dom = new DOMDocument();
323
        if (!@$dom->loadHTML($punchsTableHtml)) {
324
            throw new InvalidArgumentException('Failed to parse punchsTable');
325
        }
326
327
        $rows = $dom->getElementsByTagName('tr');
328
329
        $punchCollection = [];
330
331
        /** @var \DOMElement $row */
332
        foreach ($rows as $row) {
333
            $cols = $row->getElementsByTagName('td');
334
            if ($cols->length !== 8) {
335
                continue;
336
            }
337
338
            $date = trim($cols->item(0)->nodeValue);
339
            $punches = $this->parsePunchs($cols->item(2)->nodeValue);
340
341
            $punchCollection = array_merge($punchCollection, $this->createPunchesDate($date, $punches));
342
        }
343
344
        return $punchCollection;
345
    }
346
347
    private function getPunchsTableHtml(IHttpResponse $punchsPageResponse)
348
    {
349
        $tables = $this->getPageTables($punchsPageResponse);
350
351
        //A primeira posição é a table
352
        return $tables['punchs'];
353
    }
354
355
    /**
356
     * Get both tables and return the strings into an array with the properties 'summary' and 'punchs'.
357
     *
358
     * @param IHttpResponse $punchsPageResponse
359
     *
360
     * @return array
361
     */
362
    private function getPageTables(IHttpResponse $punchsPageResponse)
363
    {
364
        $regex = '/<table.*?>.*?<\/table>/si';
365
366
        if (!preg_match_all($regex, $punchsPageResponse->getBody(), $matches)) {
367
            throw new InvalidArgumentException('Pattern not found in the response');
368
        }
369
370
        return [
371
            'summary' => $matches[0][0],
372
            'punchs'  => $matches[0][1],
373
        ];
374
    }
375
376
    /**
377
     * Retrive all the punches for the given string.
378
     *
379
     * @param string $punchsStr
380
     *
381
     * @return array
382
     */
383
    private function parsePunchs($punchsStr)
384
    {
385
        $punches = [];
386
        if (!!preg_match_all('/(\d{2}:\d{2})/is', $punchsStr, $matches)) {
387
            $punches = $matches[0];
388
        }
389
390
        return $punches;
391
    }
392
393
    /**
394
     * Convert the date string and the datepunch array to an array of DateTime's.
395
     *
396
     * @param string $date
397
     * @param array $punches
398
     *
399
     * @return \DateTime[]
400
     */
401
    private function createPunchesDate($date, array $punches = [])
402
    {
403
        $dates = [];
404
        foreach ($punches as $punch) {
405
            $dates[] = DateTime::createFromFormat($this->datetimeFormat, sprintf('%s %s', $date, $punch));
406
        }
407
408
        return $dates;
409
    }
410
411
}
412