|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace ORM; |
|
4
|
|
|
|
|
5
|
|
|
use ORM\Dbal\Column; |
|
6
|
|
|
use ORM\Dbal\Type; |
|
7
|
|
|
use ORM\Exceptions\NotScalar; |
|
8
|
|
|
use ORM\Exceptions\UnsupportedDriver; |
|
9
|
|
|
|
|
10
|
|
|
/** |
|
11
|
|
|
* Class Dbal |
|
12
|
|
|
* |
|
13
|
|
|
* This is the base class for the database abstraction layer. |
|
14
|
|
|
* |
|
15
|
|
|
* @package ORM |
|
16
|
|
|
* @author Thomas Flori <[email protected]> |
|
17
|
|
|
*/ |
|
18
|
|
|
abstract class Dbal |
|
19
|
|
|
{ |
|
20
|
|
|
/** @var EntityManager */ |
|
21
|
|
|
protected $em; |
|
22
|
|
|
|
|
23
|
|
|
protected static $quotingCharacter = '"'; |
|
24
|
|
|
protected static $identifierDivider = '.'; |
|
25
|
|
|
protected static $booleanTrue = '1'; |
|
26
|
|
|
protected static $booleanFalse = '0'; |
|
27
|
|
|
|
|
28
|
|
|
protected static $registeredTypes = []; |
|
29
|
|
|
protected static $typeMapping = []; |
|
30
|
|
|
|
|
31
|
17 |
|
public static function setQuotingCharacter($char) |
|
32
|
|
|
{ |
|
33
|
17 |
|
static::$quotingCharacter = $char; |
|
34
|
17 |
|
} |
|
35
|
|
|
|
|
36
|
17 |
|
public static function setIdentifierDivider($divider) |
|
37
|
|
|
{ |
|
38
|
17 |
|
static::$identifierDivider = $divider; |
|
39
|
17 |
|
} |
|
40
|
|
|
|
|
41
|
17 |
|
public static function setBooleanTrue($true) |
|
42
|
|
|
{ |
|
43
|
17 |
|
static::$booleanTrue = $true; |
|
44
|
17 |
|
} |
|
45
|
|
|
|
|
46
|
17 |
|
public static function setBooleanFalse($false) |
|
47
|
|
|
{ |
|
48
|
17 |
|
static::$booleanFalse = $false; |
|
49
|
17 |
|
} |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* @return string |
|
53
|
|
|
*/ |
|
54
|
1 |
|
public static function getQuotingCharacter() |
|
55
|
|
|
{ |
|
56
|
1 |
|
return static::$quotingCharacter; |
|
57
|
|
|
} |
|
58
|
|
|
|
|
59
|
|
|
/** |
|
60
|
|
|
* @return string |
|
61
|
|
|
*/ |
|
62
|
1 |
|
public static function getIdentifierDivider() |
|
63
|
|
|
{ |
|
64
|
1 |
|
return static::$identifierDivider; |
|
65
|
|
|
} |
|
66
|
|
|
|
|
67
|
|
|
/** |
|
68
|
|
|
* @return string |
|
69
|
|
|
*/ |
|
70
|
1 |
|
public static function getBooleanTrue() |
|
71
|
|
|
{ |
|
72
|
1 |
|
return static::$booleanTrue; |
|
73
|
|
|
} |
|
74
|
|
|
|
|
75
|
|
|
/** |
|
76
|
|
|
* @return string |
|
77
|
|
|
*/ |
|
78
|
1 |
|
public static function getBooleanFalse() |
|
79
|
|
|
{ |
|
80
|
1 |
|
return static::$booleanFalse; |
|
81
|
|
|
} |
|
82
|
|
|
|
|
83
|
|
|
/** |
|
84
|
|
|
* Dbal constructor. |
|
85
|
|
|
* |
|
86
|
|
|
* @param EntityManager $entityManager |
|
87
|
|
|
*/ |
|
88
|
278 |
|
public function __construct(EntityManager $entityManager) |
|
89
|
|
|
{ |
|
90
|
278 |
|
$this->em = $entityManager; |
|
91
|
278 |
|
} |
|
92
|
|
|
|
|
93
|
|
|
/** |
|
94
|
|
|
* Returns $identifier quoted for use in a sql statement |
|
95
|
|
|
* |
|
96
|
|
|
* @param string $identifier Identifier to quote |
|
97
|
|
|
* @return string |
|
98
|
|
|
*/ |
|
99
|
163 |
|
public function escapeIdentifier($identifier) |
|
100
|
|
|
{ |
|
101
|
163 |
|
$q = static::$quotingCharacter; |
|
102
|
163 |
|
$d = static::$identifierDivider; |
|
103
|
163 |
|
return $q . str_replace($d, $q . $d . $q, $identifier) . $q; |
|
104
|
|
|
} |
|
105
|
|
|
|
|
106
|
|
|
/** |
|
107
|
|
|
* Returns $value formatted to use in a sql statement. |
|
108
|
|
|
* |
|
109
|
|
|
* @param mixed $value The variable that should be returned in SQL syntax |
|
110
|
|
|
* @return string |
|
111
|
|
|
* @throws NotScalar |
|
112
|
|
|
*/ |
|
113
|
162 |
|
public function escapeValue($value) |
|
114
|
|
|
{ |
|
115
|
162 |
|
switch (strtolower(gettype($value))) { |
|
116
|
162 |
|
case 'string': |
|
117
|
119 |
|
return $this->em->getConnection()->quote($value); |
|
118
|
|
|
|
|
119
|
57 |
|
case 'integer': |
|
120
|
49 |
|
return (string) $value; |
|
121
|
|
|
|
|
122
|
8 |
|
case 'double': |
|
123
|
4 |
|
return (string) $value; |
|
124
|
|
|
|
|
125
|
4 |
|
case 'null': |
|
126
|
1 |
|
return 'NULL'; |
|
127
|
|
|
|
|
128
|
3 |
|
case 'boolean': |
|
129
|
2 |
|
return ($value) ? static::$booleanTrue : static::$booleanFalse; |
|
130
|
|
|
|
|
131
|
|
|
default: |
|
132
|
1 |
|
throw new NotScalar('$value has to be scalar data type. ' . gettype($value) . ' given'); |
|
133
|
|
|
} |
|
134
|
|
|
} |
|
135
|
|
|
|
|
136
|
|
|
/** |
|
137
|
|
|
* Describe a table |
|
138
|
|
|
* |
|
139
|
|
|
* @param $table |
|
140
|
|
|
* @return Column[] |
|
141
|
|
|
* @throws UnsupportedDriver |
|
142
|
|
|
*/ |
|
143
|
1 |
|
public function describe($table) |
|
144
|
|
|
{ |
|
145
|
1 |
|
throw new UnsupportedDriver('Not supported for this driver'); |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
/** |
|
149
|
|
|
* Inserts $entity and returns the new ID for autoincrement or true |
|
150
|
|
|
* |
|
151
|
|
|
* @param Entity $entity |
|
152
|
|
|
* @param bool $useAutoIncrement |
|
153
|
|
|
* @return bool|int |
|
154
|
|
|
* @throws UnsupportedDriver |
|
155
|
|
|
*/ |
|
156
|
3 |
|
public function insert($entity, $useAutoIncrement = true) |
|
157
|
|
|
{ |
|
158
|
3 |
|
$statement = $this->buildInsertStatement($entity); |
|
159
|
|
|
|
|
160
|
3 |
|
if ($useAutoIncrement && $entity::isAutoIncremented()) { |
|
161
|
1 |
|
throw new UnsupportedDriver('Auto incremented column for this driver is not supported'); |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
2 |
|
$this->em->getConnection()->query($statement); |
|
165
|
2 |
|
$this->em->sync($entity, true); |
|
166
|
2 |
|
return true; |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
|
|
/** |
|
170
|
|
|
* Update $entity in database |
|
171
|
|
|
* |
|
172
|
|
|
* @param Entity $entity |
|
173
|
|
|
* @return bool |
|
174
|
|
|
* @internal |
|
175
|
|
|
*/ |
|
176
|
6 |
|
public function update(Entity $entity) |
|
177
|
|
|
{ |
|
178
|
6 |
|
$data = $entity->getData(); |
|
179
|
6 |
|
$primaryKey = $entity->getPrimaryKey(); |
|
180
|
|
|
|
|
181
|
6 |
|
$where = []; |
|
182
|
6 |
|
foreach ($primaryKey as $var => $value) { |
|
183
|
6 |
|
$col = $entity::getColumnName($var); |
|
184
|
6 |
|
$where[] = $this->escapeIdentifier($col) . ' = ' . $this->escapeValue($value); |
|
185
|
6 |
|
if (isset($data[$col])) { |
|
186
|
6 |
|
unset($data[$col]); |
|
187
|
|
|
} |
|
188
|
|
|
} |
|
189
|
|
|
|
|
190
|
6 |
|
$set = []; |
|
191
|
6 |
|
foreach ($data as $col => $value) { |
|
192
|
6 |
|
$set[] = $this->escapeIdentifier($col) . ' = ' . $this->escapeValue($value); |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
6 |
|
$statement = 'UPDATE ' . $this->escapeIdentifier($entity::getTableName()) . ' ' . |
|
196
|
6 |
|
'SET ' . implode(',', $set) . ' ' . |
|
197
|
6 |
|
'WHERE ' . implode(' AND ', $where); |
|
198
|
6 |
|
$this->em->getConnection()->query($statement); |
|
199
|
|
|
|
|
200
|
3 |
|
return true; |
|
201
|
|
|
} |
|
202
|
|
|
|
|
203
|
|
|
/** |
|
204
|
|
|
* Delete $entity from database |
|
205
|
|
|
* |
|
206
|
|
|
* This method does not delete from the map - you can still receive the entity via fetch. |
|
207
|
|
|
* |
|
208
|
|
|
* @param Entity $entity |
|
209
|
|
|
* @return bool |
|
210
|
|
|
*/ |
|
211
|
6 |
|
public function delete(Entity $entity) |
|
212
|
|
|
{ |
|
213
|
6 |
|
$primaryKey = $entity->getPrimaryKey(); |
|
214
|
6 |
|
$where = []; |
|
215
|
6 |
|
foreach ($primaryKey as $var => $value) { |
|
216
|
6 |
|
$col = $entity::getColumnName($var); |
|
217
|
6 |
|
$where[] = $this->escapeIdentifier($col) . ' = ' . $this->escapeValue($value); |
|
218
|
|
|
} |
|
219
|
|
|
|
|
220
|
6 |
|
$statement = 'DELETE FROM ' . $this->escapeIdentifier($entity::getTableName()) . ' ' . |
|
221
|
6 |
|
'WHERE ' . implode(' AND ', $where); |
|
222
|
6 |
|
$this->em->getConnection()->query($statement); |
|
223
|
|
|
|
|
224
|
4 |
|
return true; |
|
225
|
|
|
} |
|
226
|
|
|
|
|
227
|
|
|
/** |
|
228
|
|
|
* Build the insert statement for $entity |
|
229
|
|
|
* |
|
230
|
|
|
* @param Entity $entity |
|
231
|
|
|
* @return string |
|
232
|
|
|
*/ |
|
233
|
11 |
|
protected function buildInsertStatement($entity) |
|
234
|
|
|
{ |
|
235
|
11 |
|
$data = $entity->getData(); |
|
236
|
|
|
|
|
237
|
|
|
$cols = array_map(function ($key) { |
|
238
|
11 |
|
return $this->escapeIdentifier($key); |
|
239
|
11 |
|
}, array_keys($data)); |
|
240
|
|
|
|
|
241
|
11 |
|
$values = array_map(function ($value) use ($entity) { |
|
242
|
11 |
|
return $this->escapeValue($value); |
|
243
|
11 |
|
}, array_values($data)); |
|
244
|
|
|
|
|
245
|
11 |
|
$statement = 'INSERT INTO ' . $this->escapeIdentifier($entity::getTableName()) . ' ' . |
|
246
|
11 |
|
'(' . implode(',', $cols) . ') VALUES (' . implode(',', $values) . ')'; |
|
247
|
|
|
|
|
248
|
11 |
|
return $statement; |
|
249
|
|
|
} |
|
250
|
|
|
|
|
251
|
61 |
|
protected function normalizeType($type) |
|
252
|
|
|
{ |
|
253
|
61 |
|
$type = strtolower($type); |
|
254
|
|
|
|
|
255
|
61 |
|
if (($p = strpos($type, '(')) !== false && $p > 0) { |
|
256
|
23 |
|
$type = substr($type, 0, $p); |
|
257
|
|
|
} |
|
258
|
|
|
|
|
259
|
61 |
|
return $type; |
|
260
|
|
|
} |
|
261
|
|
|
|
|
262
|
9 |
|
protected function extractParenthesis($type) |
|
263
|
|
|
{ |
|
264
|
9 |
|
if (preg_match('/\(([\d,]+)\)/', $type, $match)) { |
|
265
|
4 |
|
return $match[1]; |
|
266
|
|
|
} |
|
267
|
|
|
|
|
268
|
5 |
|
return null; |
|
269
|
|
|
} |
|
270
|
|
|
|
|
271
|
85 |
|
protected function getType($columnDefinition) |
|
272
|
|
|
{ |
|
273
|
85 |
|
if (isset(static::$typeMapping[$columnDefinition['data_type']])) { |
|
274
|
76 |
|
$class = static::$typeMapping[$columnDefinition['data_type']]; |
|
275
|
|
|
|
|
276
|
|
|
switch ($class) { |
|
277
|
76 |
|
case Type\VarChar::class: |
|
278
|
8 |
|
return new Type\VarChar($columnDefinition['character_maximum_length']); |
|
|
|
|
|
|
279
|
68 |
|
case Type\DateTime::class: |
|
280
|
8 |
|
return new Type\DateTime($columnDefinition['datetime_precision']); |
|
|
|
|
|
|
281
|
60 |
|
case Type\Time::class: |
|
282
|
4 |
|
return new Type\Time($columnDefinition['datetime_precision']); |
|
|
|
|
|
|
283
|
|
|
default: |
|
284
|
56 |
|
return new $class; |
|
285
|
|
|
} |
|
286
|
|
|
} else { |
|
287
|
9 |
|
foreach (self::$registeredTypes as $class) { |
|
288
|
|
|
if ($type = $class::fromDefinition($columnDefinition)) { |
|
289
|
|
|
return $type; |
|
290
|
|
|
} |
|
291
|
|
|
} |
|
292
|
|
|
|
|
293
|
9 |
|
return new Type\Text(); |
|
294
|
|
|
} |
|
295
|
|
|
} |
|
296
|
|
|
|
|
297
|
85 |
|
protected function columnFactory($columnDefinition, $type) |
|
298
|
|
|
{ |
|
299
|
85 |
|
return new Column( |
|
300
|
85 |
|
$columnDefinition['column_name'], |
|
301
|
|
|
$type, |
|
302
|
85 |
|
$columnDefinition['column_default'] !== null, |
|
303
|
85 |
|
$columnDefinition['is_nullable'] |
|
304
|
|
|
); |
|
305
|
|
|
} |
|
306
|
|
|
} |
|
307
|
|
|
|
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
@ignorePhpDoc annotation to the duplicate definition and it will be ignored.