|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
View Code Duplication |
class DatabaseConnection |
|
|
|
|
|
|
4
|
|
|
{ |
|
5
|
|
|
/** |
|
6
|
|
|
* [$instance description] |
|
7
|
|
|
* @var [type] |
|
8
|
|
|
*/ |
|
9
|
|
|
private static $instance; |
|
10
|
|
|
|
|
11
|
|
|
/** |
|
12
|
|
|
* [$databaseConnection description] |
|
13
|
|
|
* @var [type] |
|
14
|
|
|
*/ |
|
15
|
|
|
public $databaseConnection; |
|
16
|
|
|
|
|
17
|
|
|
/** |
|
18
|
|
|
* The default PDO connection options. |
|
19
|
|
|
* |
|
20
|
|
|
* @var array |
|
21
|
|
|
*/ |
|
22
|
|
|
protected $options = [ |
|
23
|
|
|
PDO::ATTR_CASE => PDO::CASE_NATURAL, |
|
24
|
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
|
25
|
|
|
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, |
|
26
|
|
|
PDO::ATTR_STRINGIFY_FETCHES => false, |
|
27
|
|
|
PDO::ATTR_EMULATE_PREPARES => false, |
|
28
|
|
|
]; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* Singleton Class: |
|
32
|
|
|
* Constructor reads database config file and creates connection. |
|
33
|
|
|
*/ |
|
34
|
|
|
private function __construct(DatabaseConnectionStringFactoryInterface $dbConnStringFactory) |
|
35
|
|
|
{ |
|
36
|
|
|
//Read config file |
|
37
|
|
|
$this->config = parse_ini_file('config.ini'); |
|
|
|
|
|
|
38
|
|
|
$dsn = $dbConnStringFactory->createDatabaseSourceString($this->config); |
|
39
|
|
|
$this->databaseConnection = $this->createConnection($dsn, $this->options); |
|
40
|
|
|
} |
|
41
|
|
|
|
|
42
|
|
|
public static function getInstance() |
|
43
|
|
|
{ |
|
44
|
|
|
if (self::$instance === null) { |
|
45
|
|
|
self::$instance = new self(new DatabaseConnectionStringFactory); |
|
46
|
|
|
} |
|
47
|
|
|
return self::$instance; |
|
48
|
|
|
} |
|
49
|
|
|
|
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* Create a new PDO connection. |
|
53
|
|
|
* |
|
54
|
|
|
* @param string $dsn |
|
55
|
|
|
* @param array $config |
|
56
|
|
|
* @return \PDO |
|
57
|
|
|
*/ |
|
58
|
|
|
public function createConnection($dsn, array $config) |
|
|
|
|
|
|
59
|
|
|
{ |
|
60
|
|
|
$username = $this->config['USERNAME']; |
|
61
|
|
|
|
|
62
|
|
|
$password = $this->config['PASSWORD']; |
|
63
|
|
|
|
|
64
|
|
|
|
|
65
|
|
|
try { |
|
66
|
|
|
$pdo = new PDO($dsn, $username, $password, $this->options); |
|
67
|
|
|
} catch (Exception $e) { |
|
68
|
|
|
$pdo = $this->tryAgainIfCausedByLostConnection( |
|
69
|
|
|
$e, $dsn, $username, $password, $options |
|
|
|
|
|
|
70
|
|
|
); |
|
71
|
|
|
} |
|
72
|
|
|
|
|
73
|
|
|
return $pdo; |
|
74
|
|
|
} |
|
75
|
|
|
|
|
76
|
|
|
/** |
|
77
|
|
|
* Get the default PDO connection options. |
|
78
|
|
|
* |
|
79
|
|
|
* @return array |
|
80
|
|
|
*/ |
|
81
|
|
|
public function getDefaultOptions() |
|
82
|
|
|
{ |
|
83
|
|
|
return $this->options; |
|
84
|
|
|
} |
|
85
|
|
|
|
|
86
|
|
|
/** |
|
87
|
|
|
* Set the default PDO connection options. |
|
88
|
|
|
* |
|
89
|
|
|
* @param array $options |
|
90
|
|
|
* @return void |
|
91
|
|
|
*/ |
|
92
|
|
|
public function setDefaultOptions(array $options) |
|
93
|
|
|
{ |
|
94
|
|
|
$this->options = $options; |
|
95
|
|
|
} |
|
96
|
|
|
|
|
97
|
|
|
/** |
|
98
|
|
|
* Handle a exception that occurred during connect execution. |
|
99
|
|
|
* |
|
100
|
|
|
* @param \Exception $e |
|
101
|
|
|
* @param string $dsn |
|
102
|
|
|
* @param string $username |
|
103
|
|
|
* @param string $password |
|
104
|
|
|
* @param array $options |
|
105
|
|
|
* @return \PDO |
|
106
|
|
|
* |
|
107
|
|
|
* @throws \Exception |
|
108
|
|
|
*/ |
|
109
|
|
|
protected function tryAgainIfCausedByLostConnection(Exception $e, $dsn, $username, $password, $options) |
|
110
|
|
|
{ |
|
111
|
|
|
if ($this->causedByLostConnection($e)) { |
|
112
|
|
|
return new PDO($dsn, $username, $password, $options); |
|
113
|
|
|
} |
|
114
|
|
|
|
|
115
|
|
|
throw $e; |
|
116
|
|
|
} |
|
117
|
|
|
|
|
118
|
|
|
/** |
|
119
|
|
|
* Determine if the given exception was caused by a lost connection. |
|
120
|
|
|
* |
|
121
|
|
|
* @param \Exception $e |
|
122
|
|
|
* @return bool |
|
123
|
|
|
*/ |
|
124
|
|
|
protected function causedByLostConnection(Exception $e) |
|
125
|
|
|
{ |
|
126
|
|
|
$message = $e->getMessage(); |
|
127
|
|
|
|
|
128
|
|
|
return Helpers::contains($message, [ |
|
129
|
|
|
'server has gone away', |
|
130
|
|
|
'no connection to the server', |
|
131
|
|
|
'Lost connection', |
|
132
|
|
|
'is dead or not enabled', |
|
133
|
|
|
'Error while sending', |
|
134
|
|
|
'decryption failed or bad record mac', |
|
135
|
|
|
'SSL connection has been closed unexpectedly', |
|
136
|
|
|
'Deadlock found when trying to get lock', |
|
137
|
|
|
]); |
|
138
|
|
|
} |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
View Code Duplication |
abstract class Model implements ModelInterface |
|
|
|
|
|
|
142
|
|
|
{ |
|
143
|
|
|
|
|
144
|
|
|
|
|
145
|
|
|
protected $properties = []; |
|
146
|
|
|
|
|
147
|
|
|
/** |
|
148
|
|
|
* Store instance of database connection used. |
|
149
|
|
|
* @var [type] |
|
150
|
|
|
*/ |
|
151
|
|
|
protected $databaseConnection; |
|
152
|
|
|
|
|
153
|
|
|
public function __construct() |
|
154
|
|
|
{ |
|
155
|
|
|
$this->databaseConnection = DatabaseConnection::getInstance()->databaseConnection; |
|
156
|
|
|
//$databaseConnection->databaseConnection->connect(); |
|
|
|
|
|
|
157
|
|
|
} |
|
158
|
|
|
/** |
|
159
|
|
|
* @param string $key rep column name |
|
160
|
|
|
* @param string $val rep column value |
|
161
|
|
|
* sets into $propertie the $key => $value pairs |
|
162
|
|
|
*/ |
|
163
|
|
|
public function __set($key, $val) |
|
164
|
|
|
{ |
|
165
|
|
|
$this->properties[$key] = $val; |
|
166
|
|
|
} |
|
167
|
|
|
/** |
|
168
|
|
|
* @param string $key reps the column name |
|
169
|
|
|
* @return $key and $value |
|
|
|
|
|
|
170
|
|
|
*/ |
|
171
|
|
|
public function __get($key) |
|
172
|
|
|
{ |
|
173
|
|
|
return $this->properties[$key]; |
|
174
|
|
|
} |
|
175
|
|
|
/** |
|
176
|
|
|
* Get all the model properties |
|
177
|
|
|
* |
|
178
|
|
|
* @return array |
|
179
|
|
|
*/ |
|
180
|
|
|
public function getProperties() |
|
181
|
|
|
{ |
|
182
|
|
|
return $this->properties; |
|
183
|
|
|
} |
|
184
|
|
|
/** |
|
185
|
|
|
* Gets the name of the child class only |
|
186
|
|
|
* without the namespace |
|
187
|
|
|
* @var $className |
|
188
|
|
|
* @var $table |
|
189
|
|
|
* @return $table |
|
|
|
|
|
|
190
|
|
|
*/ |
|
191
|
|
|
public function getTableName() |
|
192
|
|
|
{ |
|
193
|
|
|
$className = explode('\\', get_called_class()); |
|
194
|
|
|
$table = strtolower(end($className) .'s'); |
|
195
|
|
|
return $table; |
|
196
|
|
|
} |
|
197
|
|
|
/** |
|
198
|
|
|
* returns a particular record |
|
199
|
|
|
* @param $id reps the record id |
|
200
|
|
|
* @param $connection initialised to null |
|
201
|
|
|
* @return object |
|
202
|
|
|
*/ |
|
203
|
|
|
public static function find($id) |
|
204
|
|
|
{ |
|
205
|
|
|
$model = new static; |
|
206
|
|
|
return $model->get($id); |
|
207
|
|
|
} |
|
208
|
|
|
/** |
|
209
|
|
|
* returns a particular record |
|
210
|
|
|
* @param $id reps the record id |
|
211
|
|
|
* @param $connection initialised to null |
|
212
|
|
|
* @return object |
|
213
|
|
|
*/ |
|
214
|
|
|
public function get($id) |
|
215
|
|
|
{ |
|
216
|
|
|
$sql = "SELECT * FROM {$this->getTableName()} WHERE id={$id}"; |
|
217
|
|
|
$sqlStatement = $this->databaseConnection->prepare($sql); |
|
218
|
|
|
$sqlStatement->setFetchMode(PDO::FETCH_CLASS, get_called_class()); |
|
219
|
|
|
$sqlStatement->execute(); |
|
220
|
|
|
if($sqlStatement->rowCount() < 1){ |
|
221
|
|
|
throw new ModelNotFoundException($id); |
|
222
|
|
|
} |
|
223
|
|
|
return $sqlStatement->fetch(); |
|
224
|
|
|
} |
|
225
|
|
|
|
|
226
|
|
|
public static function getAll() |
|
227
|
|
|
{ |
|
228
|
|
|
$model = new static; |
|
229
|
|
|
return $model->all(); |
|
230
|
|
|
} |
|
231
|
|
|
|
|
232
|
|
|
public function all() |
|
233
|
|
|
{ |
|
234
|
|
|
$sql = "SELECT * FROM {$this->getTableName()}"; |
|
235
|
|
|
$row = $this->databaseConnection->prepare($sql); |
|
236
|
|
|
$row->execute(); |
|
237
|
|
|
|
|
238
|
|
|
return $row->fetchAll($this->databaseConnection::FETCH_CLASS); |
|
239
|
|
|
|
|
240
|
|
|
} |
|
241
|
|
|
/** update table with instance properties |
|
242
|
|
|
* |
|
243
|
|
|
*/ |
|
244
|
|
|
private function update() |
|
245
|
|
|
{ |
|
246
|
|
|
$connection = $this->getConnection(); |
|
247
|
|
|
$columnNames = ""; |
|
|
|
|
|
|
248
|
|
|
$columnValues = ""; |
|
|
|
|
|
|
249
|
|
|
$count = 0; |
|
250
|
|
|
$update = "UPDATE " . $this->getTableName() . " SET " ; |
|
251
|
|
|
foreach ($this->properties as $key => $val) { |
|
252
|
|
|
$count++; |
|
253
|
|
|
if(($key == 'id')) continue; |
|
254
|
|
|
$update .= "$key = '$val'"; |
|
255
|
|
|
if ($count < count($this->properties) ) |
|
256
|
|
|
{ |
|
257
|
|
|
$update .=","; |
|
258
|
|
|
} |
|
259
|
|
|
} |
|
260
|
|
|
$update .= " WHERE id = " . $this->properties['id']; |
|
261
|
|
|
$stmt = $connection->prepare($update); |
|
262
|
|
|
foreach ($this->properties as $key => $val) { |
|
263
|
|
|
if($key == 'id') continue; |
|
264
|
|
|
} |
|
265
|
|
|
$stmt->execute(); |
|
266
|
|
|
return $stmt->rowCount(); |
|
267
|
|
|
} |
|
268
|
|
|
/** |
|
269
|
|
|
* insert instance data into the table |
|
270
|
|
|
*/ |
|
271
|
|
|
private function create() |
|
272
|
|
|
{ |
|
273
|
|
|
$connection = $this->getConnection(); |
|
274
|
|
|
$columnNames = ""; |
|
275
|
|
|
$columnValues = ""; |
|
276
|
|
|
$count = 0; |
|
277
|
|
|
$create = "INSERT" . " INTO " . $this->getTableName()." ("; |
|
278
|
|
|
foreach ($this->properties as $key => $val) { |
|
279
|
|
|
$columnNames .= $key; |
|
280
|
|
|
$columnValues .= ':' . $key; |
|
281
|
|
|
$count++; |
|
282
|
|
|
if ($count < count($this->properties)) |
|
283
|
|
|
{ |
|
284
|
|
|
$columnNames .= ', '; |
|
285
|
|
|
$columnValues .= ', '; |
|
286
|
|
|
} |
|
287
|
|
|
} |
|
288
|
|
|
$create .= $columnNames.') VALUES (' .$columnValues.')'; |
|
289
|
|
|
$stmt = $connection->prepare($create); |
|
290
|
|
|
foreach ($this->properties as $key => $val) { |
|
291
|
|
|
$stmt->bindValue(':'.$key, $val); |
|
292
|
|
|
} |
|
293
|
|
|
try { |
|
294
|
|
|
// if prop returned and props from db differ throw exception |
|
295
|
|
|
$stmt->execute(); |
|
296
|
|
|
} catch(PDOException $e){ |
|
297
|
|
|
return $e->getExceptionMessage(); |
|
298
|
|
|
} |
|
299
|
|
|
return $stmt->rowCount(); |
|
300
|
|
|
} |
|
301
|
|
|
/** |
|
302
|
|
|
* get db connection |
|
303
|
|
|
*/ |
|
304
|
|
|
public function getConnection($connection = null) |
|
305
|
|
|
{ |
|
306
|
|
|
if(is_null($connection)) |
|
307
|
|
|
{ |
|
308
|
|
|
return new Connection(); |
|
309
|
|
|
} |
|
310
|
|
|
} |
|
311
|
|
|
/** |
|
312
|
|
|
* checks if the id exists |
|
313
|
|
|
* update if exist |
|
314
|
|
|
* create if not exist |
|
315
|
|
|
*/ |
|
316
|
|
|
public function save() |
|
317
|
|
|
{ |
|
318
|
|
|
if ($this->id) { |
|
|
|
|
|
|
319
|
|
|
$this->update(); |
|
320
|
|
|
} else { |
|
321
|
|
|
$this->create(); |
|
322
|
|
|
} |
|
323
|
|
|
} |
|
324
|
|
|
/** |
|
325
|
|
|
* @param row reps record id |
|
326
|
|
|
* @param $connection initialised to null |
|
327
|
|
|
* @return boolean |
|
328
|
|
|
*/ |
|
329
|
|
|
public static function destroy($id) |
|
330
|
|
|
{ |
|
331
|
|
|
|
|
332
|
|
|
$sql = "DELETE" . " FROM " . self::getTableName()." WHERE id = ". $id; |
|
333
|
|
|
$delete = $connection->prepare($sql); |
|
|
|
|
|
|
334
|
|
|
$delete->execute(); |
|
335
|
|
|
$count = $delete->rowCount(); |
|
336
|
|
|
if ($count < 1) { |
|
337
|
|
|
throw new RecordNotFoundException('Record with id ' . $id . ' does not exist.'); |
|
338
|
|
|
} |
|
339
|
|
|
return ($count > 0) ? true : false; |
|
340
|
|
|
} |
|
341
|
|
|
|
|
342
|
|
|
/** |
|
343
|
|
|
* Handle dynamic static method calls into the method. |
|
344
|
|
|
* |
|
345
|
|
|
* @param string $method |
|
346
|
|
|
* @param array $parameters |
|
347
|
|
|
* @return mixed |
|
348
|
|
|
*/ |
|
349
|
|
|
public static function __callStatic($method, $parameters) |
|
|
|
|
|
|
350
|
|
|
{ |
|
351
|
|
|
exit("HERE"); |
|
352
|
|
|
$instance = new static; |
|
|
|
|
|
|
353
|
|
|
|
|
354
|
|
|
return call_user_func_array([$instance, $method], $parameters); |
|
355
|
|
|
} |
|
356
|
|
|
} |
|
357
|
|
|
|
|
358
|
|
|
interface ModelInterface { |
|
359
|
|
|
/** |
|
360
|
|
|
* Get all models from database. |
|
361
|
|
|
* |
|
362
|
|
|
* @return array |
|
363
|
|
|
*/ |
|
364
|
|
|
static function getAll(); |
|
365
|
|
|
|
|
366
|
|
|
/** |
|
367
|
|
|
* Find model with the specified id. |
|
368
|
|
|
*/ |
|
369
|
|
|
static function find($id); |
|
370
|
|
|
|
|
371
|
|
|
/** |
|
372
|
|
|
* Delete model with the specified id. |
|
373
|
|
|
* |
|
374
|
|
|
*/ |
|
375
|
|
|
static function destroy($id); |
|
376
|
|
|
} |
|
377
|
|
|
|
|
378
|
|
View Code Duplication |
class DatabaseConnectionStringFactory implements DatabaseConnectionStringFactoryInterface |
|
|
|
|
|
|
379
|
|
|
{ |
|
380
|
|
|
/** |
|
381
|
|
|
* Create a connection string |
|
382
|
|
|
* |
|
383
|
|
|
* @param array $config |
|
384
|
|
|
* @throws Pyjac\PotatoORM\Exception\DatabaseDriverNotSupportedException |
|
385
|
|
|
* @return string |
|
386
|
|
|
*/ |
|
387
|
|
|
public function createDatabaseSourceString($config) |
|
388
|
|
|
{ |
|
389
|
|
|
|
|
390
|
|
|
$driver = $config['DRIVER']; |
|
391
|
|
|
|
|
392
|
|
|
switch ($driver) { |
|
393
|
|
|
case 'sqlite': |
|
394
|
|
|
$dsn = $driver.'::memory:'; |
|
395
|
|
|
break; |
|
396
|
|
|
case 'mysql': |
|
397
|
|
|
case 'postgres': |
|
398
|
|
|
if(strcasecmp($driver, 'postgres') == 0) $driver="pgsql"; |
|
399
|
|
|
$dsn = $driver.':host='.$config['HOSTNAME'].';dbname='.$config['DBNAME']; |
|
400
|
|
|
if(isset($config['PORT'])) $dsn .= ';port='.$config['PORT']; |
|
401
|
|
|
break; |
|
402
|
|
|
default: |
|
403
|
|
|
throw new DatabaseDriverNotSupportedException; |
|
404
|
|
|
} |
|
405
|
|
|
return $dsn; |
|
406
|
|
|
} |
|
407
|
|
|
} |
|
408
|
|
|
|
|
409
|
|
|
interface DatabaseConnectionStringFactoryInterface |
|
410
|
|
|
{ |
|
411
|
|
|
/** |
|
412
|
|
|
* Create a connection string |
|
413
|
|
|
* |
|
414
|
|
|
* @param array $config |
|
415
|
|
|
* @throws Pyjac\PotatoORM\Exception\DatabaseDriverNotSupportedException |
|
416
|
|
|
* @return string |
|
417
|
|
|
*/ |
|
418
|
|
|
public function createDatabaseSourceString($config); |
|
419
|
|
|
} |
|
420
|
|
|
|
|
421
|
|
|
|
|
422
|
|
|
|
|
423
|
|
|
class DatabaseDriverNotSupportedException extends Exception |
|
424
|
|
|
{ |
|
425
|
|
|
|
|
426
|
|
|
function __construct() |
|
|
|
|
|
|
427
|
|
|
{ |
|
428
|
|
|
parent::__construct("Database driver not supported."); |
|
429
|
|
|
} |
|
430
|
|
|
} |
|
431
|
|
|
|
|
432
|
|
|
class ModelNotFoundException extends Exception |
|
433
|
|
|
{ |
|
434
|
|
|
|
|
435
|
|
|
function __construct($id) |
|
|
|
|
|
|
436
|
|
|
{ |
|
437
|
|
|
parent::__construct('The requested Model with ' . $id . ' does not exist'); |
|
438
|
|
|
} |
|
439
|
|
|
} |
|
440
|
|
|
|
|
441
|
|
View Code Duplication |
class Helpers |
|
|
|
|
|
|
442
|
|
|
{ |
|
443
|
|
|
|
|
444
|
|
|
/** |
|
445
|
|
|
* Determine if a given string contains a given substring. |
|
446
|
|
|
* |
|
447
|
|
|
* @param string $haystack |
|
448
|
|
|
* @param string|array $needles |
|
449
|
|
|
* @return bool |
|
450
|
|
|
*/ |
|
451
|
|
|
public static function contains($haystack, $needles) |
|
452
|
|
|
{ |
|
453
|
|
|
foreach ((array) $needles as $needle) { |
|
454
|
|
|
if ($needle != '' && strpos($haystack, $needle) !== false) { |
|
455
|
|
|
return true; |
|
456
|
|
|
} |
|
457
|
|
|
} |
|
458
|
|
|
|
|
459
|
|
|
return false; |
|
460
|
|
|
} |
|
461
|
|
|
} |
|
462
|
|
|
|
|
463
|
|
|
class User extends Model { |
|
|
|
|
|
|
464
|
|
|
|
|
465
|
|
|
} |
|
466
|
|
|
|
|
467
|
|
|
$users = User::getAll(); |
|
468
|
|
|
var_dump($users); |
|
|
|
|
|
|
469
|
|
|
var_dump(User::find(1)); |
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.