Completed
Push — master ( f17301...aeaf79 )
by Jan-Petter
02:43
created

DelayHandlerClient::pdoInitialize()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 22
rs 8.6737
cc 5
eloc 16
nc 8
nop 1
1
<?php
2
namespace vipnytt\RobotsTxtParser\Client\Directives;
3
4
use PDO;
5
use vipnytt\RobotsTxtParser\Exceptions\SQLException;
6
use vipnytt\RobotsTxtParser\SQL\SQLInterface;
7
use vipnytt\UserAgentParser;
8
9
/**
10
 * Class DelayHandlerClient
11
 *
12
 * @package vipnytt\RobotsTxtParser\Client\Directives
13
 */
14
class DelayHandlerClient implements SQLInterface
15
{
16
    /**
17
     * Supported database drivers
18
     */
19
    const SUPPORTED_DRIVERS = [
20
        self::DRIVER_MYSQL,
21
    ];
22
23
    /**
24
     * Database connection
25
     * @var PDO
26
     */
27
    private $pdo;
28
29
    /**
30
     * PDO driver
31
     * @var string
32
     */
33
    private $driver;
34
35
    /**
36
     * Base UriClient
37
     * @var string
38
     */
39
    private $base;
40
41
    /**
42
     * User-agent
43
     * @var string
44
     */
45
    private $userAgent;
46
47
    /**
48
     * Delay
49
     * @var float|int
50
     */
51
    private $delay;
52
53
    /**
54
     * DelayClient constructor.
55
     *
56
     * @param PDO $pdo
57
     * @param string $baseUri
58
     * @param string $userAgent
59
     * @param float|int $delay
60
     * @throws SQLException
61
     */
62
    public function __construct(PDO $pdo, $baseUri, $userAgent, $delay)
63
    {
64
        $this->pdo = $this->pdoInitialize($pdo);
65
        $this->base = $baseUri;
66
        $uaStringParser = new UserAgentParser($userAgent);
67
        $this->userAgent = $uaStringParser->stripVersion();
68
        $this->delay = round($delay, 6, PHP_ROUND_HALF_UP);
69
    }
70
71
    /**
72
     * Initialize PDO connection
73
     *
74
     * @param PDO $pdo
75
     * @return PDO
76
     * @throws SQLException
77
     */
78
    private function pdoInitialize(PDO $pdo)
79
    {
80
        if ($pdo->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_SILENT) {
81
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
82
        }
83
        $pdo->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL);
84
        $pdo->exec('SET NAMES ' . self::SQL_ENCODING);
85
        $this->driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
86
        if (!in_array($this->driver, self::SUPPORTED_DRIVERS)) {
87
            throw new SQLException('Unsupported database. ' . self::README_SQL_DELAY);
88
        }
89
        try {
90
            $pdo->query("SELECT 1 FROM robotstxt__delay0 LIMIT 1;");
91
        } catch (\Exception $exception1) {
92
            try {
93
                $pdo->query(file_get_contents(__DIR__ . '/../../SQL/delay.sql'));
94
            } catch (\Exception $exception2) {
95
                throw new SQLException('Missing table `' . self::TABLE_DELAY . '`. Setup instructions: ' . self::README_SQL_DELAY);
96
            }
97
        }
98
        return $pdo;
99
    }
100
101
    /**
102
     * Queue
103
     *
104
     * @return float|int
105
     */
106
    public function getQueue()
107
    {
108
        if ($this->delay == 0) {
109
            return 0;
110
        }
111
        $query = $this->pdo->prepare(<<<SQL
112
SELECT GREATEST(0, (delayUntil / 1000000) - UNIX_TIMESTAMP(CURTIME(6))) AS sec
113
FROM robotstxt__delay0
114
WHERE base = :base AND userAgent = :userAgent;
115
SQL
116
        );
117
        $query->bindParam(':base', $this->base, PDO::PARAM_STR);
118
        $query->bindParam(':userAgent', $this->userAgent, PDO::PARAM_STR);
119
        $query->execute();
120
        if ($query->rowCount() > 0) {
121
            $row = $query->fetch(PDO::FETCH_ASSOC);
122
            return $row['sec'];
123
        }
124
        return 0;
125
    }
126
127
    /**
128
     * Reset queue
129
     *
130
     * @return bool
131
     */
132
    public function reset()
133
    {
134
        $query = $this->pdo->prepare(<<<SQL
135
DELETE FROM robotstxt__delay0
136
WHERE base = :base AND userAgent = :useragent;
137
SQL
138
        );
139
        $query->bindParam(':base', $this->base, PDO::PARAM_INT);
140
        $query->bindParam(':useragent', $this->userAgent, PDO::PARAM_INT);
141
        return $query->execute();
142
    }
143
144
    /**
145
     * Sleep
146
     *
147
     * @return float|int
148
     */
149
    public function sleep()
150
    {
151
        $start = microtime(true);
152
        $until = $this->getTimeSleepUntil();
153
        if (microtime(true) > $until) {
154
            return 0;
155
        }
156
        try {
157
            time_sleep_until($until);
158
        } catch (\Exception $warning) {
159
            // Timestamp already in the past
160
        }
161
        return microtime(true) - $start;
162
    }
163
164
    /**
165
     * Timestamp with milliseconds
166
     *
167
     * @return float|int
168
     * @throws SQLException
169
     */
170
    public function getTimeSleepUntil()
171
    {
172
        if ($this->delay == 0) {
173
            return 0;
174
        }
175
        $query = $this->pdo->prepare(<<<SQL
176
SELECT
177
  delayUntil,
178
  UNIX_TIMESTAMP()
179
FROM robotstxt__delay0
180
WHERE base = :base AND userAgent = :userAgent;
181
SQL
182
        );
183
        $query->bindParam(':base', $this->base, PDO::PARAM_STR);
184
        $query->bindParam(':userAgent', $this->userAgent, PDO::PARAM_STR);
185
        $query->execute();
186
        $this->increment();
187
        if ($query->rowCount() > 0) {
188
            $row = $query->fetch(PDO::FETCH_ASSOC);
189
            if (abs(time() - $row['UNIX_TIMESTAMP()']) > 10) {
190
                throw new SQLException('`PHP server` and `SQL server` timestamps are out of sync. Please fix!');
191
            }
192
            return $row['delayUntil'] / 1000000;
193
        }
194
        return 0;
195
    }
196
197
    /**
198
     * Set new delayUntil timestamp
199
     *
200
     * @return bool
201
     */
202
    private function increment()
203
    {
204
        $query = $this->pdo->prepare(<<<SQL
205
INSERT INTO robotstxt__delay0 (base, userAgent, delayUntil, lastDelay)
206
VALUES (:base, :userAgent, (UNIX_TIMESTAMP(CURTIME(6)) + :delay) * 1000000, :delay * 1000000)
207
ON DUPLICATE KEY UPDATE
208
  delayUntil = GREATEST((UNIX_TIMESTAMP(CURTIME(6)) + :delay) * 1000000, delayUntil + (:delay * 1000000)),
209
  lastDelay = :delay * 1000000;
210
SQL
211
        );
212
        $query->bindParam(':base', $this->base, PDO::PARAM_STR);
213
        $query->bindParam(':userAgent', $this->userAgent, PDO::PARAM_STR);
214
        $query->bindParam(':delay', $this->delay, is_int($this->delay) ? PDO::PARAM_INT : PDO::PARAM_STR);
215
        return $query->execute();
216
    }
217
}
218