Completed
Push — master ( cba42a...a89212 )
by Gabriel
01:47
created

TableDataDumper   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 142
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 15
lcom 1
cbo 2
dl 0
loc 142
ccs 46
cts 46
cp 1
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A prepareSelect() 0 17 2
A processChunk() 0 21 3
A prepareInsert() 0 11 2
A prepareRow() 0 20 4
A dump() 0 18 3
1
<?php declare(strict_types=1);
2
namespace Y0lk\SQLDumper;
3
4
use PDO;
5
use PDOStatement;
6
7
/**
8
 * A TableDumper instance is used to dump data of single Table
9
 *
10
 * @author Gabriel Jean <[email protected]>
11
 */
12
class TableDataDumper {
13
    /**
14
     * Amount of rows to process per SELECT statement
15
     */
16
    public const CHUNK_SIZE = 1000;
17
18
    /**
19
     * @var TableDumper   TableDumper instance related to this data dumper
20
     */
21
    protected $tableDumper;
22
23
    /**
24
     * @param TableDumper TableDumper this data dumper will be based on
25
     */
26 27
    public function __construct(TableDumper $tableDumper)
27
    {
28 27
        $this->tableDumper = $tableDumper;
29 27
    }
30
31
    /**
32
     * Prepares the SELECT statement that will be used to get the data
33
     * @param  PDO    $db       PDO instance to use for DB queries
34
     * @return PDOStatement     Returns the PDOStatement for to be used for processing chunks
35
     */
36 27
    protected function prepareSelect(PDO $db): PDOStatement
37
    {
38
        //Get data from table
39 27
        $select = 'SELECT * FROM '.$this->tableDumper->getTable()->getName();
40
41 27
        if (!empty($this->tableDumper->getWhere())) {
42 3
            $select .= ' WHERE '.$this->tableDumper->getWhere();
43
        }
44
45
        //Add limit
46 27
        $select .= ' LIMIT :limit OFFSET :offset';
47
48 27
        $stmt = $db->prepare($select);
49 27
        $stmt->bindValue(':limit', self::CHUNK_SIZE, PDO::PARAM_INT);
50
51 27
        return $stmt;
52
    }
53
54
    /**
55
     * Gets the data for a chunk of rows and writes this part of the statement to the dump stream
56
     * @param  PDOStatement $stmt       SELECT statement to get data for the chunk
57
     * @param  resource       $stream   Stream to write to
58
     * @param  int          $chunkIndex Index of the chunk to process
59
     * @return int                      Returns the current row index after processing this chunk
60
     */
61 27
    protected function processChunk(PDOStatement $stmt, $stream, int $chunkIndex): int
62
    {
63 27
        $stmt->bindValue(':offset', $chunkIndex*self::CHUNK_SIZE, PDO::PARAM_INT);
64 27
        $stmt->execute();
65
66 27
        $rowIndex = $chunkIndex*self::CHUNK_SIZE;
67
68 27
        while (($row = $stmt->fetch(PDO::FETCH_ASSOC)) !== false) {
69
            //Write start of INSERT statement
70 24
            if ($rowIndex === 0) {
71
                //Gets keys from array indexes of first row
72 24
                fwrite($stream, $this->prepareInsert(array_keys($row)));
73
            }
74
75 24
            fwrite($stream, $this->prepareRow(array_values($row), $rowIndex === 0));
76 24
            $rowIndex++;
77
        }
78
79 27
        $stmt->closeCursor();
80 27
        return $rowIndex;
81
    }
82
83
    /**
84
     * Prepare the first part of the INSERT statement
85
     * @param  array  $listCols   List columns to include in that INSERT statement
86
     * @return string             Returns the first of the INSERT statement
87
     */
88 24
    protected function prepareInsert(array $listCols): string
89
    {
90
        //Escape them
91 24
        foreach ($listCols as $key => $col) {
92 24
            $listCols[$key] = '`'.$col.'`';
93
        }
94
95 24
        $cols = implode(',', $listCols);
96
        
97 24
        return 'INSERT INTO `'.$this->tableDumper->getTable()->getName().'` ('.$cols.') VALUES ';
98
    }
99
100
    /**
101
     * Takes a list of values for one row and prepares the string for the INSERT statement
102
     * @param  array  $listValues List of values for that row
103
     * @param  bool   $first      TRUE if this is the first row, FALSE otherwise
104
     * @return string             Return the values for that row as a string for the INSERT statement
105
     */
106 24
    protected function prepareRow(array $listValues, bool $first): string
107
    {
108
        //Write values of this row
109 24
        $valuesDump = '';
110
111 24
        if (!$first) {
112 24
            $valuesDump .= ", \r\n";
113
        }
114
115
        //Quote values or replace with NULL if null
116 24
        foreach ($listValues as $key => $value) {
117 24
            $quotedValue = str_replace("'", "\'", str_replace('"', '\"', $value));
118 24
            $listValues[$key] = (!isset($value) ? 'NULL' : "'".$quotedValue."'");
119
        }
120
121
        //Add values from this row to valuesDump
122 24
        $valuesDump .= '('.implode(',', $listValues).')';
123
124 24
        return $valuesDump;
125
    }
126
127
    /**
128
     * Writes an INSERT statement for the data to the dump stream
129
     * 
130
     * @param  PDO      $db     PDO instance to use for DB queries
131
     * @param  resource $stream Stream to write the statement to
132
     * 
133
     * @return void
134
     */
135 27
    public function dump(PDO $db, $stream): void
136
    {
137 27
        $stmt = $this->prepareSelect($db);
138
139 27
        $chunkIndex = 0;
140 27
        $rowIndex = 0;
141
142
        //Dump an INSERT of all rows with paging 
143
        do {
144 27
            $rowIndex = $this->processChunk($stmt, $stream, $chunkIndex, $rowIndex);
0 ignored issues
show
Unused Code introduced by
The call to TableDataDumper::processChunk() has too many arguments starting with $rowIndex.

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
145 27
            $chunkIndex++;
146 27
        } while ($rowIndex === $chunkIndex*self::CHUNK_SIZE);
147
148
        //If there was at least one row, write end of INSERT statement
149 27
        if ($rowIndex > 0) {
150 24
            fwrite($stream, ";\r\n");
151
        }
152
    }
153
}