|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* @author Jared King <[email protected]> |
|
5
|
|
|
* |
|
6
|
|
|
* @see http://jaredtking.com |
|
7
|
|
|
* |
|
8
|
|
|
* @copyright 2015 Jared King |
|
9
|
|
|
* @license MIT |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
namespace Pulsar\Driver; |
|
13
|
|
|
|
|
14
|
|
|
use ICanBoogie\Inflector; |
|
15
|
|
|
use PDOException; |
|
16
|
|
|
use PDOStatement; |
|
17
|
|
|
use Pimple\Container; |
|
18
|
|
|
use Pulsar\Exception\DriverException; |
|
19
|
|
|
use Pulsar\Model; |
|
20
|
|
|
use Pulsar\Property; |
|
21
|
|
|
use Pulsar\Query; |
|
22
|
|
|
|
|
23
|
|
|
/** |
|
24
|
|
|
* Class DatabaseDriver. |
|
25
|
|
|
*/ |
|
26
|
|
|
class DatabaseDriver implements DriverInterface |
|
27
|
|
|
{ |
|
28
|
|
|
/** |
|
29
|
|
|
* @var Container |
|
30
|
|
|
*/ |
|
31
|
|
|
private $app; |
|
32
|
|
|
|
|
33
|
|
|
/** |
|
34
|
|
|
* @param Container $app |
|
35
|
|
|
*/ |
|
36
|
|
|
public function __construct(Container $app = null) |
|
37
|
|
|
{ |
|
38
|
|
|
$this->app = $app; |
|
39
|
|
|
} |
|
40
|
|
|
|
|
41
|
|
View Code Duplication |
public function createModel(Model $model, array $parameters) |
|
|
|
|
|
|
42
|
|
|
{ |
|
43
|
|
|
$values = $this->serialize($parameters); |
|
44
|
|
|
$tablename = $this->getTablename($model); |
|
45
|
|
|
$db = $this->app['db']; |
|
46
|
|
|
|
|
47
|
|
|
try { |
|
48
|
|
|
return $db->insert($values) |
|
49
|
|
|
->into($tablename) |
|
50
|
|
|
->execute() instanceof PDOStatement; |
|
51
|
|
|
} catch (PDOException $original) { |
|
52
|
|
|
$e = new DriverException('An error occurred in the database driver when creating the '.$model::modelName().': '.$original->getMessage()); |
|
53
|
|
|
$e->setException($original); |
|
54
|
|
|
throw $e; |
|
55
|
|
|
} |
|
56
|
|
|
} |
|
57
|
|
|
|
|
58
|
|
|
public function getCreatedID(Model $model, $propertyName) |
|
59
|
|
|
{ |
|
60
|
|
|
try { |
|
61
|
|
|
$id = $this->app['db']->getPDO()->lastInsertId(); |
|
62
|
|
|
} catch (PDOException $original) { |
|
63
|
|
|
$e = new DriverException('An error occurred in the database driver when getting the ID of the new '.$model::modelName().': '.$original->getMessage()); |
|
64
|
|
|
$e->setException($original); |
|
65
|
|
|
throw $e; |
|
66
|
|
|
} |
|
67
|
|
|
|
|
68
|
|
|
return $this->unserializeValue($model::getProperty($propertyName), $id); |
|
|
|
|
|
|
69
|
|
|
} |
|
70
|
|
|
|
|
71
|
|
View Code Duplication |
public function loadModel(Model $model) |
|
|
|
|
|
|
72
|
|
|
{ |
|
73
|
|
|
$tablename = $this->getTablename($model); |
|
74
|
|
|
$db = $this->app['db']; |
|
75
|
|
|
|
|
76
|
|
|
try { |
|
77
|
|
|
$row = $db->select('*') |
|
78
|
|
|
->from($tablename) |
|
79
|
|
|
->where($model->ids()) |
|
80
|
|
|
->one(); |
|
81
|
|
|
} catch (PDOException $original) { |
|
82
|
|
|
$e = new DriverException('An error occurred in the database driver when loading an instance of '.$model::modelName().': '.$original->getMessage()); |
|
83
|
|
|
$e->setException($original); |
|
84
|
|
|
throw $e; |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
|
|
if (!is_array($row)) { |
|
88
|
|
|
return false; |
|
89
|
|
|
} |
|
90
|
|
|
|
|
91
|
|
|
return $this->unserialize($row, $model::getProperties()); |
|
92
|
|
|
} |
|
93
|
|
|
|
|
94
|
|
View Code Duplication |
public function updateModel(Model $model, array $parameters) |
|
|
|
|
|
|
95
|
|
|
{ |
|
96
|
|
|
if (count($parameters) == 0) { |
|
97
|
|
|
return true; |
|
98
|
|
|
} |
|
99
|
|
|
|
|
100
|
|
|
$values = $this->serialize($parameters); |
|
101
|
|
|
$tablename = $this->getTablename($model); |
|
102
|
|
|
$db = $this->app['db']; |
|
103
|
|
|
|
|
104
|
|
|
try { |
|
105
|
|
|
return $db->update($tablename) |
|
106
|
|
|
->values($values) |
|
107
|
|
|
->where($model->ids()) |
|
108
|
|
|
->execute() instanceof PDOStatement; |
|
109
|
|
|
} catch (PDOException $original) { |
|
110
|
|
|
$e = new DriverException('An error occurred in the database driver when updating the '.$model::modelName().': '.$original->getMessage()); |
|
111
|
|
|
$e->setException($original); |
|
112
|
|
|
throw $e; |
|
113
|
|
|
} |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
|
View Code Duplication |
public function deleteModel(Model $model) |
|
|
|
|
|
|
117
|
|
|
{ |
|
118
|
|
|
$tablename = $this->getTablename($model); |
|
119
|
|
|
$db = $this->app['db']; |
|
120
|
|
|
|
|
121
|
|
|
try { |
|
122
|
|
|
return $db->delete($tablename) |
|
123
|
|
|
->where($model->ids()) |
|
124
|
|
|
->execute() instanceof PDOStatement; |
|
125
|
|
|
} catch (PDOException $original) { |
|
126
|
|
|
$e = new DriverException('An error occurred in the database driver while deleting the '.$model::modelName().': '.$original->getMessage()); |
|
127
|
|
|
$e->setException($original); |
|
128
|
|
|
throw $e; |
|
129
|
|
|
} |
|
130
|
|
|
} |
|
131
|
|
|
|
|
132
|
|
|
public function queryModels(Query $query) |
|
133
|
|
|
{ |
|
134
|
|
|
$model = $query->getModel(); |
|
135
|
|
|
$tablename = $this->getTablename($model); |
|
136
|
|
|
|
|
137
|
|
|
// build a DB query from the model query |
|
138
|
|
|
$dbQuery = $this->app['db'] |
|
139
|
|
|
->select($this->prefixSelect('*', $tablename)) |
|
140
|
|
|
->from($tablename) |
|
141
|
|
|
->where($this->prefixWhere($query->getWhere(), $tablename)) |
|
142
|
|
|
->limit($query->getLimit(), $query->getStart()) |
|
143
|
|
|
->orderBy($this->prefixSort($query->getSort(), $tablename)); |
|
144
|
|
|
|
|
145
|
|
|
// join conditions |
|
146
|
|
|
foreach ($query->getJoins() as $join) { |
|
147
|
|
|
list($foreignModel, $column, $foreignKey) = $join; |
|
148
|
|
|
|
|
149
|
|
|
$foreignTablename = $this->getTablename($foreignModel); |
|
150
|
|
|
$condition = $this->prefixColumn($column, $tablename).'='.$this->prefixColumn($foreignKey, $foreignTablename); |
|
151
|
|
|
|
|
152
|
|
|
$dbQuery->join($foreignTablename, $condition); |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
try { |
|
156
|
|
|
$data = $dbQuery->all(); |
|
157
|
|
|
} catch (PDOException $original) { |
|
158
|
|
|
$e = new DriverException('An error occurred in the database driver while performing the '.$model::modelName().' query: '.$original->getMessage()); |
|
159
|
|
|
$e->setException($original); |
|
160
|
|
|
throw $e; |
|
161
|
|
|
} |
|
162
|
|
|
|
|
163
|
|
|
$properties = $model::getProperties(); |
|
164
|
|
|
foreach ($data as &$row) { |
|
165
|
|
|
$row = $this->unserialize($row, $properties); |
|
166
|
|
|
} |
|
167
|
|
|
|
|
168
|
|
|
return $data; |
|
169
|
|
|
} |
|
170
|
|
|
|
|
171
|
|
View Code Duplication |
public function totalRecords(Query $query) |
|
|
|
|
|
|
172
|
|
|
{ |
|
173
|
|
|
$model = $query->getModel(); |
|
174
|
|
|
$tablename = $this->getTablename($model); |
|
175
|
|
|
$db = $this->app['db']; |
|
176
|
|
|
|
|
177
|
|
|
try { |
|
178
|
|
|
return (int) $db->select('count(*)') |
|
179
|
|
|
->from($tablename) |
|
180
|
|
|
->where($query->getWhere()) |
|
181
|
|
|
->scalar(); |
|
182
|
|
|
} catch (PDOException $original) { |
|
183
|
|
|
$e = new DriverException('An error occurred in the database driver while getting the number of '.$model::modelName().' objects: '.$original->getMessage()); |
|
184
|
|
|
$e->setException($original); |
|
185
|
|
|
throw $e; |
|
186
|
|
|
} |
|
187
|
|
|
} |
|
188
|
|
|
|
|
189
|
|
|
/** |
|
190
|
|
|
* Generates the tablename for the model. |
|
191
|
|
|
* |
|
192
|
|
|
* @param string|Model $model |
|
193
|
|
|
* |
|
194
|
|
|
* @return string |
|
195
|
|
|
*/ |
|
196
|
|
|
public function getTablename($model) |
|
197
|
|
|
{ |
|
198
|
|
|
$inflector = Inflector::get(); |
|
199
|
|
|
|
|
200
|
|
|
return $inflector->camelize($inflector->pluralize($model::modelName())); |
|
201
|
|
|
} |
|
202
|
|
|
|
|
203
|
|
|
/** |
|
204
|
|
|
* Marshals a value to storage. |
|
205
|
|
|
* |
|
206
|
|
|
* @param mixed $value |
|
207
|
|
|
* |
|
208
|
|
|
* @return mixed serialized value |
|
209
|
|
|
*/ |
|
210
|
|
|
public function serializeValue($value) |
|
211
|
|
|
{ |
|
212
|
|
|
// encode arrays/objects as JSON |
|
213
|
|
|
if (is_array($value) || is_object($value)) { |
|
214
|
|
|
return json_encode($value); |
|
215
|
|
|
} |
|
216
|
|
|
|
|
217
|
|
|
return $value; |
|
218
|
|
|
} |
|
219
|
|
|
|
|
220
|
|
|
/** |
|
221
|
|
|
* Marshals a value for a given property from storage. |
|
222
|
|
|
* |
|
223
|
|
|
* @param array $property |
|
224
|
|
|
* @param mixed $value |
|
225
|
|
|
* |
|
226
|
|
|
* @return mixed unserialized value |
|
227
|
|
|
*/ |
|
228
|
|
|
public function unserializeValue(array $property, $value) |
|
229
|
|
|
{ |
|
230
|
|
|
if ($value === null) { |
|
231
|
|
|
return; |
|
232
|
|
|
} |
|
233
|
|
|
|
|
234
|
|
|
// handle empty strings as null |
|
235
|
|
|
if ($property['null'] && $value == '') { |
|
236
|
|
|
return; |
|
237
|
|
|
} |
|
238
|
|
|
|
|
239
|
|
|
$type = array_value($property, 'type'); |
|
240
|
|
|
$m = 'to_'.$type; |
|
241
|
|
|
|
|
242
|
|
|
if (!method_exists(Property::class, $m)) { |
|
243
|
|
|
return $value; |
|
244
|
|
|
} |
|
245
|
|
|
|
|
246
|
|
|
return Property::$m($value); |
|
247
|
|
|
} |
|
248
|
|
|
|
|
249
|
|
|
/** |
|
250
|
|
|
* Serializes an array of values. |
|
251
|
|
|
* |
|
252
|
|
|
* @param array $values |
|
253
|
|
|
* |
|
254
|
|
|
* @return array |
|
255
|
|
|
*/ |
|
256
|
|
|
private function serialize(array $values) |
|
257
|
|
|
{ |
|
258
|
|
|
foreach ($values as &$value) { |
|
259
|
|
|
$value = $this->serializeValue($value); |
|
260
|
|
|
} |
|
261
|
|
|
|
|
262
|
|
|
return $values; |
|
263
|
|
|
} |
|
264
|
|
|
|
|
265
|
|
|
/** |
|
266
|
|
|
* Unserializes an array of values. |
|
267
|
|
|
* |
|
268
|
|
|
* @param array $values |
|
269
|
|
|
* @param array $properties model properties |
|
270
|
|
|
* |
|
271
|
|
|
* @return array |
|
272
|
|
|
*/ |
|
273
|
|
|
private function unserialize(array $values, array $properties) |
|
274
|
|
|
{ |
|
275
|
|
|
foreach ($values as $k => &$value) { |
|
276
|
|
|
if (isset($properties[$k])) { |
|
277
|
|
|
$value = $this->unserializeValue($properties[$k], $value); |
|
278
|
|
|
} |
|
279
|
|
|
} |
|
280
|
|
|
|
|
281
|
|
|
return $values; |
|
282
|
|
|
} |
|
283
|
|
|
|
|
284
|
|
|
/** |
|
285
|
|
|
* Returns a prefixed select statement. |
|
286
|
|
|
* |
|
287
|
|
|
* @param string $columns |
|
288
|
|
|
* @param string $tablename |
|
289
|
|
|
* |
|
290
|
|
|
* @return string |
|
291
|
|
|
*/ |
|
292
|
|
|
private function prefixSelect($columns, $tablename) |
|
293
|
|
|
{ |
|
294
|
|
|
$prefixed = []; |
|
295
|
|
|
foreach (explode(',', $columns) as $column) { |
|
296
|
|
|
$prefixed[] = $this->prefixColumn($column, $tablename); |
|
297
|
|
|
} |
|
298
|
|
|
|
|
299
|
|
|
return implode(',', $prefixed); |
|
300
|
|
|
} |
|
301
|
|
|
|
|
302
|
|
|
/** |
|
303
|
|
|
* Returns a prefixed where statement. |
|
304
|
|
|
* |
|
305
|
|
|
* @param string $columns |
|
|
|
|
|
|
306
|
|
|
* @param string $tablename |
|
307
|
|
|
* |
|
308
|
|
|
* @return array |
|
309
|
|
|
*/ |
|
310
|
|
|
private function prefixWhere(array $where, $tablename) |
|
311
|
|
|
{ |
|
312
|
|
|
$return = []; |
|
313
|
|
|
foreach ($where as $key => $condition) { |
|
314
|
|
|
// handles $where[property] = value |
|
315
|
|
|
if (!is_numeric($key)) { |
|
316
|
|
|
$return[$this->prefixColumn($key, $tablename)] = $condition; |
|
317
|
|
|
// handles $where[] = [property, value, '='] |
|
|
|
|
|
|
318
|
|
|
} elseif (is_array($condition)) { |
|
319
|
|
|
$condition[0] = $this->prefixColumn($condition[0], $tablename); |
|
320
|
|
|
$return[] = $condition; |
|
321
|
|
|
// handles raw SQL - do nothing |
|
322
|
|
|
} else { |
|
323
|
|
|
$return[] = $condition; |
|
324
|
|
|
} |
|
325
|
|
|
} |
|
326
|
|
|
|
|
327
|
|
|
return $return; |
|
328
|
|
|
} |
|
329
|
|
|
|
|
330
|
|
|
/** |
|
331
|
|
|
* Returns a prefixed sort statement. |
|
332
|
|
|
* |
|
333
|
|
|
* @param string $columns |
|
|
|
|
|
|
334
|
|
|
* @param string $tablename |
|
335
|
|
|
* |
|
336
|
|
|
* @return array |
|
337
|
|
|
*/ |
|
338
|
|
|
private function prefixSort(array $sort, $tablename) |
|
339
|
|
|
{ |
|
340
|
|
|
foreach ($sort as &$condition) { |
|
341
|
|
|
$condition[0] = $this->prefixColumn($condition[0], $tablename); |
|
342
|
|
|
} |
|
343
|
|
|
|
|
344
|
|
|
return $sort; |
|
345
|
|
|
} |
|
346
|
|
|
|
|
347
|
|
|
/** |
|
348
|
|
|
* Prefix columns with tablename that contains only |
|
349
|
|
|
* alphanumeric/underscores/*. |
|
350
|
|
|
* |
|
351
|
|
|
* @param string $column |
|
352
|
|
|
* @param string $tablename |
|
353
|
|
|
* |
|
354
|
|
|
* @return string prefixed column |
|
355
|
|
|
*/ |
|
356
|
|
|
private function prefixColumn($column, $tablename) |
|
357
|
|
|
{ |
|
358
|
|
|
if ($column === '*' || preg_match('/^[a-z0-9_]+$/i', $column)) { |
|
359
|
|
|
return "$tablename.$column"; |
|
360
|
|
|
} |
|
361
|
|
|
|
|
362
|
|
|
return $column; |
|
363
|
|
|
} |
|
364
|
|
|
} |
|
365
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.