Issues (16)

src/Utils/DBUtils.php (3 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * neuralyzer : Data Anonymization Library and CLI Tool
7
 *
8
 * PHP Version 7.2
9
 *
10
 * @author    Emmanuel Dyan
11
 * @author    Rémi Sauvat
12
 *
13
 * @copyright 2020 Emmanuel Dyan
14
 *
15
 * @package edyan/neuralyzer
16
 *
17
 * @license GNU General Public License v2.0
18
 *
19
 * @link https://github.com/edyan/neuralyzer
20
 */
21
22
namespace Edyan\Neuralyzer\Utils;
23
24
use Doctrine\DBAL\Configuration as DbalConfiguration;
25
use Doctrine\DBAL\Connection;
26
use Doctrine\DBAL\DriverManager;
27
use Doctrine\DBAL\Query\QueryBuilder;
28
use Edyan\Neuralyzer\Exception\NeuralyzerException;
29
use Edyan\Neuralyzer\Helper\DB as DBHelper;
30
31
/**
32
 * A few generic methods to help interacting with DB
33
 */
34
class DBUtils
35
{
36
    /**
37
     * Doctrine DB Adapter
38
     *
39
     * @var Connection
40
     */
41
    private $conn;
42
43
    /**
44
     * A helper for the current driver
45
     *
46
     * @var DBHelper\AbstractDBHelper
47
     */
48
    private $dbHelper;
49
50
    /**
51
     * Set the connection (dependency)
52
     *
53
     * @param array $params
54
     *
55 68
     * @throws \Doctrine\DBAL\DBALException
56
     */
57 68
    public function configure(array $params): void
58
    {
59
        $dbHelperClass = DBHelper\DriverGuesser::getDBHelper($params['driver']);
60 68
61 68
        // Set specific options
62 68
        $params['driverOptions'] = $dbHelperClass::getDriverOptions();
63
        $this->conn = DriverManager::getConnection($params, new DbalConfiguration());
64 68
        $this->conn->setFetchMode(\Doctrine\DBAL\FetchMode::ASSOCIATIVE);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Connection::setFetchMode() has been deprecated: Use one of the fetch- or iterate-related methods. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

64
        /** @scrutinizer ignore-deprecated */ $this->conn->setFetchMode(\Doctrine\DBAL\FetchMode::ASSOCIATIVE);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
65 68
66
        $this->dbHelper = new $dbHelperClass($this->conn);
67
    }
68
69
    public function getDBHelper(): DBHelper\AbstractDBHelper
70 33
    {
71
        return $this->dbHelper;
72 33
    }
73
74
    /**
75
     * Get Doctrine Connection
76
     */
77
    public function getConn(): Connection
78
    {
79
        if (empty($this->conn)) {
80 40
            $msg = 'Make sure you have called $dbUtils->configure($params) first';
81
            throw new \RuntimeException($msg);
82 40
        }
83 1
        return $this->conn;
84
    }
85 39
86
    /**
87
     * Do a simple count for a table
88
     */
89
    public function countResults(string $table): int
90
    {
91
        $queryBuilder = $this->conn->createQueryBuilder();
92
        $rows = $queryBuilder->select('COUNT(1)')->from($table)->execute();
93
94
        return (int) $rows->fetch(\Doctrine\DBAL\FetchMode::NUMERIC)[0];
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\ForwardCompatibility\Result::fetch() has been deprecated: Use fetchNumeric(), fetchAssociative() or fetchOne() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

94
        return (int) /** @scrutinizer ignore-deprecated */ $rows->fetch(\Doctrine\DBAL\FetchMode::NUMERIC)[0];

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
95 23
    }
96
97 23
    /**
98 23
     * Identify the primary key for a table
99
     *
100 21
     * @throws NeuralyzerException
101
     */
102
    public function getPrimaryKey(string $table): string
103
    {
104
        $schema = $this->conn->getSchemaManager();
105
        $tableDetails = $schema->listTableDetails($table);
106
        if ($tableDetails->hasPrimaryKey() === false) {
107
            throw new NeuralyzerException("Can't find a primary key for '{$table}'");
108
        }
109
110
        return $tableDetails->getPrimaryKey()->getColumns()[0];
111
    }
112 29
113
    /**
114 29
     * Retrieve columns list for a table with type and length
115 29
     *
116 29
     * @return array $cols
117 2
     */
118
    public function getTableCols(string $table): array
119
    {
120 27
        $schema = $this->conn->getSchemaManager();
121
        $tableCols = $schema->listTableColumns($table);
122
        $cols = [];
123
        foreach ($tableCols as $col) {
124
            $cols[$col->getName()] = [
125
                'length' => $col->getLength(),
126
                'type' => $col->getType(),
127
                'unsigned' => $col->getUnsigned(),
128
            ];
129
        }
130
131 30
        return $cols;
132
    }
133 30
134 30
    /**
135 30
     * To debug, build the final SQL (can be approximate)
136 30
     */
137 30
    public function getRawSQL(QueryBuilder $queryBuilder): string
138 30
    {
139 30
        $sql = $queryBuilder->getSQL();
140 30
        foreach ($queryBuilder->getParameters() as $parameter => $value) {
141
            $sql = str_replace($parameter, "'${value}'", $sql);
142
        }
143
144 30
        return $sql;
145
    }
146
147
    /**
148
     * Make sure a table exists
149
     *
150
     * @param  string $table [description]
151
     *
152
     * @throws NeuralyzerException
153
     */
154
    public function assertTableExists(string $table): void
155 10
    {
156
        if ($this->conn->getSchemaManager()->tablesExist([$table]) === false) {
157 10
            throw new NeuralyzerException("Table ${table} does not exist");
158 10
        }
159 9
    }
160
161
    /**
162 10
     * Build the condition by casting the value if needed
163
     *
164
     * @param  array  $fieldConf Various values about the field
165
     */
166
    public function getCondition(string $field, array $fieldConf): string
167
    {
168
        $type = ltrim(strtolower((string) $fieldConf['type']), '\\');
169
        $unsigned = $fieldConf['unsigned'];
170
171 30
        $integerCast = $this->getIntegerCast($unsigned);
172
173 30
        $condition = "(CASE ${field} WHEN NULL THEN NULL ELSE :${field} END)";
174 2
175
        $typeToCast = [
176 28
            'date' => 'DATE',
177
            'datetime' => 'DATE',
178
            'time' => 'TIME',
179
            'smallint' => $integerCast,
180
            'integer' => $integerCast,
181
            'bigint' => $integerCast,
182
            'float' => 'DECIMAL',
183
            'decimal' => 'DECIMAL',
184
        ];
185
186
        // No cast required
187 13
        if (! array_key_exists($type, $typeToCast)) {
188
            return $condition;
189 13
        }
190 13
191
        return "CAST(${condition} AS {$typeToCast[$type]})";
192 13
    }
193
194 13
    /**
195
     * Gives an empty value according to the field (example : numeric = 0)
196
     *
197 13
     * @return mixed
198 13
     */
199 13
    public function getEmptyValue($type)
200 13
    {
201 13
        $type = strtolower($type);
202 13
        $typeToValue = [
203 13
            'date' => '1970-01-01',
204 13
            'datetime' => '1970-01-01 00:00:00',
205
            'time' => '00:00:00',
206
            'smallint' => 0,
207
            'integer' => 0,
208 13
            'bigint' => 0,
209 11
            'float' => 0,
210
            'decimal' => 0,
211
        ];
212 12
213
        // Value is simply an empty string
214
        if (! array_key_exists($type, $typeToValue)) {
215
            return '';
216
        }
217
218
        return $typeToValue[$type];
219
    }
220
221
    /**
222
     * Get the right CAST for an INTEGER
223 7
     */
224
    private function getIntegerCast(bool $unsigned): string
225 7
    {
226
        $driver = $this->conn->getDriver()->getName();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Driver::getName() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

226
        $driver = /** @scrutinizer ignore-deprecated */ $this->conn->getDriver()->getName();
Loading history...
227 7
        if ($driver === 'pdo_mysql') {
228
            return $unsigned === true ? 'UNSIGNED INTEGER' : 'SIGNED INTEGER';
229
        }
230
231
        return 'INTEGER';
232
    }
233
}
234