TableDataDumper::dump()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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