CommandFacade::executeCommands()   B
last analyzed

Complexity

Conditions 8
Paths 36

Size

Total Lines 58
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 26
c 1
b 0
f 0
nc 36
nop 4
dl 0
loc 58
rs 8.4444

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Lagdo\DbAdmin\Db\Facades;
4
5
use Lagdo\DbAdmin\Driver\Db\ConnectionInterface;
6
use Lagdo\DbAdmin\Driver\Entity\QueryEntity;
7
8
use function compact;
9
use function count;
10
use function function_exists;
11
use function ini_set;
12
use function max;
13
use function memory_get_usage;
14
use function microtime;
15
use function strlen;
16
17
/**
18
 * Facade to command functions
19
 */
20
class CommandFacade extends AbstractFacade
21
{
22
    /**
23
     * Connection for exploring indexes and EXPLAIN (to not replace FOUND_ROWS())
24
     * //! PDO - silent error
25
     *
26
     * @var ConnectionInterface
27
     */
28
    protected $connection = null;
29
30
    /**
31
     * @var array
32
     */
33
    protected $results;
34
35
    /**
36
     * @var float
37
     */
38
    protected $duration;
39
40
    /**
41
     * Open a second connection to the server
42
     *
43
     * @return void
44
     */
45
    private function createConnection()
46
    {
47
        // Connection for exploring indexes and EXPLAIN (to not replace FOUND_ROWS())
48
        //! PDO - silent error
49
        if ($this->connection === null && $this->driver->database() !== '') {
50
            $this->connection = $this->driver
51
                ->connect($this->driver->database(), $this->driver->schema());
52
        }
53
    }
54
55
    /**
56
     * @param array $row
57
     * @param array $blobs
58
     * @param array $types
59
     *
60
     * @return array
61
    */
62
    protected function values(array $row, array $blobs, array $types): array
63
    {
64
        $values = [];
65
        foreach ($row as $key => $value) {
66
            // $link = $this->editLink($val);
67
            if ($value === null) {
68
                $value = '<i>NULL</i>';
69
            } elseif (isset($blobs[$key]) && $blobs[$key] && !$this->utils->str->isUtf8($value)) {
70
                //! link to download
71
                $value = '<i>' . $this->utils->trans->lang('%d byte(s)', strlen($value)) . '</i>';
72
            } else {
73
                $value = $this->utils->str->html($value);
74
                if (isset($types[$key]) && $types[$key] == 254) { // 254 - char
75
                    $value = "<code>$value</code>";
76
                }
77
            }
78
            $values[$key] = $value;
79
        }
80
        return $values;
81
    }
82
83
    /**
84
     * @param mixed $statement
85
     * @param int $limit
86
     *
87
     * @return string
88
    */
89
    private function message($statement, int $limit): string
90
    {
91
        $numRows = $statement->rowCount();
92
        $message = '';
93
        if ($numRows > 0) {
94
            if ($limit > 0 && $numRows > $limit) {
95
                $message = $this->utils->trans->lang('%d / ', $limit);
96
            }
97
            $message .= $this->utils->trans->lang('%d row(s)', $numRows);
98
        }
99
        return $message;
100
    }
101
102
    /**
103
     * Print select result
104
     * From editing.inc.php
105
     *
106
     * @param mixed $statement
107
     * @param int $limit
108
     *
109
     * @return array
110
    */
111
    protected function select($statement, int $limit = 0): array
112
    {
113
        // No resultset
114
        if ($statement === true) {
115
            $affected = $this->driver->affectedRows();
116
            $message = $this->utils->trans
117
                ->lang('Query executed OK, %d row(s) affected.', $affected); //  . "$time";
118
            return [null, [$message]];
119
        }
120
        // Fetch the first row.
121
        if (!($row = $statement->fetchRow())) {
122
            // Empty resultset.
123
            $message = $this->utils->trans->lang('No rows.');
124
            return [null, [$message]];
125
        }
126
127
        $blobs = []; // colno => bool - display bytes for blobs
128
        $types = []; // colno => type - display char in <code>
129
        $tables = []; // table => orgtable - mapping to use in EXPLAIN
130
        $headers = [];
131
        $details = [];
132
        // Table headers.
133
        $colCount = count($row);
134
        for ($j = 0; $j < $colCount; $j++) {
135
            $field = $statement->fetchField();
136
            // PostgreSQL fix: the table field can be missing.
137
            $tables[$field->tableName()] = $field->orgTable();
138
            // $this->indexes($field);
139
            if ($field->isBinary()) {
140
                $blobs[$j] = true;
141
            }
142
            $types[$j] = $field->type(); // Some drivers don't set the type field.
143
            $headers[] = $this->utils->str->html($field->name());
144
        }
145
146
        // Table rows (the first was already fetched).
147
        $rowCount = 0;
148
        do {
149
            $rowCount++;
150
            $details[] = $this->values($row, $blobs, $types);
151
        } while (($limit === 0 || $rowCount < $limit) && ($row = $statement->fetchRow()));
152
153
        $message = $this->message($statement, $limit);
154
        return [compact('tables', 'headers', 'details'), [$message]];
155
    }
156
157
    /**
158
     * @param QueryEntity $queryEntity
159
     *
160
     * @return bool
161
     */
162
    private function executeCommand(QueryEntity $queryEntity): bool
163
    {
164
        //! Don't allow changing of character_set_results, convert encoding of displayed query
165
        $startTimestamp = microtime(true);
166
        if ($this->driver->multiQuery($queryEntity->query) && $this->connection !== null) {
167
            $this->connection->execUseQuery($queryEntity->query);
168
        }
169
        $this->duration += max(0, microtime(true) - $startTimestamp);
170
171
        do {
172
            $select = null;
173
            $errors = [];
174
            $messages = [];
175
            $statement = $this->driver->storedResult();
176
177
            if ($this->driver->hasError()) {
178
                $errors[] = $this->driver->errorMessage();
179
            } elseif (!$queryEntity->onlyErrors) {
180
                [$select, $messages] = $this->select($statement, $queryEntity->limit);
181
            }
182
183
            $result = compact('errors', 'messages', 'select');
184
            $result['query'] = $queryEntity->query;
185
            $this->results[] = $result;
186
            if ($this->driver->hasError() && $queryEntity->errorStops) {
187
                return false;
188
            }
189
        } while ($this->driver->nextResult());
190
191
        return true;
192
    }
193
194
    /**
195
     * Execute a set of queries
196
     *
197
     * @param string $queries       The queries to execute
198
     * @param int    $limit         The max number of rows to return
199
     * @param bool   $errorStops    Stop executing the requests in case of error
200
     * @param bool   $onlyErrors    Return only errors
201
     *
202
     * @return array
203
     */
204
    public function executeCommands(string $queries, int $limit, bool $errorStops, bool $onlyErrors): array
205
    {
206
        if (function_exists('memory_get_usage')) {
207
            // @ - may be disabled, 2 - substr and trim, 8e6 - other variables
208
            try {
209
                ini_set('memory_limit', max($this->admin->iniBytes('memory_limit'),
210
                    2 * strlen($queries) + memory_get_usage() + 8e6));
211
            }
212
            catch(\Exception $e) {
213
                // Do nothing if the option is not modified.
214
            }
215
        }
216
217
        // if($queries != '' && strlen($queries) < 1e6) { // don't add big queries
218
        // 	$q = $queries . (\preg_match("~;[ \t\r\n]*\$~", $queries) ? '' : ';'); //! doesn't work with DELIMITER |
219
        // 	if(!$history || \reset(\end($history)) != $q) { // no repeated queries
220
        // 		\restart_session();
221
        // 		$history[] = [$q, \time()]; //! add elapsed time
222
        // 		\set_session('queries', $history_all); // required because reference is unlinked by stop_session()
223
        // 		\stop_session();
224
        // 	}
225
        // }
226
227
        // $timestamps = [];
228
        // $total_start = \microtime(true);
229
        // \parse_str($_COOKIE['adminer_export'], $adminer_export);
230
        // $dump_format = $this->admin->dumpFormat();
231
        // unset($dump_format['sql']);
232
233
        // The second connection must be created before executing the queries.
234
        $this->createConnection();
235
236
        $this->results = [];
237
        $this->duration = 0;
238
        $commands = 0;
239
        $errors = 0;
240
        $queryEntity = new QueryEntity($queries, $limit, $errorStops, $onlyErrors);
241
        while ($this->driver->parseQueries($queryEntity)) {
242
            $commands++;
243
            if (!$this->executeCommand($queryEntity)) {
244
                $errors++;
245
                if ($errorStops) {
246
                    break;
247
                }
248
            }
249
        }
250
251
        $messages = [];
252
        if ($commands === 0) {
253
            $messages[] = $this->utils->trans->lang('No commands to execute.');
254
        } elseif ($onlyErrors) {
255
            $messages[] =  $this->utils->trans->lang('%d query(s) executed OK.', $commands - $errors);
256
        }
257
258
        return [
259
            'results' => $this->results,
260
            'messages' => $messages,
261
            'duration' => $this->duration,
262
        ];
263
    }
264
}
265