Completed
Push — master ( 83a00c...7fd29b )
by Emmanuel
04:00
created

DB::getConn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * neuralyzer : Data Anonymization Library and CLI Tool
4
 *
5
 * PHP Version 7.1
6
 *
7
 * @author Emmanuel Dyan
8
 * @author Rémi Sauvat
9
 * @copyright 2018 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 Doctrine\DBAL\Configuration as DbalConfiguration;
21
use Doctrine\DBAL\Query\QueryBuilder;
22
use Inet\Neuralyzer\Exception\NeuralizerException;
23
24
/**
25
 * DB Anonymizer
26
 */
27
class DB extends AbstractAnonymizer
28
{
29
    /**
30
     * Zend DB Adapter
31
     *
32
     * @var \Doctrine\DBAL\Connection
33
     */
34
    private $conn;
35
36
    /**
37
     * Constructor
38
     *
39
     * @param $params   Parameters to send to Doctrine DB
40
     */
41
    public function __construct(array $params)
42
    {
43
        $this->conn = \Doctrine\DBAL\DriverManager::getConnection($params, new DbalConfiguration());
44
    }
45
46 16
    /**
47
     * Process an entity by reading / writing to the DB
48 16
     *
49 16
     * @param string        $table
50 16
     * @param callable|null $callback
51
     * @param bool          $pretend
52
     * @param bool          $returnResult
53
     *
54
     * @return void|array
55
     */
56
    public function processEntity(
57
        string $table,
58
        callable $callback = null,
59
        bool $pretend = true,
60
        bool $returnResult = false
61
    ) {
62 14
        $schema = $this->conn->getSchemaManager();
63
        if ($schema->tablesExist($table) === false) {
0 ignored issues
show
Documentation introduced by
$table is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
64
            throw new NeuralizerException("Table $table does not exist");
65
        }
66
67
        $queries = [];
68 14
        $actionsOnThatEntity = $this->whatToDoWithEntity($table);
69 14
70
        if ($actionsOnThatEntity & self::TRUNCATE_TABLE) {
71 12
            $where = $this->getWhereConditionInConfig($table);
72 4
            $query = $this->runDelete($table, $where, $pretend);
73 4
            ($returnResult === true ? array_push($queries, $query) : '');
74 3
        }
75
76
        if ($actionsOnThatEntity & self::UPDATE_TABLE) {
77 11
            // I need to read line by line if I have to update the table
78
            // to make sure I do update by update (slower but no other choice for now)
79
            $rowNum = 0;
80 10
81 10
            $key = $this->getPrimaryKey($table);
82
            $tableCols = $this->getTableCols($table);
83 10
84 8
            $queryBuilder = $this->conn->createQueryBuilder();
85 8
            $rows = $queryBuilder->select($key)->from($table)->execute();
86
87 8
            foreach ($rows as $row) {
0 ignored issues
show
Bug introduced by
The expression $rows of type object<Doctrine\DBAL\Driver\Statement>|integer is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
88
                $val = $row[$key];
89 8
                $data = $this->generateFakeData($table, $tableCols);
90 8
                $queryBuilder = $this->prepareUpdate($table, $data, $key, $val);
91 8
92
                ($returnResult === true ? array_push($queries, $this->getRawSQL($queryBuilder)) : '');
93 8
94 5
                if ($pretend === false) {
95
                    $this->runUpdate($queryBuilder, $table);
96 7
                }
97
98 7
                if (!is_null($callback)) {
99 3
                    $callback(++$rowNum);
100
                }
101
            }
102
        }
103
104 7
        return $queries;
105
    }
106
107 8
108
    /**
109
     * Get Doctrine Connection
110
     * @return Doctrine\DBAL\Connection
111
     */
112
    public function getConn()
113
    {
114
        return $this->conn;
115
    }
116
117 10
118
    /**
119
     * Identify the primary key for a table
120 10
     *
121 1
     * @param string $table
122 1
     *
123
     * @return string Field's name
124
     */
125 9
    protected function getPrimaryKey(string $table)
126
    {
127 9
        $schema = $this->conn->getSchemaManager();
128 1
        $tableDetails = $schema->listTableDetails($table);
129
        if ($tableDetails->hasPrimaryKey() === false) {
130
            throw new NeuralizerException("Can't find a primary key for '$table'");
131 8
        }
132
133
        return $tableDetails->getPrimaryKey()->getColumns()[0];
134
    }
135
136
137
    /**
138
     * Identify the primary key for a table
139
     *
140
     * @param string $table
141
     *
142 5
     * @return array $cols
143
     */
144 5
    protected function getTableCols(string $table)
145 5
    {
146
        $schema = $this->conn->getSchemaManager();
147
        $tableCols = $schema->listTableColumns($table);
148 5
        $cols = [];
149 5
        foreach ($tableCols as $col) {
150 5
            $cols[$col->getName()] = [
151
                'length' => $col->getLength(),
152
            ];
153
        }
154 5
155 1
        return $cols;
156 1
    }
157 1
158
159 4
    /**
160
     * Execute the Update with PDO
161
     *
162
     * @param  string $table      Name of the table
163
     * @param  array  $data       Array of fields => value to update the table
164
     * @param  string $primaryKey
165
     * @param  string $val        Primary Key's Value
166
     * @return string             Doctrine DBAL QueryBuilder
167
     */
168
    private function prepareUpdate(string $table, array $data, string $primaryKey, $val)
169
    {
170 5
        $queryBuilder = $this->conn->createQueryBuilder();
171
        $queryBuilder = $queryBuilder->update($table);
172 5
        foreach ($data as $field => $value) {
173 5
            $condition = "(CASE $field WHEN NULL THEN NULL ELSE :$field END)";
174 5
            $queryBuilder = $queryBuilder->set($field, $condition);
175
            $queryBuilder = $queryBuilder->setParameter(":$field", $value);
176 5
        }
177 5
        $queryBuilder = $queryBuilder->where("$primaryKey = :$primaryKey");
178 5
        $queryBuilder = $queryBuilder->setParameter(":$primaryKey", $val);
179
180
        return $queryBuilder;
181
    }
182
183
184
    /**
185
     * Execute the Update with PDO
186
     *
187
     * @param QueryBuilder $queryBuilder
188
     * @param string                     $table          Name of the table
189 4
     */
190
    private function runUpdate(QueryBuilder $queryBuilder, string $table)
191 4
    {
192 4
        try {
193
            $queryBuilder->execute();
194 4
        } catch (\Doctrine\DBAL\Exception\DriverException $e) {
195 1
            throw new NeuralizerException("Problem anonymizing $table (" . $e->getMessage() . ')');
196
        }
197
    }
198
199 3
200 1
    /**
201 1
     * To debug, build the final SQL (can be approximative)
202
     * @param  QueryBuilder $queryBuilder
203
     * @return string
204 2
     */
205
    private function getRawSQL(QueryBuilder $queryBuilder)
206
    {
207
        $sql = $queryBuilder->getSQL();
208
        foreach ($queryBuilder->getParameters() as $parameter => $value) {
209
            $sql = str_replace($parameter, "'$value'", $sql);
210
        }
211
212
        return $sql;
213
    }
214
215
216 6
    /**
217
     * Execute the Delete with PDO
218 6
     *
219 6
     * @param string $table
220 6
     * @param string $where
221
     * @param bool   $pretend
222
     *
223 6
     * @return string
224
     */
225 6
    private function runDelete(string $table, string $where, bool $pretend): string
226
    {
227
        $queryBuilder = $this->conn->createQueryBuilder();
228
        $queryBuilder = $queryBuilder->delete($table);
229
        if (!empty($where)) {
230
            $queryBuilder = $queryBuilder->where($where);
231
        }
232
        $sql = $queryBuilder->getSQL();
233
234
        if ($pretend === true) {
235
            return $sql;
236
        }
237
238
        try {
239
            $queryBuilder->execute();
240
        } catch (\Exception $e) {
241
            throw new NeuralizerException('Query DELETE Error (' . $e->getMessage() . ')');
242
        }
243
244
        return $sql;
245
    }
246
}
247