Passed
Push — master ( a3d508...a65292 )
by Alfred
03:35 queued 01:43
created

GeoipDatabase::rollback()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
namespace iriven\bin;
4
5
class GeoipDatabase
6
{
7
    const DS = DIRECTORY_SEPARATOR;
8
    /**
9
     * Database instance
10
     *
11
     * @var \PDO
12
     */
13
    private $oPDOInstance;
14
    /**
15
     * PDO transaction Counter
16
    *
17
    * @var integer
18
    */
19
    private $transactionCounter = 0;
20
    /**
21
     * Class Constructor
22
     *
23
     * @param string $database
24
     */
25
    public function __construct(string $database = null)
26
    {
27
        try
28
        {
29
            if (!extension_loaded('pdo_sqlite')):
30
                throw new \Throwable(
31
                    'The PHP PDO_SQLite extension is required. Please enable it before running this program !'
0 ignored issues
show
Unused Code introduced by
The call to Throwable::__construct() has too many arguments starting with 'The PHP PDO_SQLite exte...running this program !'. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

31
                throw /** @scrutinizer ignore-call */ new \Throwable(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
32
                );
33
            endif;
34
            $aOptions = [
35
                    \PDO::ATTR_ERRMODE            => \PDO::ERRMODE_EXCEPTION,
36
                    \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
37
                    \PDO::ATTR_EMULATE_PREPARES   => false,
38
                ];
39
            $this->oPDOInstance = new \PDO($this->genDsn($database), null, null, $aOptions);
40
            $this->initialize();
41
        } catch (\Throwable $th) {
42
            trigger_error($th->getMessage(), E_USER_ERROR);
43
        }
44
        return $this->oPDOInstance;
45
    }
46
    /**
47
     * Create Database tables structure.
48
     *
49
     * @return GeoipDatabase
50
     */
51
    private function initialize()
52
    {
53
        $aCommands = [
54
            'CREATE TABLE IF NOT EXISTS `ipv4Range`(
55
                    `start` INT UNSIGNED ,
56
                    `end` INT UNSIGNED ,
57
                    `country` VARCHAR(2)
58
                )',
59
            'CREATE TABLE IF NOT EXISTS `ipv6Range`(
60
                    `start` BIGINT UNSIGNED ,
61
                    `end` BIGINT UNSIGNED ,
62
                    `country` VARCHAR(2)
63
                )',
64
            'CREATE UNIQUE INDEX IF NOT EXISTS `idx_ipv4Range` ON `ipv4Range`(`start`, `end`)',
65
            'CREATE UNIQUE INDEX IF NOT EXISTS `idx_ipv6Range` ON `ipv6Range`(`start`, `end`)'
66
            ];
67
        foreach ($aCommands as $command)
68
        {
69
            $this->oPDOInstance->query($command);
70
        }
71
        return $this;
72
    }
73
    /**
74
     * Generate PDO SQLite3 DSN
75
     *
76
     * @param string|null $database
77
     * @return string
78
     */
79
    private function genDsn(string $database = null)
80
    {
81
        $database || $database='Geoip.db.sqlite';
82
        try {
83
            $destination = rtrim(dirname(__DIR__), self::DS);
84
            if (!is_writeable($destination))
85
            {
86
                throw new \RuntimeException('The required destination path is not writable: '.$destination);
87
            }
88
            $info = new \SplFileInfo($database);
89
            $dbName= $info->getFilename();
90
            $dbSuffix='.sqlite';
91
            if (substr_compare(strtolower($dbName), $dbSuffix, -strlen($dbSuffix)) !== 0) { $dbName .= $dbSuffix ; }
92
        } catch (\RuntimeException $th) {
93
            trigger_error($th->getMessage(), E_USER_ERROR);
94
        }
95
        $destination .= self::DS.'data';
96
        if (!is_dir($destination)) { mkdir($destination, '0755', true); }
97
        return 'sqlite:'.realpath($destination).self::DS.$dbName ;
98
    }
99
    /**
100
     * Get the table list in the database
101
     *
102
     * @return array
103
     */
104
    public function showTables()
105
    {
106
        try
107
        {
108
            $command = 'SELECT `name` FROM `sqlite_master` WHERE `type` = \'table\' ORDER BY name';
109
            $statement = $this->oPDOInstance->query($command);
110
            $tables = [];
111
            while ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
112
                $tables[] = $row['name'];
113
            }
114
            return $tables;
115
        } catch (\PDOException $th) {
116
            trigger_error($th->getMessage(), E_USER_ERROR);
117
        }
118
    }
119
    /**
120
     * Retrieve Column(s) value from given table
121
     *
122
     * @param string $sTable
123
     * @param array $columns
124
     * @return array
125
     */
126
    public function fetchAll(string $sTable, array $columns = [])
127
    {
128
        !empty($columns) || $columns = '*';
129
        if (is_array($columns)) { $columns = implode('`, `', $columns); }
130
        try
131
        {
132
            $sCommand = 'SELECT `%s` from  `%s`';
133
            $statement = $this->oPDOInstance->prepare(sprintf($sCommand, $columns, $sTable));
134
            $statement->execute();
135
            return $statement->fetchAll();
136
        } catch (\PDOException $th) {
137
            trigger_error($th->getMessage(), E_USER_ERROR);
138
        }
139
    }
140
    /**
141
     * Return Country code from given IP address (converted to integer)
142
     *
143
     * @param integer $start
144
     * @param integer $ipVersion  (ip version)
145
     * @return string
146
     */
147
    public function fetch(int $start, int $ipVersion)
148
    {
149
        try
150
        {
151
            $sCommand  = 'SELECT `start`, `end`, `country` ';
152
            $sCommand .= 'FROM `ipv%dRange` ';
153
            $sCommand .= 'WHERE `start` <= :start ';
154
            $sCommand .= 'ORDER BY start DESC LIMIT 1';
155
            $statement = $this->oPDOInstance->prepare(sprintf($sCommand, $ipVersion)) ;
156
            $statement->execute([':start' => $start ]) ;
157
            $row = $statement->fetch(\PDO::FETCH_OBJ) ;
158
            if (is_bool($row) && $row === false)
159
            {
160
                $row = new \stdClass();
161
                $row->end = 0 ;
162
            }
163
            if ($row->end < $start || !$row->country) { $row->country = 'ZZ' ; }
164
            return $row->country ;
165
        } catch (\PDOException $th) {
166
            trigger_error($th->getMessage(), E_USER_ERROR);
167
        }
168
    }
169
    /**
170
     * Empty a given list of database tables
171
     *
172
     * @param array $tablesList
173
     * @return void
174
     */
175
    public function flush(array $tablesList=[])
176
    {
177
        !empty($tablesList) || $tablesList = $this->showTables();
178
        is_array($tablesList) || $tablesList = [$tablesList];
179
        try
180
        {
181
            if (!empty($tablesList)):
182
                $sCommand = 'DELETE FROM `%s`';
183
                foreach ($tablesList as $sTable) {
184
                    $this->oPDOInstance->query(sprintf($sCommand, $sTable));
185
                }
186
                $this->oPDOInstance->query('VACUUM');
187
            endif;
188
        } catch (\PDOException $th) {
189
            trigger_error('Statement failed: ' . $th->getMessage(), E_USER_ERROR);
190
        }
191
    }
192
    /**
193
     * Insert data into database
194
     *
195
     * @param integer $start
196
     * @param integer $end
197
     * @param integer $ipVersion
198
     * @param string $country
199
     * @return void
200
     */
201
    public function insert(int $start, int $end, int $ipVersion, string $country)
202
    {
203
        try
204
        {
205
            $sQuery ='INSERT INTO `ipv%dRange` (`start`, `end`, `country`) values (:start, :end, :country)';
206
            $command = sprintf($sQuery, $ipVersion);
207
            $statement = $this->oPDOInstance->prepare($command);
208
            $statement->execute([
209
                ':start'   => $start,
210
                ':end'     => $end,
211
                ':country' => $country
212
            ]) ;
213
        } catch (\PDOException $th) {
214
            trigger_error('Statement failed: ' . $th->getMessage(), E_USER_ERROR);
215
        }
216
    }
217
    /**
218
     * Begin PDO transaction, turning off autocommit
219
     *
220
     * @return bool
221
     */
222
    public function beginTransaction()
223
    {
224
        if (!$this->transactionCounter++) {return $this->oPDOInstance->beginTransaction();}
225
        return $this->transactionCounter >= 0;
226
    }
227
    /**
228
     * Commit PDO transaction changes
229
     *
230
     * @return bool
231
     */
232
    public function commit()
233
    {
234
        if (!--$this->transactionCounter) {return $this->oPDOInstance->commit();}
235
        return $this->transactionCounter >= 0;
236
    }
237
    /**
238
     * Rollback PDO transaction, Recognize mistake and roll back changes
239
     *
240
     * @return bool
241
     */
242
    public function rollback()
243
    {
244
        if ($this->transactionCounter >= 0) {
245
            $this->transactionCounter = 0;
246
            return $this->oPDOInstance->rollback();
247
        }
248
        $this->transactionCounter = 0;
249
        return false;
250
    }
251
}
252