|
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 |
|
$name = $columnDefinition['column_name']; |
|
300
|
85 |
|
$hasDefault = $columnDefinition['column_default'] !== null; |
|
301
|
85 |
|
$isNullable = $columnDefinition['is_nullable']; |
|
302
|
85 |
|
return new Column($name, $type, $hasDefault, $isNullable); |
|
303
|
|
|
} |
|
304
|
|
|
} |
|
305
|
|
|
|
This check looks for
foreachloops that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.Consider removing the loop.