|
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 |
|
return (static::$typeMapping[$columnDefinition['data_type']])::factory($columnDefinition); |
|
275
|
|
|
} else { |
|
276
|
|
|
// foreach (self::$registeredTypes as $class) { |
|
|
|
|
|
|
277
|
|
|
// if ($type = $class::fromDefinition($columnDefinition)) { |
|
278
|
|
|
// return $type; |
|
279
|
|
|
// } |
|
280
|
|
|
// } |
|
281
|
|
|
|
|
282
|
9 |
|
return new Type\Text(); |
|
283
|
|
|
} |
|
284
|
|
|
} |
|
285
|
|
|
} |
|
286
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.