PDO wrapper wil geen values binden
Waarom een wrapper over PDO heen is omdat ik:
- van korte inline codes hou.
- beter compatibility met andere drivers.
- .. de PDO constants echt bagger vind.
Waar het fout gaat is in de function Database->execute(...).
De type variable word herkend in de switch case, dan moet er een waarde aan de statement gekoppeld worden en dat gebeurd dus niet.
Het ergste van deze situatie is dat "tableExists" werkte toen dat ik de execute method strakker ging maken.
Zelf denk ik dat het te maken heeft dat execute() eerder word aangeroepen dan prepare, alleen als ik elke functie een nummer echo in de volgorde dat ze uitgevoerd moeten worden klopt het wel.
SQL LOG:
Code (php)
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
150527 9:02:51 1376 Connect root@localhost on mydb
1376 Query SET GLOBAL general_log_file = "/var/log/mysql.log"
/usr/sbin/mysqld, Version: 5.5.43-0ubuntu0.14.10.1 ((Ubuntu)). started with:
Tcp port: 3306 Unix socket: /var/run/mysqld/mysqld.sock
Time Id Command Argument
1376 Query SET GLOBAL general_log = 'ON'
1376 Prepare SHOW TABLES LIKE ':table'
1376 Execute SHOW TABLES LIKE ':table' <<--- ':table' zou veranderd moeten zijn in 'settings'
1376 Close stmt
1376 Quit
1376 Query SET GLOBAL general_log_file = "/var/log/mysql.log"
/usr/sbin/mysqld, Version: 5.5.43-0ubuntu0.14.10.1 ((Ubuntu)). started with:
Tcp port: 3306 Unix socket: /var/run/mysqld/mysqld.sock
Time Id Command Argument
1376 Query SET GLOBAL general_log = 'ON'
1376 Prepare SHOW TABLES LIKE ':table'
1376 Execute SHOW TABLES LIKE ':table' <<--- ':table' zou veranderd moeten zijn in 'settings'
1376 Close stmt
1376 Quit
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
<?php
class DatabaseException extends Exception{}
abstract class Database extends PDO{
private $sth;
abstract function sqlTableExists();
abstract function sqlSelectDatabase();
private function getDSN($driver, $multiarg){
return "$driver:" . implode(';', array_map(function($v, $k){return "$k=$v";}, $multiarg, array_keys($multiarg)));
}
public function prepare($stmt, $options = array()){
$this->sth = parent::prepare($stmt, $options);
return $this;
}
public function query($stmt){
return $this->sth = parent::query($stmt);
}
public function execute($stmt = null){
try{
if(is_array($stmt)){
foreach($stmt as $k => $v){
if(gettype($k) == 'string'){
$k = ($k[0] != ':') ? ":$k" : $k;
switch(gettype($v)){
case 'string':
// hier gaat het fout.. als het goed is.
// $k = :table
// $v = settings
$this->sth->bindValue($k, $v, PDO::PARAM_STR);
break;
case 'integer':
$this->sth->bindValue($k, $v, PDO::PARAM_INT);
break;
case 'boolean':
$this->sth->bindValue($k, $v, PDO::PARAM_BOOL);
case 'NULL':
$this->sth->bindValue($k, $v, PDO::PARAM_NULL);
break;
case 'resource':
$this->sth->bindValue($k, $v, PDO::PARAM_LOB);
break;
case 'double':
$this->sth->bindValue($k, (string)$v, PDO::PARAM_STR);
break;
default:
throw new DatabaseException('Unsupported variable type parsed in query.');
break;
}
} else {
throw new DatabaseException('Only named parameters are allowed.');
}
}
}
try{
$this->sth->execute();
return $this->sth;
} catch(PDOException $e){
throw $e;
}
} catch(DatabaseException $e){
throw $e;
}
}
public function fetchAssoc(){
return $this->sth->fetch(PDO::FETCH_ASSOC);
}
public function fetchBoth(){
return $this->sth->fetch(PDO::FETCH_BOTH);
}
public function fetchNum(){
return $this->sth->fetch(PDO::FETCH_NUM);
}
public function fetchObj(){
return $this->sth->fetch(PDO::FETCH_OBJ);
}
public function fetchLazy(){
return $this->sth->fetch(PDO::FETCH_LAZY);
}
public function tableExists($table){
// deze functie word aangeroepen.
return $this->prepare($this->sqlTableExists())->execute(array('table'=>$table))->rowCount()>0;
}
public function selectDatabase($db){
return $this->prepare($this->sqlSelectDatabase())->execute(array('db'=>$table));
}
function __construct($username, $password, $dsn, $options){
if(in_array(($driver = strtolower(get_class($this))), parent::getAvailableDrivers())){
$dsn = gettype($dsn) == 'array' ? $this->getDSN($driver, $dsn) : (string) $dsn;
try{
parent::__construct($dsn, $username, $password, $options);
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch(PDOException $e){
throw new DatabaseException('Could not connect to the database server.', $e);
}
} else {
throw new DatabaseException("Database driver '$this->type' is not supported on this machine.'");
}
}
}
class MySQL extends Database{
function sqlTableExists(){
return "SHOW TABLES LIKE ':table'";
}
function sqlSelectDatabase(){
return "USE :db";
}
function __construct($username, $password, $dsn, $options = array()) {
parent::__construct($username, $password, $dsn, $options);
}
}
class PostgrSQL extends Database{
function sqlTableExists(){
return "select count(*) from pg_class where relname=':table' and relkind='r'";
}
function sqlSelectDatabase(){
throw new DatabaseException('PostgrSQL does not support database swapping, create a new link or select from db.table.');
}
function __construct($username, $password, $dsn, $options = array()) {
parent::__construct($username, $password, $dsn, $options);
}
}
?>
class DatabaseException extends Exception{}
abstract class Database extends PDO{
private $sth;
abstract function sqlTableExists();
abstract function sqlSelectDatabase();
private function getDSN($driver, $multiarg){
return "$driver:" . implode(';', array_map(function($v, $k){return "$k=$v";}, $multiarg, array_keys($multiarg)));
}
public function prepare($stmt, $options = array()){
$this->sth = parent::prepare($stmt, $options);
return $this;
}
public function query($stmt){
return $this->sth = parent::query($stmt);
}
public function execute($stmt = null){
try{
if(is_array($stmt)){
foreach($stmt as $k => $v){
if(gettype($k) == 'string'){
$k = ($k[0] != ':') ? ":$k" : $k;
switch(gettype($v)){
case 'string':
// hier gaat het fout.. als het goed is.
// $k = :table
// $v = settings
$this->sth->bindValue($k, $v, PDO::PARAM_STR);
break;
case 'integer':
$this->sth->bindValue($k, $v, PDO::PARAM_INT);
break;
case 'boolean':
$this->sth->bindValue($k, $v, PDO::PARAM_BOOL);
case 'NULL':
$this->sth->bindValue($k, $v, PDO::PARAM_NULL);
break;
case 'resource':
$this->sth->bindValue($k, $v, PDO::PARAM_LOB);
break;
case 'double':
$this->sth->bindValue($k, (string)$v, PDO::PARAM_STR);
break;
default:
throw new DatabaseException('Unsupported variable type parsed in query.');
break;
}
} else {
throw new DatabaseException('Only named parameters are allowed.');
}
}
}
try{
$this->sth->execute();
return $this->sth;
} catch(PDOException $e){
throw $e;
}
} catch(DatabaseException $e){
throw $e;
}
}
public function fetchAssoc(){
return $this->sth->fetch(PDO::FETCH_ASSOC);
}
public function fetchBoth(){
return $this->sth->fetch(PDO::FETCH_BOTH);
}
public function fetchNum(){
return $this->sth->fetch(PDO::FETCH_NUM);
}
public function fetchObj(){
return $this->sth->fetch(PDO::FETCH_OBJ);
}
public function fetchLazy(){
return $this->sth->fetch(PDO::FETCH_LAZY);
}
public function tableExists($table){
// deze functie word aangeroepen.
return $this->prepare($this->sqlTableExists())->execute(array('table'=>$table))->rowCount()>0;
}
public function selectDatabase($db){
return $this->prepare($this->sqlSelectDatabase())->execute(array('db'=>$table));
}
function __construct($username, $password, $dsn, $options){
if(in_array(($driver = strtolower(get_class($this))), parent::getAvailableDrivers())){
$dsn = gettype($dsn) == 'array' ? $this->getDSN($driver, $dsn) : (string) $dsn;
try{
parent::__construct($dsn, $username, $password, $options);
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch(PDOException $e){
throw new DatabaseException('Could not connect to the database server.', $e);
}
} else {
throw new DatabaseException("Database driver '$this->type' is not supported on this machine.'");
}
}
}
class MySQL extends Database{
function sqlTableExists(){
return "SHOW TABLES LIKE ':table'";
}
function sqlSelectDatabase(){
return "USE :db";
}
function __construct($username, $password, $dsn, $options = array()) {
parent::__construct($username, $password, $dsn, $options);
}
}
class PostgrSQL extends Database{
function sqlTableExists(){
return "select count(*) from pg_class where relname=':table' and relkind='r'";
}
function sqlSelectDatabase(){
throw new DatabaseException('PostgrSQL does not support database swapping, create a new link or select from db.table.');
}
function __construct($username, $password, $dsn, $options = array()) {
parent::__construct($username, $password, $dsn, $options);
}
}
?>
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
try{
$db = new MySQL('usr', 'pw', array('host' => 'localhost', 'port' => 3306, 'dbname' => 'mydb'));
$db->exec('SET GLOBAL general_log_file = "/var/log/mysql.log";');
$db->exec("SET GLOBAL general_log = 'ON';");
echo $db->tableExists("settings") ? 'yes' : 'no'; // no, terwijl 100% de tabel bestaat.
} catch(PDOException $e){
echo $e->getMessage();
} catch(DatabaseException $e){
echo $e->getMessage();
}
?>
try{
$db = new MySQL('usr', 'pw', array('host' => 'localhost', 'port' => 3306, 'dbname' => 'mydb'));
$db->exec('SET GLOBAL general_log_file = "/var/log/mysql.log";');
$db->exec("SET GLOBAL general_log = 'ON';");
echo $db->tableExists("settings") ? 'yes' : 'no'; // no, terwijl 100% de tabel bestaat.
} catch(PDOException $e){
echo $e->getMessage();
} catch(DatabaseException $e){
echo $e->getMessage();
}
?>
Iemand een idee? Geen foutmeldingen, niets. Volgens de code bestaat de tabel settings niet.
Gewijzigd op 27/05/2015 09:43:12 door Johan K
Toevballig geen verschil in gebruik van Hoofdletters? "Settings" vs "settings"?
Ivo P op 27/05/2015 09:56:17:
Toevballig geen verschil in gebruik van Hoofdletters? "Settings" vs "settings"?
Net even een dubbel check gedaan, maar nee. Alles lowercase dus prima.
Maar buiten dat om, in de SQL log is te zien dat hij kijkt of de tabel ":table" bestaat, niet "settings".
Uuhm, bump?
Tevens (security): zie deze comment.
Ook: met PDO::ATTR_EMULATE_PREPARES false voer je vaak meer queries uit dan noodzakelijk (per SELECT query worden 2 queries uitgevoerd, 1 voor prepare, 1 voor execute - voor dat gebruik is het uitzetten van emulated prepares misschien minder efficient).
Thomas van den Heuvel op 29/05/2015 23:24:10:
Was niet het hele idee van prepared statements dat je je ook geen zorgen hoeft te maken om quotes, oftewel, haal de quotes om ':table' (regel 117) eens weg :).
Ook: met PDO::ATTR_EMULATE_PREPARES false voer je vaak meer queries uit dan noodzakelijk (per SELECT query worden 2 queries uitgevoerd, 1 voor prepare, 1 voor execute - voor dat gebruik is het uitzetten van emulated prepares misschien minder efficient).
Ook: met PDO::ATTR_EMULATE_PREPARES false voer je vaak meer queries uit dan noodzakelijk (per SELECT query worden 2 queries uitgevoerd, 1 voor prepare, 1 voor execute - voor dat gebruik is het uitzetten van emulated prepares misschien minder efficient).
Ik had de quotes weg gehaald en toen kwam ik er achter wat echt het probleem was.
Als de emulatie op 'false' staat voerde hij een prepared query in MySQL uit en dat gaf een syntax error in de SQL op.
Dus je kan binden wat je wil, maar niets word uitgevoerd in de PHP code aangezien dat met SQL query's moet gebeuren.
Aangezien dit een wrapper is om met 1 API meerdere databases te gebruiken hem ik die flag toch maar even op true gezet want anders heb dit totaal geen nut.
Ik heb het probleem opgelost, met dank aan jouw suggestie.
Thomas van den Heuvel op 29/05/2015 23:24:10:
Je maakte me eerst even bang, maar uiteindelijk is er niets om je er over druk te maken. Niet alles wat iedereen zegt op php.net gezegd is (of waar dan ook) hoeft waar te zijn.Tevens (security): zie deze comment
Zojuist eventjes de SQL logs erbij gepakt om het te testen en beiden methoden doen niets met "%" & "_".
Waarom? Omdat er geen mogelijk is om te kijken hoe deze gebruikt moeten worden in jouw syntax. Het is dus aan de gebruiker van PDO om deze waarde te escapen naar html entities of te escapen met een backslash voordat hij word opgenomen in de prepared statement.
Persoonlijk heb ik nog nooit met LIKE gewerkt, maar ik heb wel eventjes wat meer informatie opgezocht op het internet. Het is niet echt een security risk als je LIKE op een goede manier behandeld. Het enigste wat het kan doen is de gebruiker meer flexibiliteit geven om dingen te zoeken.
Dus, je kan het beter in je voordeel gebruiken en geen gekke SQL queries uitvoeren zoals:
Want als je dat doet, ben je echt een eerste klas pannenkoek.
Gewijzigd op 31/05/2015 03:53:01 door Johan K