Completed
Push — master ( 3a5c6d...7f7360 )
by Emmanuel
02:10
created

DB::getPrimaryKey()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.4746

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 5
cts 8
cp 0.625
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 3
nop 1
crap 3.4746
1
<?php
2
/**
3
 * neuralyzer : Data Anonymization Library and CLI Tool
4
 *
5
 * PHP Version 7.0
6
 *
7
 * @author Emmanuel Dyan
8
 * @author Rémi Sauvat
9
 * @copyright 2017 Emmanuel Dyan
10
 *
11
 * @package edyan/neuralyzer
12
 *
13
 * @license GNU General Public License v2.0
14
 *
15
 * @link https://github.com/edyan/neuralyzer
16
 */
17
18
namespace Inet\Neuralyzer\Anonymizer;
19
20
use Inet\Neuralyzer\Exception\NeuralizerException;
21
22
/**
23
 * DB Anonymizer
24
 */
25
class DB extends AbstractAnonymizer
26
{
27
    /**
28
     * The PDO connection
29
     *
30
     * @var \PDO
31
     */
32
    private $pdo;
33
34
    /**
35
     * Prepared statement is stored for the update, to make the queries faster
36
     *
37
     * @var \PDOStatement
38
     */
39
    private $preparedStmt;
40
41
    /**
42
     * Constructor
43
     *
44
     * @param \PDO $pdo
45
     */
46 2
    public function __construct(\PDO $pdo)
47
    {
48 2
        $this->pdo = $pdo;
49 2
        $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
50 2
    }
51
52
    /**
53
     * Process an entity by reading / writing to the DB
54
     *
55
     * @param string        $table
56
     * @param callable|null $callback
57
     * @param bool          $pretend
58
     * @param bool          $returnResult
59
     *
60
     * @return void|array
61
     */
62 1
    public function processEntity(
63
        string $table,
64
        callable $callback = null,
65
        bool $pretend = true,
66
        bool $returnResult = false
67
    ) {
68 1
        $queries = [];
69 1
        $actionsOnThatEntity = $this->whatToDoWithEntity($table);
70
71 1
        if ($actionsOnThatEntity & self::TRUNCATE_TABLE) {
72
            $where = $this->getWhereConditionInConfig($table);
73
            $query = $this->runDelete($table, $where, $pretend);
74
            ($returnResult === true ? array_push($queries, $query) : '');
75
        }
76
77 1
        if ($actionsOnThatEntity & self::UPDATE_TABLE) {
78
            // I need to read line by line if I have to update the table
79
            // to make sure I do update by update (slower but no other choice for now)
80 1
            $i = 0;
81 1
            $this->preparedStmt = null;
82
83 1
            $key = $this->getPrimaryKey($table);
84 1
            $res = $this->pdo->query("SELECT $key FROM $table");
85 1
            $res->setFetchMode(\PDO::FETCH_ASSOC);
86
87 1
            $this->pdo->beginTransaction();
88
89 1
            while ($row = $res->fetch()) {
90 1
                $val = $row[$key];
91 1
                $data = $this->generateFakeData($table);
92
93 1
                if ($pretend === false) {
94 1
                    $this->runUpdate($table, $data, $key, $val);
95
                }
96 1
                ($returnResult === true ? array_push($queries, $this->buildUpdateSQL($table, $data, "$key = '$val'")) : '');
97
98 1
                if (!is_null($callback)) {
99 1
                    $callback(++$i);
100
                }
101
            }
102
103
            // Commit, even if I am in pretend (that will do ... nothing)
104 1
            $this->pdo->commit();
105
        }
106
107 1
        return $queries;
108
    }
109
110
    /**
111
     * Identify the primary key for a table
112
     *
113
     * @param string $table
114
     *
115
     * @return string Field's name
116
     */
117 1
    protected function getPrimaryKey(string $table)
118
    {
119
        try {
120 1
            $res = $this->pdo->query("SHOW COLUMNS FROM $table WHERE `Key` = 'Pri'");
121
        } catch (\Exception $e) {
122
            throw new NeuralizerException('Query Error : ' . $e->getMessage());
123
        }
124
125 1
        $primary = $res->fetchAll(\PDO::FETCH_COLUMN);
126
        // Didn't find a primary key !
127 1
        if (empty($primary)) {
128
            throw new NeuralizerException("Can't find a primary key for '$table'");
129
        }
130
131 1
        return $primary[0];
132
    }
133
134
    /**
135
     * Execute the Update with PDO
136
     *
137
     * @param string $table      Name of the table
138
     * @param array  $data       Array of fields => value to update the table
139
     * @param string $primaryKey
140
     * @param string $val        Primary Key's Value
141
     */
142 1
    private function runUpdate(string $table, array $data, string $primaryKey, $val)
143
    {
144 1
        if (is_null($this->preparedStmt)) {
145 1
            $this->prepareStmt($table, $primaryKey, array_keys($data));
146
        }
147
148 1
        $values = [":$primaryKey" => $val];
149 1
        foreach ($data as $field => $value) {
150 1
            $values[":$field"] = $value;
151
        }
152
153
        try {
154 1
            $this->preparedStmt->execute($values);
155
        } catch (\PDOException $e) {
156
            $this->pdo->rollback();
157
            throw new NeuralizerException("Problem anonymizing $table (" . $e->getMessage() . ')');
158
        }
159 1
    }
160
161
    /**
162
     * Prepare the statement if asked
163
     *
164
     * @param string $table
165
     * @param string $primaryKey
166
     * @param array  $fieldNames
167
     *
168
     * @return \PDOStatement
169
     */
170 1
    private function prepareStmt(string $table, string $primaryKey, array $fieldNames)
171
    {
172 1
        $fields = [];
173 1
        foreach ($fieldNames as $field) {
174 1
            $fields[] = "$field = IF($field IS NOT NULL, :$field, NULL)";
175
        }
176 1
        $sql = "UPDATE $table SET " . implode(', ', $fields) . " WHERE $primaryKey = :$primaryKey";
177 1
        $this->preparedStmt = $this->pdo->prepare($sql);
178 1
    }
179
180
    /**
181
     * Execute the Delete with PDO
182
     *
183
     * @param string $table
184
     * @param string $where
185
     * @param bool   $pretend
186
     *
187
     * @return string
188
     */
189
    private function runDelete(string $table, string $where, bool $pretend): string
190
    {
191
        $where = empty($where) ? '' : " WHERE $where";
192
        $sql = "DELETE FROM {$table}{$where}";
193
194
        if ($pretend === true) {
195
            return $sql;
196
        }
197
198
        try {
199
            $this->pdo->query($sql);
200
        } catch (\Exception $e) {
201
            throw new NeuralizerException('Query DELETE Error (' . $e->getMessage() . ')');
202
        }
203
204
        return $sql;
205
    }
206
207
    /**
208
     * Build the SQL just for debug
209
     *
210
     * @param string $table
211
     * @param array  $data
212
     * @param string $where
213
     *
214
     * @return string
215
     */
216
    private function buildUpdateSQL(string $table, array $data, string $where): string
217
    {
218
        $fieldsVals = [];
219
        foreach ($data as $field => $value) {
220
            $fieldsVals[] = "$field = IF($field IS NOT NULL, '" . $this->pdo->quote($value) . "', NULL)";
221
        }
222
223
        $sql = "UPDATE $table SET " . implode(', ', $fieldsVals) . " WHERE $where";
224
225
        return $sql;
226
    }
227
}
228