Completed
Push — master ( 0cbd7a...258693 )
by Jan-Petter
04:51
created

Manager::debug()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 12
Ratio 100 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 12
loc 12
rs 9.4285
cc 1
eloc 7
nc 1
nop 1
1
<?php
2
/**
3
 * vipnytt/RobotsTxtParser
4
 *
5
 * @link https://github.com/VIPnytt/RobotsTxtParser
6
 * @license https://github.com/VIPnytt/RobotsTxtParser/blob/master/LICENSE The MIT License (MIT)
7
 */
8
9
namespace vipnytt\RobotsTxtParser\Client\Cache\MySQL;
10
11
use PDO;
12
use vipnytt\RobotsTxtParser\Client\Cache\ManagerInterface;
13
use vipnytt\RobotsTxtParser\Exceptions\ClientException;
14
use vipnytt\RobotsTxtParser\Exceptions\DatabaseException;
15
use vipnytt\RobotsTxtParser\RobotsTxtInterface;
16
use vipnytt\RobotsTxtParser\TxtClient;
17
use vipnytt\RobotsTxtParser\UriClient;
18
19
/**
20
 * Class Manager
21
 *
22
 * @see https://github.com/VIPnytt/RobotsTxtParser/blob/master/docs/methods/Cache.md for documentation
23
 * @package vipnytt\RobotsTxtParser\Handler\Cache\MySQL
24
 */
25
class Manager implements ManagerInterface, RobotsTxtInterface
26
{
27
    /**
28
     * Database handler
29
     * @var PDO
30
     */
31
    private $pdo;
32
33
    /**
34
     * cURL options
35
     * @var array
36
     */
37
    private $curlOptions = [];
38
39
    /**
40
     * Byte limit
41
     * @var int|null
42
     */
43
    private $byteLimit = self::BYTE_LIMIT;
44
45
    /**
46
     * Manager constructor.
47
     *
48
     * @param PDO $pdo
49
     * @param array $curlOptions
50
     * @param int|null $byteLimit
51
     */
52
    public function __construct(PDO $pdo, array $curlOptions, $byteLimit)
53
    {
54
        $this->pdo = $pdo;
55
        $this->curlOptions = $curlOptions;
56
        $this->byteLimit = $byteLimit;
57
    }
58
59
    /**
60
     * Debug - Get raw data
61
     *
62
     * @param string $base
63
     * @return array
64
     */
65 View Code Duplication
    public function debug($base)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
66
    {
67
        $query = $this->pdo->prepare(<<<SQL
68
SELECT *
69
FROM robotstxt__cache1
70
WHERE base = :base;
71
SQL
72
        );
73
        $query->bindParam(':base', $base, PDO::PARAM_STR);
74
        $query->execute();
75
        return $query->fetchAll(PDO::FETCH_ASSOC);
76
    }
77
78
    /**
79
     * Parser client
80
     *
81
     * @param string $base
82
     * @return TxtClient
83
     */
84
    public function client($base)
85
    {
86
        $query = $this->pdo->prepare(<<<SQL
87
SELECT
88
  content,
89
  statusCode,
90
  nextUpdate,
91
  effective,
92
  worker,
93
  UNIX_TIMESTAMP()
94
FROM robotstxt__cache1
95
WHERE base = :base;
96
SQL
97
        );
98
        $query->bindParam(':base', $base, PDO::PARAM_STR);
99
        $query->execute();
100
        if ($query->rowCount() > 0) {
101
            $row = $query->fetch(PDO::FETCH_ASSOC);
102
            $this->clockSyncCheck($row['UNIX_TIMESTAMP()']);
103
            if ($row['nextUpdate'] >= $row['UNIX_TIMESTAMP()']) {
104
                $this->markAsActive($base, $row['worker']);
105
                return new TxtClient($base, $row['statusCode'], $row['content'], self::ENCODING, $row['effective'], $this->byteLimit);
106
            }
107
        }
108
        $query = $this->pdo->prepare(<<<SQL
109
UPDATE robotstxt__cache1
110
SET worker = 0
111
WHERE base = :base;
112
SQL
113
        );
114
        $query->bindParam(':base', $base, PDO::PARAM_STR);
115
        $query->execute();
116
        $request = new UriClient($base, $this->curlOptions, $this->byteLimit);
117
        $this->push($request, null);
118
        return $request;
119
    }
120
121
    /**
122
     * Clock sync check
123
     *
124
     * @param int $time
125
     * @throws DatabaseException
126
     */
127
    private function clockSyncCheck($time)
128
    {
129
        if (abs(time() - $time) >= 10) {
130
            throw new DatabaseException('`PHP server` and `SQL server` timestamps are out of sync. Please fix!');
131
        }
132
    }
133
134
    /**
135
     * Mark robots.txt as active
136
     *
137
     * @param string $base
138
     * @param int|null $workerID
139
     * @return bool
140
     */
141 View Code Duplication
    private function markAsActive($base, $workerID = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
142
    {
143
        if ($workerID == 0) {
144
            $query = $this->pdo->prepare(<<<SQL
145
UPDATE robotstxt__cache1
146
SET worker = NULL
147
WHERE base = :base AND worker = 0;
148
SQL
149
            );
150
            $query->bindParam(':base', $base, PDO::PARAM_STR);
151
            return $query->execute();
152
        }
153
        return true;
154
    }
155
156
    /**
157
     * Update an robots.txt in the database
158
     *
159
     * @param UriClient $client
160
     * @param int|null $worker
161
     * @return bool
162
     */
163
    private function push(UriClient $client, $worker = 0)
164
    {
165
        $base = $client->getBaseUri();
166
        $statusCode = $client->getStatusCode();
167
        $nextUpdate = $client->nextUpdate();
168
        $effective = ($effective = $client->getEffectiveUri()) === $base ? null : $effective;
169
        if (strpos($base, 'http') === 0 &&
170
            (
171
                $statusCode === null ||
172
                (
173
                    $statusCode >= 500 &&
174
                    $statusCode < 600
175
                )
176
            ) &&
177
            $this->displacePush($base, $nextUpdate, $worker)
178
        ) {
179
            return true;
180
        }
181
        $validUntil = $client->validUntil();
182
        $content = $client->render()->compressed("\n");
183
        $query = $this->pdo->prepare(<<<SQL
184
INSERT INTO robotstxt__cache1 (base, content, statusCode, validUntil, nextUpdate, effective)
185
VALUES (:base, :content, :statusCode, :validUntil, :nextUpdate, :effective)
186
ON DUPLICATE KEY UPDATE content = :content, statusCode = :statusCode, validUntil = :validUntil,
187
  nextUpdate = :nextUpdate, effective = :effective, worker = :worker;
188
SQL
189
        );
190
        $query->bindParam(':base', $base, PDO::PARAM_STR);
191
        $query->bindParam(':content', $content, PDO::PARAM_STR);
192
        $query->bindParam(':statusCode', $statusCode, PDO::PARAM_INT | PDO::PARAM_NULL);
193
        $query->bindParam(':validUntil', $validUntil, PDO::PARAM_INT);
194
        $query->bindParam(':nextUpdate', $nextUpdate, PDO::PARAM_INT);
195
        $query->bindParam(':effective', $effective, PDO::PARAM_STR | PDO::PARAM_NULL);
196
        $query->bindParam(':worker', $worker, PDO::PARAM_INT | PDO::PARAM_NULL);
197
        return $query->execute();
198
    }
199
200
    /**
201
     * Displace push timestamp
202
     *
203
     * @param string $base
204
     * @param int $nextUpdate
205
     * @param int|null $worker
206
     * @return bool
207
     */
208
    private function displacePush($base, $nextUpdate, $worker = null)
209
    {
210
        $query = $this->pdo->prepare(<<<SQL
211
SELECT
212
  validUntil,
213
  UNIX_TIMESTAMP()
214
FROM robotstxt__cache1
215
WHERE base = :base;
216
SQL
217
        );
218
        $query->bindParam(':base', $base, PDO::PARAM_STR);
219
        $query->execute();
220
        if ($query->rowCount() > 0) {
221
            $row = $query->fetch(PDO::FETCH_ASSOC);
222
            $this->clockSyncCheck($row['UNIX_TIMESTAMP()']);
223
            if ($row['validUntil'] > $row['UNIX_TIMESTAMP()']) {
224
                $nextUpdate = min($row['validUntil'], $nextUpdate);
225
                $query = $this->pdo->prepare(<<<SQL
226
UPDATE robotstxt__cache1
227
SET nextUpdate = :nextUpdate, worker = :worker
228
WHERE base = :base;
229
SQL
230
                );
231
                $query->bindParam(':base', $base, PDO::PARAM_STR);
232
                $query->bindParam(':nextUpdate', $nextUpdate, PDO::PARAM_INT);
233
                $query->bindParam(':worker', $worker, PDO::PARAM_INT | PDO::PARAM_NULL);
234
                return $query->execute();
235
            }
236
            $this->invalidate($base);
237
        }
238
        return false;
239
    }
240
241
    /**
242
     * Invalidate cache
243
     *
244
     * @param $base
245
     * @return bool
246
     */
247
    public function invalidate($base)
248
    {
249
        $query = $this->pdo->prepare(<<<SQL
250
DELETE FROM robotstxt__cache1
251
WHERE base = :base;
252
SQL
253
        );
254
        $query->bindParam(':base', $base, PDO::PARAM_STR);
255
        return $query->execute();
256
    }
257
258
    /**
259
     * Process the update queue
260
     *
261
     * @param float|int|null $timeLimit
262
     * @param int|null $workerID
263
     * @return string[]
264
     * @throws ClientException
265
     */
266
    public function cron($timeLimit, $workerID)
267
    {
268
        $start = microtime(true);
269
        $worker = $this->setWorkerID($workerID);
270
        $log = [];
271
        $lastCount = -1;
272
        while (count($log) > $lastCount &&
273
            (
274
                empty($timeLimit) ||
275
                $timeLimit > (microtime(true) - $start)
276
            )
277
        ) {
278
            $lastCount = count($log);
279
            $query = $this->pdo->prepare(<<<SQL
280
UPDATE robotstxt__cache1
281
SET worker = :workerID
282
WHERE worker IS NULL AND nextUpdate <= UNIX_TIMESTAMP()
283
ORDER BY nextUpdate ASC
284
LIMIT 1;
285
SQL
286
            );
287
            $query->bindParam(':workerID', $worker, PDO::PARAM_INT);
288
            $query->execute();
289
            $query = $this->pdo->prepare(<<<SQL
290
SELECT base
291
FROM robotstxt__cache1
292
WHERE worker = :workerID
293
LIMIT 10;
294
SQL
295
            );
296
            $query->bindParam(':workerID', $worker, PDO::PARAM_INT);
297
            $query->execute();
298
            if ($query->rowCount() > 0) {
299
                while ($row = $query->fetch(PDO::FETCH_ASSOC)) {
300
                    if (!$this->push(new UriClient($row['base'], $this->curlOptions, $this->byteLimit))) {
301
                        throw new ClientException('Unable to update `' . $row['base'] . '`');
302
                    }
303
                    $log[(string)microtime(true)] = $row['base'];
304
                }
305
            }
306
        }
307
        return $log;
308
    }
309
310
    /**
311
     * Set WorkerID
312
     *
313
     * @param int|null $workerID
314
     * @return int
315
     * @throws DatabaseException
316
     */
317
    private function setWorkerID($workerID = null)
318
    {
319
        if (is_int($workerID) &&
320
            $workerID <= 255 &&
321
            $workerID >= 1
322
        ) {
323
            return $workerID;
324
        } elseif ($workerID !== null) {
325
            throw new DatabaseException('WorkerID out of range (1-255)');
326
        }
327
        return rand(1, 255);
328
    }
329
330
    /**
331
     * Clean the cache table
332
     *
333
     * @param int $delay - in seconds
334
     * @return bool
335
     */
336 View Code Duplication
    public function clean($delay)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
337
    {
338
        $delay = self::CACHE_TIME + $delay;
339
        $query = $this->pdo->prepare(<<<SQL
340
DELETE FROM robotstxt__cache1
341
WHERE (worker = 0 OR worker IS NULL) AND nextUpdate < (UNIX_TIMESTAMP() - :delay);
342
SQL
343
        );
344
        $query->bindParam(':delay', $delay, PDO::PARAM_INT);
345
        return $query->execute();
346
    }
347
}
348