Completed
Branch 2.0-dev (d250b8)
by Jan-Petter
03:02
created

SQL   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 221
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 22
c 1
b 0
f 0
lcom 1
cbo 4
dl 0
loc 221
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
B cron() 0 28 4
B setWorkerID() 0 13 5
B push() 0 50 6
B client() 0 27 3
A markAsActive() 0 14 2
1
<?php
2
namespace vipnytt\RobotsTxtParser;
3
4
use PDO;
5
use vipnytt\RobotsTxtParser\Client\SQL\SQLInterface;
6
use vipnytt\RobotsTxtParser\Client\SQL\SQLTrait;
7
use vipnytt\RobotsTxtParser\Parser\UrlParser;
8
9
/**
10
 * Class SQL
11
 *
12
 * @package vipnytt\RobotsTxtParser\Client
13
 */
14
class SQL implements RobotsTxtInterface, SQLInterface
15
{
16
    use SQLTrait;
17
    use UrlParser;
18
19
    /**
20
     * Database connection
21
     * @var PDO
22
     */
23
    private $pdo;
24
25
    /**
26
     * PDO driver
27
     * @var string
28
     */
29
    private $driver;
30
31
    /**
32
     * GuzzleHTTP config
33
     * @var array
34
     */
35
    private $guzzleConfig = [];
36
37
    /**
38
     * Byte limit
39
     * @var int
40
     */
41
    private $byteLimit = self::BYTE_LIMIT;
42
43
    /**
44
     * Client nextUpdate margin in seconds
45
     * @var int
46
     */
47
    private $clientUpdateMargin = 300;
48
49
    /**
50
     * Cache constructor.
51
     *
52
     * @param PDO $pdo
53
     * @param array $guzzleConfig
54
     * @param int|null $byteLimit
55
     */
56
    public function __construct(PDO $pdo, array $guzzleConfig = [], $byteLimit = self::BYTE_LIMIT)
57
    {
58
        $this->pdo = $this->pdoInitialize($pdo);
59
        $this->driver = $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
60
        if ($this->driver != 'mysql') {
61
            trigger_error('Unsupported database. Currently only MySQL are officially supported. ' . self::README_SQL_CACHE, E_USER_WARNING);
62
        }
63
        $this->guzzleConfig = $guzzleConfig;
64
        $this->byteLimit = $byteLimit;
65
    }
66
67
    /**
68
     * Process the update queue
69
     *
70
     * @param int|null $workerID
71
     * @return bool
72
     */
73
    public function cron($workerID = null)
74
    {
75
        $worker = $this->setWorkerID($workerID);
76
        $result = true;
77
        while ($result) {
78
            $query = $this->pdo->prepare(<<<SQL
79
UPDATE robotstxt__cache0
80
SET worker = :workerID
81
WHERE worker IS NULL AND nextUpdate <= UNIX_TIMESTAMP()
82
ORDER BY nextUpdate ASC
83
LIMIT 1;
84
SELECT base
85
FROM robotstxt__cache0
86
WHERE worker = :worker;
87
SQL
88
            );
89
            $query->bindParam(':workerID', $worker, PDO::PARAM_INT);
90
            $query->execute();
91
            if ($query->rowCount() > 0) {
92
                while ($row = $query->fetch(PDO::FETCH_ASSOC)) {
93
                    $result = $this->push(new URI($row['base'], $this->guzzleConfig, $this->byteLimit));
94
                }
95
                continue;
96
            }
97
            return true;
98
        }
99
        return false;
100
    }
101
102
    /**
103
     * Set WorkerID
104
     *
105
     * @param int|null $workerID
106
     * @return int
107
     */
108
    private function setWorkerID($workerID = null)
109
    {
110
        if (
111
            is_int($workerID) &&
112
            $workerID <= 255 &&
113
            $workerID >= 1
114
        ) {
115
            return $workerID;
116
        } elseif ($workerID !== null) {
117
            trigger_error('WorkerID out of range (1-255)', E_USER_WARNING);
118
        }
119
        return rand(1, 255);
120
    }
121
122
    /**
123
     * Update an robots.txt in the database
124
     *
125
     * @param URI $request
126
     * @return bool
127
     */
128
    public function push(URI $request)
129
    {
130
        $base = $request->getBaseUri();
131
        $statusCode = $request->getStatusCode();
132
        $nextUpdate = $request->nextUpdate();
133
        if (
134
            $statusCode >= 500 &&
135
            $statusCode < 600 &&
136
            mb_strpos($base, 'http') === 0
137
        ) {
138
            $query = $this->pdo->prepare(<<<SQL
139
SELECT validUntil
140
FROM robotstxt__cache0
141
WHERE base = :base;
142
SQL
143
            );
144
            $query->bindParam(':base', $base, PDO::PARAM_STR);
145
            $query->execute();
146
            if (
147
                $query->rowCount() > 0 &&
148
                ($existingValidUntil = $query->fetch(PDO::FETCH_ASSOC)['validUntil']) > time()
149
            ) {
150
                $nextUpdate = min($existingValidUntil, $nextUpdate);
151
                $query = $this->pdo->prepare(<<<SQL
152
UPDATE robotstxt__cache0
153
SET nextUpdate = :nextUpdate, worker = NULL
154
WHERE base = :base;
155
SQL
156
                );
157
                $query->bindParam(':base', $base, PDO::PARAM_STR);
158
                $query->bindParam(':nextUpdate', $nextUpdate, PDO::PARAM_INT);
159
                return $query->execute();
160
            }
161
        }
162
        $validUntil = $request->validUntil();
163
        $content = $request->getContents();
164
        $query = $this->pdo->prepare(<<<SQL
165
INSERT INTO robotstxt__cache0 (base, content, statusCode, validUntil, nextUpdate)
166
VALUES (:base, :content, :statusCode, :validUntil, :nextUpdate)
167
ON DUPLICATE KEY UPDATE content = :content, statusCode = :statusCode, validUntil = :validUntil,
168
  nextUpdate = :nextUpdate, worker = 0;
169
SQL
170
        );
171
        $query->bindParam(':base', $base, PDO::PARAM_STR);
172
        $query->bindParam(':content', $content, PDO::PARAM_STR);
173
        $query->bindParam(':statusCode', $statusCode, PDO::PARAM_INT);
174
        $query->bindParam(':validUntil', $validUntil, PDO::PARAM_INT);
175
        $query->bindParam(':nextUpdate', $nextUpdate, PDO::PARAM_INT);
176
        return $query->execute();
177
    }
178
179
    /**
180
     * Parser client
181
     *
182
     * @param string $baseUri
183
     * @return Input
184
     */
185
    public function client($baseUri)
186
    {
187
        $base = $this->urlBase($this->urlEncode($baseUri));
188
        $query = $this->pdo->prepare(<<<SQL
189
SELECT
190
  content,
191
  statusCode,
192
  nextUpdate,
193
  worker
194
FROM robotstxt__cache0
195
WHERE base = :base;
196
SQL
197
        );
198
        $query->bindParam(':base', $base, PDO::PARAM_STR);
199
        $query->execute();
200
        if ($query->rowCount() > 0) {
201
            $row = $query->fetch(PDO::FETCH_ASSOC);
202
            if ($row['nextUpdate'] >= (time() - $this->clientUpdateMargin)) {
203
                $this->markAsActive($base, $row['worker']);
204
                return new Input($base, $row['code'], $row['content'], self::ENCODING, $this->byteLimit);
205
            }
206
        }
207
        $request = new URI($base, $this->guzzleConfig, $this->byteLimit);
208
        $this->push($request);
209
        $this->markAsActive($base);
210
        return $request->getClientClass();
211
    }
212
213
    /**
214
     * Mark robots.txt as active
215
     *
216
     * @param string $base
217
     * @param int|null $workerID
218
     * @return bool
219
     */
220
    private function markAsActive($base, $workerID = 0)
221
    {
222
        if ($workerID == 0) {
223
            $query = $this->pdo->prepare(<<<SQL
224
UPDATE robotstxt__cache0
225
SET worker = NULL
226
WHERE base = :base AND worker = 0;
227
SQL
228
            );
229
            $query->bindParam(':base', $base, PDO::PARAM_STR);
230
            return $query->execute();
231
        }
232
        return true;
233
    }
234
}
235