Passed
Branch master (918e99)
by Marcio
02:33
created

MySQLDump::checkSizendLen()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 11
c 1
b 0
f 0
dl 0
loc 16
rs 9.9
cc 3
nc 4
nop 3
1
<?php
2
3
namespace Ballybran\Database;
4
5
use mysqli;
6
7
/**
8
 * MySQL database dump.
9
 *
10
 * @version    1.0
11
 */
12
class MySQLDump
13
{
14
    const MAX_SQL_SIZE = 1e6;
15
16
    const NONE = 0;
17
    const DROP = 1;
18
    const CREATE = 2;
19
    const DATA = 4;
20
    const TRIGGERS = 8;
21
    const ALL = 15; // DROP | CREATE | DATA | TRIGGERS
22
23
    private $size = 0;
24
    private $delTable;
25
    /** @var array */
26
    public $tables = [
27
        '*' => self::ALL,
28
    ];
29
30
    /** @var mysqli */
31
    private $connection;
32
33
34
    /**
35
     * Connects to database.
36
     * @param  mysqli connection
0 ignored issues
show
Bug introduced by
The type Ballybran\Database\connection was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
37
     */
38
    public function __construct(mysqli $connection, $charset = 'utf8')
39
    {
40
        $this->connection = $connection;
41
42
        if ($connection->connect_errno) {
43
            throw new \Exception($connection->connect_error);
44
45
        } elseif (!$connection->set_charset($charset)) { // was added in MySQL 5.0.7 and PHP 5.0.5, fixed in PHP 5.1.5)
46
            throw new \Exception($connection->error);
47
        }
48
    }
49
50
51
    /**
52
     * Saves dump to the file.
53
     * @param  string filename
0 ignored issues
show
Bug introduced by
The type Ballybran\Database\filename was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
54
     * @return void
55
     */
56
    public function save($file)
57
    {
58
        $handle = strcasecmp(substr($file, -3), '.gz') ? fopen($file, 'wb') : gzopen($file, 'wb');
59
        if (!$handle) {
0 ignored issues
show
introduced by
$handle is of type false|resource, thus it always evaluated to false.
Loading history...
60
            throw new \Exception("ERROR: Cannot write file '$file'.");
61
        }
62
        $this->write($handle);
63
    }
64
65
66
    /**
67
     * Writes dump to logical file.
68
     * @param  resource
69
     * @return void
70
     */
71
    public function write($handle = null)
72
    {
73
        if ($handle === null) {
74
            $handle = fopen('php://output', 'wb');
75
        } elseif (!is_resource($handle) || get_resource_type($handle) !== 'stream') {
76
            throw new \Exception('Argument must be stream resource.');
77
        }
78
79
        $tables = $views = [];
80
81
        $res = $this->connection->query('SHOW FULL TABLES');
82
        while ($row = $res->fetch_row()) {
83
            if ($row[1] === 'VIEW') {
84
                $views[] = $row[0];
85
            } else {
86
                $tables[] = $row[0];
87
            }
88
        }
89
        $res->close();
90
91
        $tables = array_merge($tables, $views); // views must be last
92
93
        $this->connection->query('LOCK TABLES `' . implode('` READ, `', $tables) . '` READ');
94
95
        $db = $this->connection->query('SELECT DATABASE()')->fetch_row();
96
        fwrite($handle, '-- Created at ' . date('j.n.Y G:i') . " using David Grudl MySQL Dump Utility\n"
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

96
        fwrite(/** @scrutinizer ignore-type */ $handle, '-- Created at ' . date('j.n.Y G:i') . " using David Grudl MySQL Dump Utility\n"
Loading history...
97
            . (isset($_SERVER['HTTP_HOST']) ? "-- Host: $_SERVER[HTTP_HOST]\n" : '')
98
            . '-- MySQL Server: ' . $this->connection->server_info . "\n"
99
            . '-- Database: ' . $db[0] . "\n"
100
            . "\n"
101
            . "SET NAMES utf8;\n"
102
            . "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO';\n"
103
            . "SET FOREIGN_KEY_CHECKS=0;\n"
104
            . "SET UNIQUE_CHECKS=0;\n"
105
            . "SET AUTOCOMMIT=0;\n"
106
        );
107
108
        foreach ($tables as $table) {
109
            $this->dumpTable($handle, $table);
110
        }
111
112
        fwrite($handle, "COMMIT;\n");
113
        fwrite($handle, "-- THE END\n");
114
115
        $this->connection->query('UNLOCK TABLES');
116
    }
117
118
119
    /**
120
     * Dumps table to logical file.
121
     * @param  resource
122
     * @return void
123
     */
124
    public function dumpTable($handle, $table)
125
    {
126
        $this->delTable = $this->delimite($table);
127
        $res = $this->connection->query("SHOW CREATE TABLE $this->delTable");
128
        $row = $res->fetch_assoc();
129
        $res->close();
130
131
        fwrite($handle, "-- --------------------------------------------------------\n\n");
132
133
        $mode = isset($this->tables[$table]) ? $this->tables[$table] : $this->tables['*'];
134
        $view = isset($row['Create View']);
135
136
        if ($mode & self::DROP) {
137
            fwrite($handle, 'DROP ' . ($view ? 'VIEW' : 'TABLE') . " IF EXISTS $this->delTable;\n\n");
138
        }
139
140
        if ($mode & self::CREATE) {
141
            fwrite($handle, $row[$view ? 'Create View' : 'Create Table'] . ";\n\n");
142
        }
143
144
        if (!$view && ($mode & self::DATA)) {
145
            fwrite($handle, 'ALTER ' . ($view ? 'VIEW' : 'TABLE') . ' ' . $this->delTable . " DISABLE KEYS;\n\n");
0 ignored issues
show
introduced by
The condition $view is always false.
Loading history...
146
            $numeric = [];
147
            $res = $this->connection->query("SHOW COLUMNS FROM $this->delTable");
148
            $cols = [];
149
            while ($row = $res->fetch_assoc()) {
150
                $col = $row['Field'];
151
                $cols[] = $this->delimite($col);
152
                $numeric[$col] = (bool)preg_match('#^[^(]*(BYTE|COUNTER|SERIAL|INT|LONG$|CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER)#i', $row['Type']);
153
            }
154
            $cols = '(' . implode(', ', $cols) . ')';
155
            $res->close();
156
157
            $this->mysqliUseResult($mode, $table, $numeric, $cols, $view, $handle);
158
        }
159
    }
160
161
        private function mysqliUseResult($mode, $table, $numeric, $cols, $view, $handle){
162
            $this->size = 0;
163
            $res = $this->connection->query("SELECT * FROM $this->delTable", MYSQLI_USE_RESULT);
164
            while ($row = $res->fetch_assoc()) {
165
                $s = '(';
166
                foreach ($row as $key => $value) {
167
                    if ($value === null) {
168
                        $s .= "NULL,\t";
169
                    } elseif ($numeric[$key]) {
170
                        $s .= $value . ",\t";
171
                    } else {
172
                        $s .= "'" . $this->connection->real_escape_string($value) . "',\t";
173
                    }
174
                }
175
176
                echo $this->checkSizendLen($cols, $s, $handle);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->checkSizendLen($cols, $s, $handle) targeting Ballybran\Database\MySQLDump::checkSizendLen() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
177
            }
178
179
            $res->close();
180
            if ($this->size) {
181
                fwrite($handle, ";\n");
182
            }
183
            fwrite($handle, 'ALTER ' . ($view ? 'VIEW' : 'TABLE') . ' ' . $this->delTable . " ENABLE KEYS;\n\n");
184
            fwrite($handle, "\n");
185
        
186
187
188
            $this->trigger($mode, $handle, $table);
189
        
190
        fwrite($handle, "\n");
191
    
192
    }
193
    function checkSizendLen($cols, $s, $handle){
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
194
195
        if ($this->size == 0) {
196
            $s = "INSERT INTO $this->delTable $cols VALUES\n$s";
197
        } else {
198
            $s = ",\n$s";
199
        }
200
201
        $len = strlen($s) - 1;
202
        $s[$len - 1] = ')';
203
        fwrite($handle, $s, $len);
204
205
        $this->size += $len;
206
        if ($this->size > self::MAX_SQL_SIZE) {
207
            fwrite($handle, ";\n");
208
            $this->size = 0;
209
        }
210
    }
211
    private function trigger($mode, $handle, $table){
212
        if ($mode & self::TRIGGERS) {
213
            $res = $this->connection->query("SHOW TRIGGERS LIKE '" . $this->connection->real_escape_string($table) . "'");
214
            if ($res->num_rows) {
215
                fwrite($handle, "DELIMITER ;;\n\n");
216
                while ($row = $res->fetch_assoc()) {
217
                    fwrite($handle, "CREATE TRIGGER {$this->delimite($row['Trigger'])} $row[Timing] $row[Event] ON $this->delTable FOR EACH ROW\n$row[Statement];;\n\n");
218
                }
219
                fwrite($handle, "DELIMITER ;\n\n");
220
            }
221
            $res->close();
222
        }
223
224
    }
225
226
227
    private function delimite($s)
228
    {
229
        return '`' . str_replace('`', '``', $s) . '`';
230
    }
231
}
232