Completed
Push — master ( d4f070...1ddfcf )
by Jan-Petter
05:06
created

DelayHandlerClient   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 199
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 18
c 1
b 0
f 0
lcom 2
cbo 3
dl 0
loc 199
rs 10

7 Methods

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