1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* File containing an interface for the Doctrine database abstractions. |
5
|
|
|
* |
6
|
|
|
* @copyright Copyright (C) eZ Systems AS. All rights reserved. |
7
|
|
|
* @license For full copyright and license information view LICENSE file distributed with this source code. |
8
|
|
|
* |
9
|
|
|
* @version //autogentag// |
10
|
|
|
*/ |
11
|
|
|
namespace eZ\Publish\Core\Persistence\Doctrine; |
12
|
|
|
|
13
|
|
|
use eZ\Publish\Core\Persistence\Database\DatabaseHandler; |
14
|
|
|
use eZ\Publish\Core\Persistence\Database\QueryException; |
15
|
|
|
use Doctrine\DBAL\Connection; |
16
|
|
|
use Doctrine\DBAL\DriverManager; |
17
|
|
|
use Doctrine\DBAL\DBALException; |
18
|
|
|
|
19
|
|
|
class ConnectionHandler implements DatabaseHandler |
20
|
|
|
{ |
21
|
|
|
/** |
22
|
|
|
* @var \Doctrine\DBAL\Connection |
23
|
|
|
*/ |
24
|
|
|
protected $connection; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @param string|array $dsn |
28
|
|
|
* |
29
|
|
|
* @return \Doctrine\DBAL\Connection |
30
|
|
|
*/ |
31
|
|
|
public static function createConnectionFromDSN($dsn) |
32
|
|
|
{ |
33
|
|
|
if (is_string($dsn)) { |
34
|
|
|
$parsed = self::parseDSN($dsn); |
35
|
|
|
} else { |
36
|
|
|
$parsed = $dsn; |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
return DriverManager::getConnection($parsed); |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Create a Connection Handler from given Doctrine $connection. |
44
|
|
|
* |
45
|
|
|
* @param \Doctrine\DBAL\Connection $connection |
46
|
|
|
* |
47
|
|
|
* @return \eZ\Publish\Core\Persistence\Doctrine\ConnectionHandler |
48
|
|
|
*/ |
49
|
|
|
public static function createFromConnection(Connection $connection) |
50
|
|
|
{ |
51
|
|
|
$driver = $connection->getDriver()->getName(); |
52
|
|
|
|
53
|
|
|
if ($driver === 'pdo_sqlite') { |
54
|
|
|
return new ConnectionHandler\SqliteConnectionHandler($connection); |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
if ($driver === 'pdo_pgsql') { |
58
|
|
|
return new ConnectionHandler\PostgresConnectionHandler($connection); |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
return new self($connection); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Create a Connection Handler with corresponding Doctrine connection from DSN. |
66
|
|
|
* |
67
|
|
|
* @param string|array $dsn |
68
|
|
|
* |
69
|
|
|
* @return ConnectionHandler |
70
|
|
|
*/ |
71
|
|
|
public static function createFromDSN($dsn) |
72
|
|
|
{ |
73
|
|
|
if (is_string($dsn)) { |
74
|
|
|
$parsed = self::parseDSN($dsn); |
75
|
|
|
} else { |
76
|
|
|
$parsed = $dsn; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
$connection = DriverManager::getConnection($parsed); |
80
|
|
|
|
81
|
|
|
if ($parsed['driver'] === 'pdo_sqlite') { |
82
|
|
|
return new ConnectionHandler\SqliteConnectionHandler($connection); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
if ($parsed['driver'] === 'pdo_pgsql') { |
86
|
|
|
return new ConnectionHandler\PostgresConnectionHandler($connection); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
return new self($connection); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Returns the Data Source Name as a structure containing the various parts of the DSN. |
94
|
|
|
* |
95
|
|
|
* Additional keys can be added by appending a URI query string to the |
96
|
|
|
* end of the DSN. |
97
|
|
|
* |
98
|
|
|
* The format of the supplied DSN is in its fullest form: |
99
|
|
|
* <code> |
100
|
|
|
* driver://user:password@protocol+host/database?option=8&another=true |
101
|
|
|
* </code> |
102
|
|
|
* |
103
|
|
|
* Most variations are allowed: |
104
|
|
|
* <code> |
105
|
|
|
* driver://user:password@protocol+host:110//usr/db_file.db?mode=0644 |
106
|
|
|
* driver://user:password@host/dbname |
107
|
|
|
* driver://user:password@host |
108
|
|
|
* driver://user@host |
109
|
|
|
* driver://host/dbname |
110
|
|
|
* driver://host |
111
|
|
|
* driver |
112
|
|
|
* </code> |
113
|
|
|
* |
114
|
|
|
* This function is 'borrowed' from PEAR /DB.php . |
115
|
|
|
* |
116
|
|
|
* @license Apache2 from Zeta Components Database project |
117
|
|
|
* |
118
|
|
|
* @param string $dsn Data Source Name to be parsed |
119
|
|
|
* |
120
|
|
|
* @return array an associative array with the following keys: |
121
|
|
|
* + driver: Database backend used in PHP (mysql, odbc etc.) |
122
|
|
|
* + host: Host specification (hostname[:port]) |
123
|
|
|
* + dbname: Database to use on the DBMS server |
124
|
|
|
* + username: User name for login |
125
|
|
|
* + password: Password for login |
126
|
|
|
*/ |
127
|
|
|
public static function parseDSN($dsn) |
128
|
|
|
{ |
129
|
|
|
$parsed = array( |
130
|
|
|
'driver' => false, |
131
|
|
|
'user' => false, |
132
|
|
|
'password' => false, |
133
|
|
|
'host' => false, |
134
|
|
|
'port' => false, |
135
|
|
|
'unix_socket' => false, |
136
|
|
|
'dbname' => false, |
137
|
|
|
'memory' => false, |
138
|
|
|
'path' => false, |
139
|
|
|
); |
140
|
|
|
|
141
|
|
|
// Find driver and dbsyntax |
142
|
|
|
if (($pos = strpos($dsn, '://')) !== false) { |
143
|
|
|
$str = substr($dsn, 0, $pos); |
144
|
|
|
$dsn = substr($dsn, $pos + 3); |
145
|
|
|
} else { |
146
|
|
|
$str = $dsn; |
147
|
|
|
$dsn = null; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
// Get driver and dbsyntax |
151
|
|
|
// $str => driver(dbsyntax) |
152
|
|
|
if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) { |
153
|
|
|
$parsed['driver'] = $arr[1]; |
154
|
|
|
} else { |
155
|
|
|
$parsed['driver'] = $str; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
if (!count($dsn)) { |
159
|
|
|
return $parsed; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
// Get (if found): username and password |
163
|
|
|
// $dsn => username:password@protocol+host/database |
164
|
|
|
if (($at = strrpos((string)$dsn, '@')) !== false) { |
165
|
|
|
$str = substr($dsn, 0, $at); |
166
|
|
|
$dsn = substr($dsn, $at + 1); |
167
|
|
|
if (($pos = strpos($str, ':')) !== false) { |
168
|
|
|
$parsed['user'] = rawurldecode(substr($str, 0, $pos)); |
169
|
|
|
$parsed['password'] = rawurldecode(substr($str, $pos + 1)); |
170
|
|
|
} else { |
171
|
|
|
$parsed['user'] = rawurldecode($str); |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
// Find protocol and host |
176
|
|
|
|
177
|
|
|
if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) { |
178
|
|
|
// $dsn => proto(proto_opts)/database |
179
|
|
|
$proto = $match[1]; |
180
|
|
|
$proto_opts = $match[2] ? $match[2] : false; |
181
|
|
|
$dsn = $match[3]; |
182
|
|
|
} else { |
183
|
|
|
// $dsn => protocol+host/database (old format) |
184
|
|
View Code Duplication |
if (strpos($dsn, '+') !== false) { |
|
|
|
|
185
|
|
|
list($proto, $dsn) = explode('+', $dsn, 2); |
186
|
|
|
} |
187
|
|
|
if (strpos($dsn, '/') !== false) { |
188
|
|
|
list($proto_opts, $dsn) = explode('/', $dsn, 2); |
189
|
|
|
} else { |
190
|
|
|
$proto_opts = $dsn; |
191
|
|
|
$dsn = null; |
192
|
|
|
} |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
// process the different protocol options |
196
|
|
|
$protocol = (!empty($proto)) ? $proto : 'tcp'; |
197
|
|
|
$proto_opts = rawurldecode($proto_opts); |
198
|
|
|
if ($protocol === 'tcp') { |
199
|
|
|
if (strpos($proto_opts, ':') !== false) { |
200
|
|
|
list($parsed['host'], $parsed['port']) = explode(':', $proto_opts); |
201
|
|
|
} else { |
202
|
|
|
$parsed['host'] = $proto_opts; |
203
|
|
|
} |
204
|
|
|
} elseif ($protocol === 'unix') { |
205
|
|
|
$parsed['unix_socket'] = $proto_opts; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
// Get dabase if any |
209
|
|
|
// $dsn => database |
210
|
|
|
if ($dsn) { |
211
|
|
|
if (($pos = strpos($dsn, '?')) === false) { |
212
|
|
|
// /database |
213
|
|
|
$parsed['dbname'] = rawurldecode($dsn); |
214
|
|
|
} else { |
215
|
|
|
// /database?param1=value1¶m2=value2 |
216
|
|
|
$parsed['dbname'] = rawurldecode(substr($dsn, 0, $pos)); |
217
|
|
|
$dsn = substr($dsn, $pos + 1); |
218
|
|
View Code Duplication |
if (strpos($dsn, '&') !== false) { |
|
|
|
|
219
|
|
|
$opts = explode('&', $dsn); |
220
|
|
|
} else { |
221
|
|
|
$opts = array($dsn); |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
foreach ($opts as $opt) { |
225
|
|
|
list($key, $value) = explode('=', $opt); |
226
|
|
|
if (!isset($parsed[$key])) { |
227
|
|
|
// don't allow params overwrite |
228
|
|
|
$parsed[$key] = rawurldecode($value); |
229
|
|
|
} |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
if ($parsed['driver'] === 'sqlite') { |
235
|
|
|
if (isset($parsed['port']) && $parsed['port'] === 'memory') { |
236
|
|
|
$parsed['memory'] = true; |
237
|
|
|
unset($parsed['port']); |
238
|
|
|
unset($parsed['host']); |
239
|
|
|
} elseif (isset($parsed['dbname'])) { |
240
|
|
|
$parsed['path'] = $parsed['dbname']; |
241
|
|
|
unset($parsed['dbname']); |
242
|
|
|
unset($parsed['host']); |
243
|
|
|
} |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
$driverMap = array( |
247
|
|
|
'mysql' => 'pdo_mysql', |
248
|
|
|
'pgsql' => 'pdo_pgsql', |
249
|
|
|
'sqlite' => 'pdo_sqlite', |
250
|
|
|
); |
251
|
|
|
|
252
|
|
|
if (isset($driverMap[$parsed['driver']])) { |
253
|
|
|
$parsed['driver'] = $driverMap[$parsed['driver']]; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
return array_filter( |
257
|
|
|
$parsed, |
258
|
|
|
function ($element) { |
259
|
|
|
return $element !== false; |
260
|
|
|
} |
261
|
|
|
); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* @param \Doctrine\DBAL\Connection $connection |
266
|
|
|
*/ |
267
|
|
|
public function __construct(Connection $connection) |
268
|
|
|
{ |
269
|
|
|
$this->connection = $connection; |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* @return \Doctrine\DBAL\Connection |
274
|
|
|
*/ |
275
|
|
|
public function getConnection() |
276
|
|
|
{ |
277
|
|
|
return $this->connection; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* @return string |
282
|
|
|
*/ |
283
|
|
|
public function getName() |
284
|
|
|
{ |
285
|
|
|
return $this->connection->getDatabasePlatform()->getName(); |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* Begin a transaction. |
290
|
|
|
*/ |
291
|
|
|
public function beginTransaction() |
292
|
|
|
{ |
293
|
|
|
try { |
294
|
|
|
$this->connection->beginTransaction(); |
295
|
|
|
} catch (DBALException $e) { |
296
|
|
|
throw new QueryException($e->getMessage(), $e->getCode(), $e); |
297
|
|
|
} |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* Commit a transaction. |
302
|
|
|
*/ |
303
|
|
|
public function commit() |
304
|
|
|
{ |
305
|
|
|
try { |
306
|
|
|
$this->connection->commit(); |
307
|
|
|
} catch (DBALException $e) { |
308
|
|
|
throw new QueryException($e->getMessage(), $e->getCode(), $e); |
309
|
|
|
} |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* Rollback a transaction. |
314
|
|
|
*/ |
315
|
|
|
public function rollBack() |
316
|
|
|
{ |
317
|
|
|
try { |
318
|
|
|
$this->connection->rollBack(); |
319
|
|
|
} catch (DBALException $e) { |
320
|
|
|
throw new QueryException($e->getMessage(), $e->getCode(), $e); |
321
|
|
|
} |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
public function prepare($query) |
325
|
|
|
{ |
326
|
|
|
return $this->connection->prepare($query); |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* Retrieve the last auto incremet or sequence id. |
331
|
|
|
* |
332
|
|
|
* @param string $sequenceName |
333
|
|
|
* |
334
|
|
|
* @return string |
335
|
|
|
*/ |
336
|
|
|
public function lastInsertId($sequenceName = null) |
337
|
|
|
{ |
338
|
|
|
return $this->connection->lastInsertId($sequenceName); |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
/** |
342
|
|
|
* @return bool |
343
|
|
|
*/ |
344
|
|
|
public function useSequences() |
345
|
|
|
{ |
346
|
|
|
return $this->connection->getDatabasePlatform()->supportsSequences(); |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* Execute a query against the database. |
351
|
|
|
* |
352
|
|
|
* @param string $query |
353
|
|
|
*/ |
354
|
|
|
public function exec($query) |
355
|
|
|
{ |
356
|
|
|
try { |
357
|
|
|
$this->connection->exec($query); |
358
|
|
|
} catch (DBALException $e) { |
359
|
|
|
throw new QueryException($e->getMessage(), $e->getCode(), $e); |
360
|
|
|
} |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* Create Select Query object. |
365
|
|
|
* |
366
|
|
|
* @return \eZ\Publish\Core\Persistence\Database\SelectQuery |
367
|
|
|
*/ |
368
|
|
|
public function createSelectQuery() |
369
|
|
|
{ |
370
|
|
|
return new SelectDoctrineQuery($this->connection); |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
/** |
374
|
|
|
* Create Insert Query object. |
375
|
|
|
* |
376
|
|
|
* @return \eZ\Publish\Core\Persistence\Database\InsertQuery |
377
|
|
|
*/ |
378
|
|
|
public function createInsertQuery() |
379
|
|
|
{ |
380
|
|
|
return new InsertDoctrineQuery($this->connection); |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* Create update Query object. |
385
|
|
|
* |
386
|
|
|
* @return \eZ\Publish\Core\Persistence\Database\UpdateQuery |
387
|
|
|
*/ |
388
|
|
|
public function createUpdateQuery() |
389
|
|
|
{ |
390
|
|
|
return new UpdateDoctrineQuery($this->connection); |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* Create a Delete Query object. |
395
|
|
|
* |
396
|
|
|
* @return \eZ\Publish\Core\Persistence\Database\DeleteQuery |
397
|
|
|
*/ |
398
|
|
|
public function createDeleteQuery() |
399
|
|
|
{ |
400
|
|
|
return new DeleteDoctrineQuery($this->connection); |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
/** |
404
|
|
|
* Creates an alias for $tableName, $columnName in $query. |
405
|
|
|
* |
406
|
|
|
* @param \eZ\Publish\Core\Persistence\Database\SelectQuery $query |
407
|
|
|
* @param string $columnName |
408
|
|
|
* @param string|null $tableName |
409
|
|
|
* |
410
|
|
|
* @return string |
411
|
|
|
*/ |
412
|
|
|
public function aliasedColumn($query, $columnName, $tableName = null) |
413
|
|
|
{ |
414
|
|
|
return $this->alias( |
415
|
|
|
$this->quoteColumn($columnName, $tableName), |
416
|
|
|
$this->quoteIdentifier( |
417
|
|
|
($tableName ? $tableName . '_' : '') . |
418
|
|
|
$columnName |
419
|
|
|
) |
420
|
|
|
); |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
/** |
424
|
|
|
* Returns a qualified identifier for $columnName in $tableName. |
425
|
|
|
* |
426
|
|
|
* @param string $columnName |
427
|
|
|
* @param string $tableName |
428
|
|
|
* |
429
|
|
|
* @return string |
430
|
|
|
*/ |
431
|
|
|
public function quoteColumn($columnName, $tableName = null) |
432
|
|
|
{ |
433
|
|
|
return |
434
|
|
|
($tableName ? $this->quoteTable($tableName) . '.' : '') . |
435
|
|
|
$this->quoteIdentifier($columnName); |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
/** |
439
|
|
|
* Returns a qualified identifier for $tableName. |
440
|
|
|
* |
441
|
|
|
* @param string $tableName |
442
|
|
|
* |
443
|
|
|
* @return string |
444
|
|
|
*/ |
445
|
|
|
public function quoteTable($tableName) |
446
|
|
|
{ |
447
|
|
|
return $this->quoteIdentifier($tableName); |
448
|
|
|
} |
449
|
|
|
|
450
|
|
|
/** |
451
|
|
|
* Custom alias method. |
452
|
|
|
* |
453
|
|
|
* Ignores some properties of identifier quoting, but since we use somehow |
454
|
|
|
* sane table and column names, ourselves, this is fine. |
455
|
|
|
* |
456
|
|
|
* This is an optimization and works around the ezcDB implementation. |
457
|
|
|
* |
458
|
|
|
* @param string $identifier |
|
|
|
|
459
|
|
|
* |
460
|
|
|
* @return string |
461
|
|
|
*/ |
462
|
|
|
public function alias($name, $alias) |
463
|
|
|
{ |
464
|
|
|
return $name . ' ' . $alias; |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
/** |
468
|
|
|
* Custom quote identifier method. |
469
|
|
|
* |
470
|
|
|
* Ignores some properties of identifier quoting, but since we use somehow |
471
|
|
|
* sane table and column names, ourselves, this is fine. |
472
|
|
|
* |
473
|
|
|
* This is an optimization and works around the ezcDB implementation. |
474
|
|
|
* |
475
|
|
|
* @param string $identifier |
476
|
|
|
* |
477
|
|
|
* @return string |
478
|
|
|
*/ |
479
|
|
|
public function quoteIdentifier($identifier) |
480
|
|
|
{ |
481
|
|
|
return '`' . $identifier . '`'; |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
/** |
485
|
|
|
* Get auto increment value. |
486
|
|
|
* |
487
|
|
|
* Returns the value used for autoincrement tables. Usually this will just |
488
|
|
|
* be null. In case for sequence based RDBMS this method can return a |
489
|
|
|
* proper value for the given column. |
490
|
|
|
* |
491
|
|
|
* @param string $table |
492
|
|
|
* @param string $column |
493
|
|
|
* |
494
|
|
|
* @return mixed |
495
|
|
|
*/ |
496
|
|
|
public function getAutoIncrementValue($table, $column) |
497
|
|
|
{ |
498
|
|
|
return 'null'; |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
/** |
502
|
|
|
* Returns the name of the affected sequence. |
503
|
|
|
* |
504
|
|
|
* @param string $table |
505
|
|
|
* @param string $column |
506
|
|
|
* |
507
|
|
|
* @return string |
508
|
|
|
*/ |
509
|
|
|
public function getSequenceName($table, $column) |
510
|
|
|
{ |
511
|
|
|
return null; |
512
|
|
|
} |
513
|
|
|
} |
514
|
|
|
|
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.