Completed
Push — master ( 6eb58a...ff20ae )
by Jan-Petter
03:32
created

Manager::client()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 36
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 36
rs 8.8571
cc 3
eloc 20
nc 3
nop 2
1
<?php
2
namespace vipnytt\RobotsTxtParser\Client\Cache\MySQL;
3
4
use PDO;
5
use vipnytt\RobotsTxtParser\Client\Cache\ManagerInterface;
6
use vipnytt\RobotsTxtParser\Exceptions\ClientException;
7
use vipnytt\RobotsTxtParser\Exceptions\SQLException;
8
use vipnytt\RobotsTxtParser\RobotsTxtInterface;
9
use vipnytt\RobotsTxtParser\TxtClient;
10
use vipnytt\RobotsTxtParser\UriClient;
11
12
/**
13
 * Class Manager
14
 *
15
 * @see https://github.com/VIPnytt/RobotsTxtParser/blob/master/docs/methods/CacheManager.md for documentation
16
 * @package vipnytt\RobotsTxtParser\Handler\Cache\MySQL
17
 */
18
class Manager implements ManagerInterface, RobotsTxtInterface
19
{
20
    /**
21
     * Database handler
22
     * @var PDO
23
     */
24
    private $pdo;
25
26
    /**
27
     * cURL options
28
     * @var array
29
     */
30
    private $curlOptions = [];
31
32
    /**
33
     * Byte limit
34
     * @var int|null
35
     */
36
    private $byteLimit = self::BYTE_LIMIT;
37
38
    /**
39
     * Manager constructor.
40
     *
41
     * @param PDO $pdo
42
     * @param array $curlOptions
43
     * @param int|null $byteLimit
44
     */
45
    public function __construct(PDO $pdo, array $curlOptions, $byteLimit)
46
    {
47
        $this->pdo = $pdo;
48
        $this->curlOptions = $curlOptions;
49
        $this->byteLimit = $byteLimit;
50
    }
51
52
    /**
53
     * Parser client
54
     *
55
     * @param string $base
56
     * @param int $updateBufferTime
57
     * @return TxtClient
58
     */
59
    public function client($base, $updateBufferTime)
60
    {
61
        $query = $this->pdo->prepare(<<<SQL
62
SELECT
63
  content,
64
  statusCode,
65
  nextUpdate,
66
  effective,
67
  worker,
68
  UNIX_TIMESTAMP()
69
FROM robotstxt__cache1
70
WHERE base = :base;
71
SQL
72
        );
73
        $query->bindParam(':base', $base, PDO::PARAM_STR);
74
        $query->execute();
75
        if ($query->rowCount() > 0) {
76
            $row = $query->fetch(PDO::FETCH_ASSOC);
77
            $this->clockSyncCheck($row['UNIX_TIMESTAMP()']);
78
            if ($row['nextUpdate'] >= $row['UNIX_TIMESTAMP()']) {
79
                $this->markAsActive($base, $row['worker']);
80
                return new TxtClient($base, $row['statusCode'], $row['content'], self::ENCODING, $row['effective'], $this->byteLimit);
81
            }
82
        }
83
        $query = $this->pdo->prepare(<<<SQL
84
UPDATE robotstxt__cache1
85
SET worker = 0
86
WHERE base = :base;
87
SQL
88
        );
89
        $query->bindParam(':base', $base, PDO::PARAM_STR);
90
        $query->execute();
91
        $request = new UriClient($base, $this->curlOptions, $this->byteLimit);
92
        $this->push($request, null);
93
        return $request;
94
    }
95
96
    /**
97
     * Clock sync check
98
     *
99
     * @param int $time
100
     * @throws SQLException
101
     */
102
    private function clockSyncCheck($time)
103
    {
104
        if (abs(time() - $time) >= 10) {
105
            throw new SQLException('`PHP server` and `SQL server` timestamps are out of sync. Please fix!');
106
        }
107
    }
108
109
    /**
110
     * Mark robots.txt as active
111
     *
112
     * @param string $base
113
     * @param int|null $workerID
114
     * @return bool
115
     */
116 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...
117
    {
118
        if ($workerID == 0) {
119
            $query = $this->pdo->prepare(<<<SQL
120
UPDATE robotstxt__cache1
121
SET worker = NULL
122
WHERE base = :base AND worker = 0;
123
SQL
124
            );
125
            $query->bindParam(':base', $base, PDO::PARAM_STR);
126
            return $query->execute();
127
        }
128
        return true;
129
    }
130
131
    /**
132
     * Update an robots.txt in the database
133
     *
134
     * @param UriClient $client
135
     * @param int|null $worker
136
     * @return bool
137
     */
138
    private function push(UriClient $client, $worker = 0)
139
    {
140
        $base = $client->getBaseUri();
141
        $statusCode = $client->getStatusCode();
142
        $nextUpdate = $client->nextUpdate();
143
        $effective = ($effective = $client->getEffectiveUri()) === $base ? null : $effective;
144
        if (
145
            stripos($base, 'http') === 0 &&
146
            (
147
                $statusCode === null ||
148
                (
149
                    $statusCode >= 500 &&
150
                    $statusCode < 600
151
                )
152
            ) &&
153
            $this->displacePush($base, $nextUpdate)
154
        ) {
155
            return true;
156
        }
157
        $validUntil = $client->validUntil();
158
        $content = $client->render();
159
        $query = $this->pdo->prepare(<<<SQL
160
INSERT INTO robotstxt__cache1 (base, content, statusCode, validUntil, nextUpdate, effective)
161
VALUES (:base, :content, :statusCode, :validUntil, :nextUpdate, :effective)
162
ON DUPLICATE KEY UPDATE content = :content, statusCode = :statusCode, validUntil = :validUntil,
163
  nextUpdate = :nextUpdate, effective = :effective, worker = :worker;
164
SQL
165
        );
166
        $query->bindParam(':base', $base, PDO::PARAM_STR);
167
        $query->bindParam(':content', $content, PDO::PARAM_STR);
168
        $query->bindParam(':statusCode', $statusCode, PDO::PARAM_INT | PDO::PARAM_NULL);
169
        $query->bindParam(':validUntil', $validUntil, PDO::PARAM_INT);
170
        $query->bindParam(':nextUpdate', $nextUpdate, PDO::PARAM_INT);
171
        $query->bindParam(':effective', $effective, PDO::PARAM_STR | PDO::PARAM_NULL);
172
        $query->bindParam(':worker', $worker, PDO::PARAM_INT | PDO::PARAM_NULL);
173
        return $query->execute();
174
    }
175
176
    /**
177
     * Displace push timestamp
178
     *
179
     * @param string $base
180
     * @param int $nextUpdate
181
     * @return bool
182
     */
183
    private function displacePush($base, $nextUpdate)
184
    {
185
        $query = $this->pdo->prepare(<<<SQL
186
SELECT
187
  validUntil,
188
  UNIX_TIMESTAMP()
189
FROM robotstxt__cache1
190
WHERE base = :base;
191
SQL
192
        );
193
        $query->bindParam(':base', $base, PDO::PARAM_STR);
194
        $query->execute();
195
        if ($query->rowCount() > 0) {
196
            $row = $query->fetch(PDO::FETCH_ASSOC);
197
            $this->clockSyncCheck($row['UNIX_TIMESTAMP()']);
198
            if ($row['validUntil'] > $row['UNIX_TIMESTAMP()']) {
199
                $nextUpdate = min($row['validUntil'], $nextUpdate);
200
                $query = $this->pdo->prepare(<<<SQL
201
UPDATE robotstxt__cache1
202
SET nextUpdate = :nextUpdate, worker = NULL
203
WHERE base = :base;
204
SQL
205
                );
206
                $query->bindParam(':base', $base, PDO::PARAM_STR);
207
                $query->bindParam(':nextUpdate', $nextUpdate, PDO::PARAM_INT);
208
                return $query->execute();
209
            }
210
            $this->invalidate($base);
211
        }
212
        return false;
213
    }
214
215
    /**
216
     * Invalidate cache
217
     *
218
     * @param $base
219
     * @return bool
220
     */
221
    public function invalidate($base)
222
    {
223
        $query = $this->pdo->prepare(<<<SQL
224
DELETE FROM robotstxt__cache1
225
WHERE base = :base;
226
SQL
227
        );
228
        $query->bindParam(':base', $base, PDO::PARAM_STR);
229
        return $query->execute();
230
    }
231
232
    /**
233
     * Process the update queue
234
     *
235
     * @param float|int $targetTime
236
     * @param int|null $workerID
237
     * @return string[]
238
     * @throws ClientException
239
     */
240
    public function cron($targetTime, $workerID)
241
    {
242
        $start = microtime(true);
243
        $worker = $this->setWorkerID($workerID);
244
        $log = [];
245
        $lastCount = -1;
246
        while (
247
            $targetTime > microtime(true) - $start &&
248
            count($log) > $lastCount
249
        ) {
250
            $lastCount = count($log);
251
            $query = $this->pdo->prepare(<<<SQL
252
UPDATE robotstxt__cache1
253
SET worker = :workerID
254
WHERE worker IS NULL AND nextUpdate <= UNIX_TIMESTAMP()
255
ORDER BY nextUpdate ASC
256
LIMIT 1;
257
SQL
258
            );
259
            $query->bindParam(':workerID', $worker, PDO::PARAM_INT);
260
            $query->execute();
261
            $query = $this->pdo->prepare(<<<SQL
262
SELECT base
263
FROM robotstxt__cache1
264
WHERE worker = :workerID
265
LIMIT 10;
266
SQL
267
            );
268
            $query->bindParam(':workerID', $worker, PDO::PARAM_INT);
269
            $query->execute();
270
            if ($query->rowCount() > 0) {
271
                while ($row = $query->fetch(PDO::FETCH_ASSOC)) {
272
                    if (!$this->push(new UriClient($row['base'], $this->curlOptions, $this->byteLimit))) {
273
                        throw new ClientException('Unable to update `' . $row['base'] . '`');
274
                    }
275
                    $log[] = $row['base'];
276
                }
277
            }
278
        }
279
        return $log;
280
    }
281
282
    /**
283
     * Set WorkerID
284
     *
285
     * @param int|null $workerID
286
     * @return int
287
     */
288
    private function setWorkerID($workerID = null)
289
    {
290
        if (
291
            is_int($workerID) &&
292
            $workerID <= 255 &&
293
            $workerID >= 1
294
        ) {
295
            return $workerID;
296
        } elseif ($workerID !== null) {
297
            trigger_error('WorkerID out of range (1-255)', E_USER_WARNING);
298
        }
299
        return rand(1, 255);
300
    }
301
302
    /**
303
     * Clean the cache table
304
     *
305
     * @param int $delay - in seconds
306
     * @return bool
307
     */
308 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...
309
    {
310
        $delay = self::CACHE_TIME + $delay;
311
        $query = $this->pdo->prepare(<<<SQL
312
DELETE FROM robotstxt__cache1
313
WHERE (worker = 0 OR worker IS NULL) AND nextUpdate < (UNIX_TIMESTAMP() - :delay);
314
SQL
315
        );
316
        $query->bindParam(':delay', $delay, PDO::PARAM_INT);
317
        return $query->execute();
318
    }
319
}
320