Ik ben nu al een paar uurtjes bezig om iets werkend te krijgen maar ik waarschijnlijk is het zo iets knulligs dat ik het zelf niet zie en hoop dat iemand dat even aan kan wijzen.

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:

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	


<?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);
}
}

?>

<?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();
}
?>

Iemand een idee? Geen foutmeldingen, niets. Volgens de code bestaat de tabel settings niet.
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?
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 :).

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).


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

Tevens (security): zie deze comment
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.

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:

  SELECT * FROM :table WHERE :column LIKE :value

Want als je dat doet, ben je echt een eerste klas pannenkoek.

Reageren