Files
DomFramework/dblayeroo.php

2871 lines
95 KiB
PHP

<?php
/** DomFramework
@package domframework
@author Dominique Fournier <dominique@fournier38.fr> */
// dblayeroo.php
/** Permit abstraction on the differents SQL databases available */
class dblayeroo
{
/** The table name to use
*/
private $table = null;
/** The tableprefix text to prepend to table name (Should finish by _)
* Just allow chars !
*/
private $tableprefix = "";
/** The fields with the definition of type, and special parameters
*/
private $fields = array ();
/** The primary field
*/
private $primary = null;
/** An array to define the unique fields (or array of unique fields)
*/
private $unique = null;
/** An array to define the foreign keys of the field
*/
private $foreign = array ();
/** Debug of the SQL
*/
private $debug = FALSE;
/** The connecting DSN
*/
private $dsn = null;
/** The driver to use
*/
private $driver = null;
/** The field group delimiter
*/
private $sep = "";
/** Titles
*/
private $titles = array ();
/** Limit to one instance of the connection to the same database
*/
// Based on an idea of http://tonylandis.com/php/php5-pdo-singleton-class/
private static $instance = array ();
/** Connection to the database engine
* See http://fr2.php.net/manual/en/pdo.construct.php for the $dsn format
* @param string $dsn PDO Data Source Name
* @param string|null $username Username to connect
* @param string|null $password Password to connect
* @param string|null $driver_options Driver options to the database
*/
public function __construct ($dsn, $username=null, $password=null,
$driver_options=null)
{
$this->connect ($dsn, $username, $password, $driver_options);
}
/** Connection to the database engine
* See http://fr2.php.net/manual/en/pdo.construct.php for the $dsn format
* @param string $dsn PDO Data Source Name
* @param string|null $username Username to connect
* @param string|null $password Password to connect
* @param string|null $driver_options Driver options to the database
*/
public function connect ($dsn, $username=null, $password=null,
$driver_options=null)
/* {{{ */
{
if (! function_exists ("mb_strlen"))
throw new \Exception ("PHP don't have the MB Support. Please add it !",
500);
$driver = @explode (":", $dsn);
if (! isset ($driver[0]))
$this->DBException (dgettext ("domframework", "No valid DSN provided"));
$driver[0] = strtolower ($driver[0]);
if (! in_array ($driver[0], pdo_drivers ()))
$this->DBException (sprintf (dgettext ("domframework",
"Driver PDO '%s' not available in PHP"),
$driver[0]));
$this->driver = $driver[0];
// Force specifics initialisations
$this->dsn = $dsn;
switch ($driver[0])
{
case "sqlite":
// Look at the right to write in database and in the directory
$file = substr ($dsn, 7);
if (! is_writeable (dirname ($file)))
$this->DBException (dgettext ("domframework",
"The directory for SQLite database is write protected"));
if (file_exists ($file) && ! is_writeable ($file))
$this->DBException (dgettext ("domframework",
"The SQLite database file is write protected"));
if (function_exists ("posix_getuid") &&
file_exists ($file) &&
fileowner ($file) === posix_getuid ())
chmod ($file, 0666);
// Print the instances of PDO objects stored :
// var_dump (self::$instance);
if (! array_key_exists ($this->dsn, self::$instance))
{
$this->debugLog ("CONNECT TO SQLite DATABASE");
try
{
self::$instance[$this->dsn] = new \PDO ($dsn, $username, $password,
$driver_options);
self::$instance[$this->dsn]->setAttribute (\PDO::ATTR_ERRMODE,
\PDO::ERRMODE_EXCEPTION);
}
catch (Exception $e)
{
$this->DBException ("PDO error : ".$e->getMessage ());
}
}
// Force ForeignKeys support (disabled by default)
self::$instance[$this->dsn]->exec ("PRAGMA foreign_keys = ON");
$this->sep = "`";
if ($this->databasename () === null)
$this->DBException ("No Database provided in DSN");
break;
case "mysql":
if (! array_key_exists ($this->dsn, self::$instance))
{
$this->debugLog ("CONNECT TO MySQL DATABASE");
try
{
$driver_options[\PDO::MYSQL_ATTR_FOUND_ROWS] = 1;
self::$instance[$this->dsn] = new \PDO ($dsn, $username, $password,
$driver_options);
self::$instance[$this->dsn]->setAttribute (\PDO::ATTR_ERRMODE,
\PDO::ERRMODE_EXCEPTION);
}
catch (Exception $e)
{
$this->DBException ("PDO error : ".$e->getMessage ());
}
}
// Set the coding to UTF8
self::$instance[$this->dsn]->exec ("SET CHARACTER SET utf8");
$this->sep = "`";
if ($this->databasename () === null)
$this->DBException ("No Database provided in DSN");
// Force the GROUP_CONCAT value max to the max allowed from the server
$st = self::$instance[$this->dsn]->query (
"SHOW VARIABLES LIKE 'max_allowed_packet'", PDO::FETCH_COLUMN, 1);
$rows = $st->fetchAll ();
if (! isset ($rows[0]))
throw new \Exception (
"Can't read the max_allowed_packet from the MySQL server", 500);
$max_allowed_packet = $rows[0];
self::$instance[$this->dsn]->exec (
"SET SESSION group_concat_max_len = $max_allowed_packet");
break;
case "pgsql":
if (! array_key_exists ($this->dsn, self::$instance))
{
$this->debugLog ("CONNECT TO PGSQL DATABASE");
try
{
self::$instance[$this->dsn] = new \PDO ($dsn, $username, $password,
$driver_options);
self::$instance[$this->dsn]->setAttribute (\PDO::ATTR_ERRMODE,
\PDO::ERRMODE_EXCEPTION);
}
catch (Exception $e)
{
$this->DBException ("PDO error : ".$e->getMessage ());
}
}
// Set the coding to UTF8
self::$instance[$this->dsn]->exec ("SET NAMES 'utf8'");
$this->sep = "\"";
if ($this->databasename () === null)
$this->DBException ("No Database provided in DSN");
break;
default:
$this->DBException (dgettext ("domframework",
"Unknown PDO driver provided"));
}
return self::$instance[$this->dsn];
}
/* }}} */
/** This function disconnect the database. It is normally only used in phpunit
* unit tests
*/
public function disconnect ()
/* {{{ */
{
unset (self::$instance[$this->dsn]);
self::$sortOrder = 0;
}
/* }}} */
/** Start a new Transaction
*/
public function beginTransaction ()
/* {{{ */
{
return self::$instance[$this->dsn]->beginTransaction ();
}
/* }}} */
/** Commit (validate) a transaction
*/
public function commit ()
/* {{{ */
{
return self::$instance[$this->dsn]->commit ();
}
/* }}} */
/** RollBack a transaction
*/
public function rollback ()
/* {{{ */
{
return self::$instance[$this->dsn]->rollback ();
}
/* }}} */
/** Return the connected database name from DSN used to connect
*/
public function databasename ()
/* {{{ */
{
if ($this->sep === "")
$this->DBException (dgettext ("domframework", "Database not connected"));
if ($this->driver === "sqlite")
{
$dbFile = substr (strstr ($this->dsn, ":"), 1);
if (trim ($dbFile) !== "")
return $dbFile;
return null;
}
$vals = explode (";", substr (strstr ($this->dsn, ":"), 1));
$dsnExplode = array ();
foreach ($vals as $val)
{
@list ($k, $v) = explode ("=", $val);
$dsnExplode[$k] = $v;
}
if (isset ($dsnExplode["dbname"]))
return $dsnExplode["dbname"];
return NULL;
}
/* }}} */
/** Return all the tables available in the database
*/
public function listTables ()
/* {{{ */
{
if ($this->sep === "")
$this->DBException (dgettext ("domframework", "Database not connected"));
switch (self::$instance[$this->dsn]->getAttribute (\PDO::ATTR_DRIVER_NAME))
{
case "sqlite":
$req = "SELECT name FROM sqlite_master WHERE type='table'";
$st = self::$instance[$this->dsn]->prepare ($req);
$st->execute ();
$res = array ();
while ($d = $st->fetch (\PDO::FETCH_ASSOC))
$res[] = $d["name"];
break;
case "mysql":
$req = "SELECT TABLE_NAME
FROM information_schema.tables
WHERE TABLE_SCHEMA='".$this->databasename ()."'";
$st = self::$instance[$this->dsn]->prepare ($req);
$st->execute ();
$res = array ();
while ($d = $st->fetch (\PDO::FETCH_ASSOC))
$res[] = $d["TABLE_NAME"];
break;
case "pgsql":
$req = "SELECT *
FROM pg_tables
WHERE schemaname = 'public'";
$st = self::$instance[$this->dsn]->prepare ($req);
$st->execute ();
$res = array ();
while ($d = $st->fetch (\PDO::FETCH_ASSOC))
$res[] = $d["tablename"];
break;
default:
$this->DBException (dgettext ("domframework",
"Unknown database driver in listTables"));
}
return $res;
}
/* }}} */
/** Create the table defined by the differents fields.
* Define the SQL syntax based on SQL engines
* $table = "dns zones";
* $fields = array (
* "id"=>array ("integer", "not null", "autoincrement"),
* "zo ne"=>array ("varchar(255)", "not null"),
* "vie wname"=>array ("varchar(255)"),
* "view clients"=>array ("varchar(255)"),
* "comme nt"=>array ("varchar(1024)"),
* "opendate"=>array ("datetime", "not null"),
* "closedate"=>array ("datetime"),
* );
* $primary = "id";
* $unique = array ("id", array ("zo ne", "vie wname"));
* $foreign = array ("zone"=>"table.field",...);
*/
public function createTable ()
/* {{{ */
{
$this->debugLog ("Entering createTable");
if ($this->sep === "")
throw new Exception (dgettext("domframework", "Database not connected"),
500);
if (count ($this->fields) === 0)
throw new Exception (dgettext("domframework", "No Field defined"), 500);
if ($this->table === null)
throw new \Exception (dgettext("domframework",
"No table name defined to create the table"), 500);
switch (self::$instance[$this->dsn]->getAttribute(\PDO::ATTR_DRIVER_NAME))
{
case "sqlite":
$sql = "CREATE TABLE IF NOT EXISTS ".
"$this->sep$this->tableprefix$this->table$this->sep ".
"(\n";
$i = 0;
foreach ($this->fields as $field=>$params)
{
if ($i > 0)
$sql .= ",\n";
// Name of field
$sql .= "$this->sep$field$this->sep ";
switch ($this->fieldTypeLight ($field))
{
case "datetime":
$sql .= "DATETIME";
$params = array_slice ($params, 1);
break;
case "date":
$sql .= "DATE";
$params = array_slice ($params, 1);
break;
case "integer":
$sql .= "INTEGER";
$params = array_slice ($params, 1);
break;
case "varchar":
$length = $this->fieldLength ($field);
$sql .= "VARCHAR($length)";
$params = array_slice ($params, 1);
break;
default:
throw new Exception (sprintf (
dgettext("domframework",
"Unknown type '%s' provided for field '%s'"),
$this->fieldTypeLight ($field), $field), 500);
}
// Primary key
if ($this->primary === $field)
$sql .= " PRIMARY KEY";
// Others parameters for field
// Sort to put the autoincrement field in front of params, if it is
// present
sort ($params);
foreach ($params as $p)
{
switch ($p)
{
case "not null": $sql .= " NOT NULL"; break;
case "autoincrement":
if ($this->primary !== $field)
throw new \Exception (sprintf (
dgettext("domframework",
"Field '%s' is autoincrement but not primary"),
$field), 500);
$sql .= " AUTOINCREMENT";
break;
default:
throw new Exception (sprintf (dgettext("domframework",
"Unknown additionnal parameter '%s' for field '%s'"),
$p, $field), 500);
}
}
$i ++;
}
// Unique fields
if ($this->unique !== null)
{
if (!is_array ($this->unique))
throw new Exception (dgettext("domframework",
"The Unique field definition is not an array"),
500);
foreach ($this->unique as $u)
{
$sql .= ",\n UNIQUE ($this->sep";
if (is_array ($u))
$sql .=implode ("$this->sep,$this->sep", $u);
else
$sql .= $u;
$sql .="$this->sep)";
}
}
// Foreign keys
$i = 0;
foreach ($this->foreign as $field=>$k)
{
$field = explode (",", $field);
$field = implode ($this->sep.",".$this->sep, $field);
$k[1] = explode (",", $k[1]);
$k[1] = implode ($this->sep.",".$this->sep, $k[1]);
$sql .= ",\n FOREIGN KEY($this->sep$field$this->sep) ".
"REFERENCES $this->sep".$k[0]."$this->sep($this->sep".
$k[1]."$this->sep)";
if (isset ($k[2]))
$sql .= " ".$k[2];
$i++;
}
$sql .=")";
break;
case "mysql":
$sql = "CREATE TABLE IF NOT EXISTS ".
"$this->sep$this->tableprefix$this->table$this->sep ".
"(\n";
$i = 0;
foreach ($this->fields as $field=>$params)
{
if ($i > 0)
$sql .= ",\n";
// Name of field
$sql .= "$this->sep$field$this->sep ";
switch ($this->fieldTypeLight ($field))
{
case "integer":
$sql .= "INTEGER";
$params = array_slice ($params, 1);
break;
case "varchar":
$length = $this->fieldLength ($field);
$sql .= "VARCHAR($length)";
$params = array_slice ($params, 1);
break;
case "datetime":
$sql .= "DATETIME";
$params = array_slice ($params, 1);
break;
case "date":
$sql .= "DATE";
$params = array_slice ($params, 1);
break;
default:
throw new Exception (sprintf (
dgettext("domframework",
"Unknown type provided for field '%s'"),
$field), 500);
}
// Primary key
if ($this->primary === $field)
$sql .= " PRIMARY KEY";
// Others parameters for field
// Sort to put the autoincrement field in front of params, if it is
// present
sort ($params);
foreach ($params as $p)
{
switch ($p)
{
case "not null": $sql .= " NOT NULL"; break;
case "autoincrement":
if ($this->primary !== $field)
throw new \Exception (sprintf (
dgettext("domframework",
"Field '%s' is autoincrement but not primary"),
$field), 500);
$sql .= " AUTO_INCREMENT";
break;
default:
throw new Exception (sprintf (
dgettext("domframework",
"Unknown additionnal '%s' parameter for field '%s'"),
$p, $field), 500);
}
}
$i ++;
}
// Unique fields
if ($this->unique !== null)
{
foreach ($this->unique as $u)
{
$sql .= ",\n UNIQUE ($this->sep";
if (is_array ($u))
$sql .=implode ("$this->sep,$this->sep", $u);
else
$sql .= $u;
$sql .="$this->sep)";
}
}
// Foreign keys
$i = 0;
foreach ($this->foreign as $field=>$k)
{
$field = explode (",", $field);
$field = implode ($this->sep.",".$this->sep, $field);
$k[1] = explode (",", $k[1]);
$k[1] = implode ($this->sep.",".$this->sep, $k[1]);
$sql .= ",\n FOREIGN KEY($this->sep$field$this->sep) ".
"REFERENCES $this->sep".$k[0]."$this->sep($this->sep".
$k[1]."$this->sep)";
if (isset ($k[2]))
$sql .= " ".$k[2];
$i++;
}
$sql .=") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
break;
case "pgsql":
$sql = "CREATE TABLE IF NOT EXISTS ".
"\"$this->tableprefix$this->table\" (\n";
$i = 0;
foreach ($this->fields as $field=>$params)
{
if ($i > 0)
$sql .= ",\n";
// Name of field
$sql .= "\"$field\" ";
if (in_array ("autoincrement", $params))
{
if ($this->primary !== $field)
throw new \Exception (sprintf (
dgettext("domframework",
"Field '%s' is autoincrement but not primary"),
$field), 500);
$sql .= "SERIAL";
}
else
{
switch ($this->fieldTypeLight ($field))
{
case "integer":
$sql .= "INTEGER";
$params = array_slice ($params, 1);
break;
case "varchar":
$length = $this->fieldLength ($field);
$sql .= "VARCHAR($length)";
$params = array_slice ($params, 1);
break;
case "datetime":
$sql .= "timestamp with time zone";
$params = array_slice ($params, 1);
break;
case "date":
$sql .= "DATE";
$params = array_slice ($params, 1);
break;
default:
throw new Exception (sprintf (
dgettext("domframework",
"Unknown type provided for field '%s'"),
$field), 500);
}
// Primary key
if ($this->primary === $field)
$sql .= " PRIMARY KEY";
// Others parameters for field
// Sort to put the autoincrement field in front of params, if it is
// present
sort ($params);
foreach ($params as $p)
{
switch ($p)
{
case "not null": $sql .= " NOT NULL"; break;
default:
throw new Exception (sprintf (
dgettext("domframework",
"Unknown additionnal parameter '%s' for field '%s'"),
$p, $field), 500);
}
}
}
$i ++;
}
// Unique fields
if ($this->unique !== null)
{
foreach ($this->unique as $u)
{
$sql .= ",\n UNIQUE (\"";
if (is_array ($u))
$sql .=implode ("\",\"", $u);
else
$sql .= $u;
$sql .="\")";
}
}
// Foreign keys
$i = 0;
foreach ($this->foreign as $field=>$k)
{
$field = explode (",", $field);
$field = implode ($this->sep.",".$this->sep, $field);
$k[1] = explode (",", $k[1]);
$k[1] = implode ($this->sep.",".$this->sep, $k[1]);
$sql .= ",\n FOREIGN KEY(\"$field\") REFERENCES \"".$k[0]."\"(\"".
$k[1]."\")";
if (isset ($k[2]))
$sql .= " ".$k[2];
$i++;
}
$sql .=")";
break;
default:
throw new Exception (dgettext("domframework",
"PDO Engine not supported in dbLayeroo"), 500);
}
$this->debugLog ($sql);
return self::$instance[$this->dsn]->exec ($sql);
}
/* }}} */
/** Drop the table
*/
public function dropTable ()
/* {{{ */
{
$this->debugLog ("Entering dropTable ()");
if ($this->sep === "")
throw new Exception (dgettext("domframework", "Database not connected"));
if ($this->table === null)
throw new \Exception (dgettext("domframework",
"No table name defined to drop the table"), 500);
$sql = "DROP TABLE $this->sep$this->tableprefix$this->table$this->sep";
$this->debugLog ($sql);
return self::$instance[$this->dsn]->exec ($sql);
}
/* }}} */
/** Get the informations about a table
* @param string $tableName The table to examine
*/
public function getTableSchema ($tableName)
/* {{{ */
{
$this->debugLog ("Entering getTableSchema (",$tableName,")");
switch (self::$instance[$this->dsn]->getAttribute(\PDO::ATTR_DRIVER_NAME))
{
case "sqlite":
$st = self::$instance[$this->dsn]->prepare (
"PRAGMA table_info($tableName)");
$st->execute ();
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
$fields = array ();
$unique = array ();
$foreign = array ();
$primary = "";
foreach ($content as $row)
{
$type = str_replace (" ", "", strtolower ($row["type"]));
$fields[$row["name"]][] = $type;
if ($row["notnull"] === "1")
$fields[$row["name"]][] = "not null";
if ($row["pk"] === "1")
$primary = $row["name"];
}
$st = self::$instance[$this->dsn]->prepare (
"PRAGMA index_list($tableName)");
$st->execute ();
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
foreach ($content as $c)
{
$st = self::$instance[$this->dsn]->prepare (
"PRAGMA index_info(".$c["name"].")");
$st->execute ();
$content2 = $st->fetchAll (\PDO::FETCH_ASSOC);
if (count ($content2) > 1)
{
$index = array ();
foreach ($content2 as $c2)
{
$index[] = $c2["name"];
}
$unique[$content2[0]["cid"]-1] = $index;
}
elseif (count ($content2) === 1)
{
$index = $content2[0]["name"];
$unique[$content2[0]["cid"]-1] = $index;
}
}
ksort ($unique);
try
{
$st = self::$instance[$this->dsn]->prepare (
"SELECT * FROM sqlite_sequence WHERE name='$tableName'");
$st->execute ();
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
if (count ($content) > 0 && $primary !== "")
$fields[$primary][] = "autoincrement";
}
catch (\Exception $e)
{
// If no autoincrement key, the sqlite_sequence table doesn't exists
}
$st = self::$instance[$this->dsn]->prepare (
"PRAGMA foreign_key_list('$tableName')");
$st->execute ();
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
foreach ($content as $for)
{
$tmp = array ($for["table"], $for["to"]);
$cascade = "";
if ($for["on_update"] !== "NO ACTION")
$cascade .= "ON UPDATE ".$for["on_update"];
if ($for["on_delete"] !== "NO ACTION")
$cascade .= "ON DELETE ".$for["on_delete"];
if ($cascade !== "")
$tmp[] = $cascade;
$foreign[$for["from"]] = $tmp;
}
return array ("fields" => $fields,
"primary" => $primary,
"unique" => $unique,
"foreign" => $foreign);
break;
case "mysql":
$st = self::$instance[$this->dsn]->prepare (
"SHOW COLUMNS FROM `$tableName`");
$st->execute ();
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
$fields = array ();
$unique = array ();
$foreign = array ("TBD");
$primary = "";
foreach ($content as $col)
{
$tmp = array ();
if ($col["Type"] === "int(11)")
$tmp[] = "integer";
else
$tmp[] = $col["Type"];
if ($col["Null"] === "NO")
$tmp[] = "not null";
if ($col["Extra"] === "auto_increment")
$tmp[] = "autoincrement";
$fields[$col["Field"]] = $tmp;
}
$st = self::$instance[$this->dsn]->prepare (
"SHOW INDEX FROM `$tableName`");
$st->execute ();
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
foreach ($content as $col)
{
if ($col["Key_name"] === "PRIMARY")
$primary = $col["Column_name"];
else
{
if ($col["Non_unique"] === "1")
continue;
if (array_key_exists ($col["Key_name"], $unique))
{
if (!is_array ($unique[$col["Key_name"]]))
$unique[$col["Key_name"]] = array ($unique[$col["Key_name"]]);
$unique[$col["Key_name"]][] = $col["Column_name"];
}
else
{
$unique[$col["Key_name"]] = $col["Column_name"];
}
}
}
$unique = array_values ($unique);
$st = self::$instance[$this->dsn]->prepare ("
SELECT UPDATE_RULE,DELETE_RULE,COLUMN_NAME,
kColUsage.REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME
FROM information_schema.REFERENTIAL_CONSTRAINTS AS rCons,
information_schema.KEY_COLUMN_USAGE as kColUsage
WHERE rCons.CONSTRAINT_SCHEMA=:dbname AND rCons.TABLE_NAME=:table
AND rCons.CONSTRAINT_NAME=kColUsage.CONSTRAINT_NAME");
$st->execute (array(':dbname' => $this->databasename (),
':table' => $tableName));
$foreignTmp = $st->fetchAll (\PDO::FETCH_ASSOC);
$foreign = array ();
foreach ($foreignTmp as $f)
{
$tmp = array ();
$tmp[] = $f["REFERENCED_TABLE_NAME"];
$tmp[] = $f["REFERENCED_COLUMN_NAME"];
$tmp[2] = "";
if ($f["UPDATE_RULE"] !== "NO ACTION" &&
$f["UPDATE_RULE"] !== "RESTRICT")
$tmp[2] .= "ON UPDATE ".$f["UPDATE_RULE"]." ";
if ($f["DELETE_RULE"] !== "NO ACTION" &&
$f["DELETE_RULE"] !== "RESTRICT")
$tmp[2] .= "ON DELETE ".$f["DELETE_RULE"];
if ($tmp[2] !== "")
$tmp[2] = trim ($tmp[2]);
else
unset ($tmp[2]);
$foreign[$f["COLUMN_NAME"]] = $tmp;
}
return array ("fields" => $fields,
"primary" => $primary,
"unique" => $unique,
"foreign" => $foreign);
break;
case "pgsql":
$st = self::$instance[$this->dsn]->prepare (
"SELECT *
FROM information_schema.columns
WHERE table_schema='public' and table_name='$tableName'");
$st->execute ();
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
$fields = array ();
$unique = array ();
$foreign = array ();
$primary = "";
foreach ($content as $col)
{
$tmp = array ();
if ($col["data_type"] === "character varying")
$tmp[] = "varchar(".$col["character_maximum_length"].")";
else
$tmp[] = $col["data_type"];
if ($col["is_nullable"] === "NO")
$tmp[] = "not null";
if (substr ($col["column_default"], 0, 7) === "nextval")
{
$tmp[] = "autoincrement";
$primary = $col["column_name"];
}
$fields[$col["column_name"]] = $tmp;
}
$st = self::$instance[$this->dsn]->prepare (
"select *
from information_schema.constraint_column_usage
where table_name='$tableName'");
$st->execute ();
$content = $st->fetchAll (\PDO::FETCH_ASSOC);
foreach ($content as $col)
{
if (array_key_exists ($col["constraint_name"], $unique))
{
if (! is_array ($unique[$col["constraint_name"]]))
$unique[$col["constraint_name"]] =
array ($unique[$col["constraint_name"]]);
$unique[$col["constraint_name"]][] = $col["column_name"];
}
else
{
$unique[$col["constraint_name"]] = $col["column_name"];
}
}
$unique = array_values ($unique);
$st = self::$instance[$this->dsn]->prepare ("
SELECT kColUsage1.column_name COLUMN_NAME,
kColUsage2.table_name REFERENCED_TABLE_NAME,
kColUsage2.column_name REFERENCED_COLUMN_NAME,
update_rule,delete_rule
FROM information_schema.KEY_COLUMN_USAGE AS kColUsage1,
information_schema.KEY_COLUMN_USAGE AS kColUsage2,
information_schema.REFERENTIAL_CONSTRAINTS AS rCons
WHERE kColUsage1.table_catalog=:dbname AND kColUsage1.table_name=:table
AND rCons.constraint_name=kColUsage1.constraint_name
AND rCons.unique_constraint_name=kColUsage2.constraint_name
");
$st->execute (array(':dbname' => $this->databasename (),
':table' => $tableName));
$foreignTmp = $st->fetchAll (\PDO::FETCH_ASSOC);
$foreign = array ();
foreach ($foreignTmp as $f)
{
$tmp = array ();
$tmp[] = $f["referenced_table_name"];
$tmp[] = $f["referenced_column_name"];
$tmp[2] = "";
if ($f["update_rule"] !== "NO ACTION" &&
$f["update_rule"] !== "RESTRICT")
$tmp[2] .= "ON UPDATE ".$f["update_rule"]." ";
if ($f["delete_rule"] !== "NO ACTION" &&
$f["delete_rule"] !== "RESTRICT")
$tmp[2] .= "ON DELETE ".$f["delete_rule"];
if ($tmp[2] !== "")
$tmp[2] = trim ($tmp[2]);
else
unset ($tmp[2]);
$foreign[$f["column_name"]] = $tmp;
}
return array ("fields" => $fields,
"primary" => $primary,
"unique" => $unique,
"foreign" => $foreign);
break;
default:
throw new Exception (dgettext("domframework",
"PDO Engine not supported in dbLayeroo"), 500);
}
}
/* }}} */
/** Return the type of the provided field
* @param string $field The field to get the type
*/
private function fieldTypeComplete ($field)
/* {{{ */
{
// $this->debugLog ("Entering fieldTypeComplete (",$field,")");
if (! array_key_exists ($field, $this->fields))
$this->DBException (sprintf (
"fieldType : can't find the definition for field '%s'",
$field));
if (! array_key_exists (0, $this->fields[$field]))
$this->DBException (sprintf (
"fieldType : can't find the type for field '%s'",
$field));
if (! is_string ($this->fields[$field][0]))
$this->DBException (sprintf (
"fieldType : The type of field '%s' is not a string",
$field));
$type = strtolower ($this->fields[$field][0]);
$type = str_replace (" ", "", $type);
return $type;
}
/* }}} */
/** Return the type of the provided field. For varchar(255), return only
* varchar
* @param string $field The field to get the type
*/
private function fieldTypeLight ($field)
/* {{{ */
{
$type = $this->fieldTypeComplete ($field);
list ($type, ) = explode ("(", $type);
return $type;
}
/* }}} */
/** Return the length of a field (generally a varchar)
* @param string $field The field to get the type
*/
private function fieldLength ($field)
/* {{{ */
{
$type = $this->fieldTypeComplete ($field);
$pos = strpos ($type, "(");
if ($pos === false)
$this->DBException (sprintf (
"fieldLength : no length defined for field '%s'", $field));
$length = intval (substr ($type, 1+$pos, -1));
if ($length === 0)
$this->DBException (sprintf (
"fieldLength : Length equal to Zero for field '%s'", $field));
return $length;
}
/* }}} */
/////////////////////////////
/// GETTERS / SETTERS ///
/////////////////////////////
/** Get/Set the table property
* @param string|null $table The table to use
*/
public function table ($table=null)
/* {{{ */
{
$this->debugLog ("Entering table (",$table,")");
if ($table === null)
return $this->table;
if (! is_string ($table))
$this->DBException ("Parameter table invalid: not a string");
if (mb_strlen ($table) > 63)
$this->DBException ("Parameter table invalid: too long");
$this->table = $table;
return $this;
}
/* }}} */
/** Get/Set the tableprefix property
* @param string|null $tableprefix The prefix to append
*/
public function tableprefix ($tableprefix=null)
/* {{{ */
{
$this->debugLog ("Entering tableprefix (",$tableprefix,")");
if ($tableprefix === null)
return $this->tableprefix;
if (! is_string ($tableprefix))
$this->DBException ("Parameter tableprefix invalid: not a string");
// 64 - at least one char for the table name
if (mb_strlen ($tableprefix) > 63)
$this->DBException ("Parameter tableprefix invalid: too long");
$this->tableprefix = $tableprefix;
return $this;
}
/* }}} */
/** Get/Set the fields property
* The fields to define are in the format:
* array ("fieldName"=>array ("type"[, "not null"[, "autoincrement"]]))
* @param array|null $fields The fields to define
*/
public function fields ($fields=null)
/* {{{ */
{
$this->debugLog ("Entering fields (VALUE)");
if ($fields === null)
return $this->fields;
if (! is_array ($fields))
$this->DBException ("Parameter fields invalid: not an array");
foreach ($fields as $field=>$params)
{
if (mb_strlen ($field) > 64)
$this->DBException ("Parameter fields invalid: column name too long");
if (! is_array ($params))
$this->DBException ("Parameter fields invalid: ".
"param not an array for '$field'");
if (array_key_exists (0, $params))
$fields[$field][0] = strtolower ($fields[$field][0]);
if (array_key_exists (1, $params))
$fields[$field][1] = strtolower ($fields[$field][1]);
if (array_key_exists (2, $params))
$fields[$field][2] = strtolower ($fields[$field][2]);
}
foreach ($fields as $field=>$params)
{
if (! array_key_exists (0, $params))
$this->DBException ("Parameter fields invalid: ".
"No type of column provided for '$field'");
if (preg_match ("#^(date|datetime|integer|time|".
"varchar\(\d+\))$#i",
$params[0]) !== 1)
$this->DBException ("Parameter fields invalid: ".
"Unknown column type provided for '$field'");
if (array_key_exists (1, $params) &&
$params[1] !== "not null" && $params[1] !== "autoincrement")
$this->DBException ("Parameter fields invalid: ".
"Second parameter invalid for '$field'");
if (array_key_exists (2, $params) &&
$params[2] !== "autoincrement")
$this->DBException ("Parameter fields invalid: ".
"Third parameter invalid for '$field'");
if ($params[0] !== "integer" && (
isset ($params[1]) && $params[1] === "autoincrement" ||
isset ($params[2]) && $params[2] === "autoincrement"))
$this->DBException ("Parameter fields invalid: ".
"Field '$field' can not be autoincrement and not integer");
if (array_key_exists (0, $params))
$fields[$field][0] = strtolower ($fields[$field][0]);
if (array_key_exists (1, $params))
$fields[$field][1] = strtolower ($fields[$field][1]);
if (array_key_exists (2, $params))
$fields[$field][2] = strtolower ($fields[$field][2]);
}
$this->fields = $fields;
return $this;
}
/* }}} */
/** Get all the fields with the table name if needed.
* If the objectJoin is set, return the fields name too
* @param boolean|null $full Add the table name if the $full is set
*/
public function fieldsAll ($full = false)
/* {{{ */
{
$fields = array ();
if ($this->joinObject)
{
foreach ($this->joinObject as $obj)
$fields = array_merge ($fields, $obj->fieldsAll (true));
$full = true;
}
foreach ($this->fields as $f=>$val)
{
if ($full !== false)
$fields[$this->sep.$this->tableprefix.$this->table.$this->sep.".".
$this->sep.$f.$this->sep] = $val;
else
$fields[$f] = $val;
}
return $fields;
}
/* }}} */
/** Get/Set the primary property
* @param string|null $primary The primary key to use
*/
public function primary ($primary=null)
/* {{{ */
{
$this->debugLog ("Entering primary (",$primary,")");
if ($primary === null)
return $this->primary;
if (! is_string ($primary))
$this->DBException ("Parameter primary invalid: not a string");
if (mb_strlen ($primary) > 64)
$this->DBException ("Parameter primary invalid: too long");
if (! array_key_exists ($primary, $this->fields))
$this->DBException ("Parameter primary invalid: column doesn't exists");
$this->primary = $primary;
return $this;
}
/* }}} */
/** Get/Set the unique property
* @param array|null $unique The unique fields constraint to add
*/
public function unique ($unique=null)
/* {{{ */
{
$this->debugLog ("Entering unique (VALUE)");
if ($unique === null)
return $this->unique;
if (! is_array ($unique))
$this->DBException ("Parameter unique invalid: not an array");
foreach ($unique as $u1)
{
if (is_string ($u1))
{
if (mb_strlen ($u1) > 64)
$this->DBException ("Parameter unique invalid: too long '$u1'");
}
elseif (is_array ($u1))
{
foreach ($u1 as $u2)
{
if (is_string ($u2))
{
if (mb_strlen ($u2) > 64)
$this->DBException ("Parameter unique invalid: too long '$u2'");
}
else
$this->DBException ("Parameter unique invalid: Not string ".
"in array");
}
}
else
$this->DBException ("Parameter unique invalid: Not string nor array: ".
gettype ($u1));
}
$this->unique = $unique;
return $this;
}
/* }}} */
/** Get/Set the foreign property
* @param array|null $foreign The definition of the foreign constraint
* The format is :
* array (
* "field" => array ("parentTable", "parentField", "options if needed"),
* )
* Multiple field and parnentField can be provided, separated by comma
*/
public function foreign ($foreign=null)
/* {{{ */
{
$this->debugLog ("Entering foreign (VALUE)");
if ($foreign === null)
return $this->foreign;
if (! is_array ($foreign))
$this->DBException ("Parameter foreign invalid: not an array");
foreach ($foreign as $cols=>$params)
{
foreach (explode (",", $cols) as $col)
{
if (! array_key_exists ($col, $this->fields))
$this->DBException (
"Parameter foreign invalid: column doesn't exists");
if (! is_array ($params))
$this->DBException ("Parameter foreign invalid: ".
"parameters not in array");
if (! array_key_exists (0, $params))
$this->DBException ("Parameter foreign invalid: ".
"ParentTable is not provided");
if (! array_key_exists (1, $params))
$this->DBException ("Parameter foreign invalid: ".
"ParentField is not provided");
if (! is_string ($params[0]))
$this->DBException ("Parameter foreign invalid: ".
"parameter 0 is not a string");
if (! is_string ($params[1]))
$this->DBException ("Parameter foreign invalid: ".
"parameter 1 is not a string");
if (mb_strlen ($params[0] > 64))
$this->DBException ("Parameter foreign invalid: ".
"parameter 0 is too long");
if (mb_strlen ($params[1] > 64))
$this->DBException ("Parameter foreign invalid: ".
"parameter 1 is too long");
}
if (substr_count ($cols, ",") !== substr_count ($params[1], ","))
$this->DBException ("Parameter foreign invalid: ".
"Not the same number of comma between the local ".
"field and the parent field");
if (array_key_exists (2, $params))
{
preg_match_all ("#^(ON UPDATE ".
"(CASCADE|RESTRICT|NO ACTION|SET DEFAULT|SET NULL) ?)?".
"(ON DELETE ".
"(CASCADE|RESTRICT|NO ACTION|SET DEFAULT|SET NULL))?$#",
$params[2], $matches);
if ($matches[1] === "" && $matches[3] === "")
$this->DBException ("Parameter foreign invalid: ".
"Unknown action provided");
}
}
$this->foreign = $foreign;
return $this;
}
/* }}} */
/** Get/Set the debug property
* @param integer|null $debug Set the debug value
*/
public function debug ($debug=null)
/* {{{ */
{
$this->debugLog ("Entering debug (",$debug,")");
if ($debug === null)
return $this->debug;
if (! is_int ($debug))
$this->DBException ("Parameter debug invalid: not an integer");
$this->debug = $debug;
return $this;
}
/* }}} */
/** Get the sep property
*/
public function sep ()
{
/* {{{ */
return $this->sep;
/* }}} */
}
/** Get/Set the dsn property
* @param string|null $dsn Set the DSN property of PDO
*/
public function dsn ($dsn=null)
/* {{{ */
{
$this->debugLog ("Entering dsn (",$dsn,")");
if ($dsn === null)
return $this->dsn;
if (! is_string ($dsn))
$this->DBException ("Parameter dsn invalid : not a string");
$this->dsn = $dsn;
return $this;
}
/* }}} */
/** Get/Set the titles property
* @param array|null $titles The titles of the fields
* @param boolean|null $full Add the table name if the $full is set
*/
public function titles ($titles=null, $full = false)
/* {{{ */
{
$this->debugLog ("Entering titles (VALUE)");
if ($titles === null)
{
if ($full === false && $this->joinObject === null)
return $this->titles;
$titles = array ();
foreach ($this->titles as $key=>$val)
{
$titles[$this->tableprefix.$this->table.".".$key] = $val;
}
if ($this->joinObject !== null)
{
foreach ($this->joinObject as $obj)
{
$titles = array_merge ($titles, $obj->titles (null, true));
}
}
return $titles;
}
if (! is_array ($titles))
$this->DBException ("Parameter titles invalid: not an array");
foreach ($titles as $title=>$translation)
{
if (! is_string ($title))
$this->DBException ("Parameter titles invalid: title not a string");
if (mb_strlen ($title) > 64)
$this->DBException ("Parameter titles invalid: title too long");
if (! is_string ($translation))
$this->DBException ("Parameter titles invalid: ".
"translation not a string (table '$this->table',".
"field '$title')");
if (mb_strlen ($translation) > 64)
$this->DBException ("Parameter titles invalid: translation too long");
}
$this->titles = $titles;
return $this;
}
/* }}} */
/** Dump the configuration of the table in an array
*/
public function exportConf ()
/* {{{ */
{
return array ("table" => $this->table,
"tableprefix" => $this->tableprefix,
"fields" => $this->fields,
"primary" => $this->primary,
"unique" => $this->unique,
"foreign" => $this->foreign,
"titles" => $this->titles,
);
}
/* }}} */
/////////////////////////////////////
/// MANAGE THE REQUEST BY OOP ///
/////////////////////////////////////
/** The command to use
*/
private $command = "";
/** The DISTINCT option
*/
private $distinct = "";
/** The columns to display in SELECT, with the tables names and the separators
* correctely defined
*/
private $displayColumn = null;
/** The alias associated to each displayColumn
*/
private $displayAlias = array ();
/** Manage the joins
*/
private $joins = array ();
/** The WHERE expression
*/
private $whereExpression = array ();
/** The values for each parameter for the WHERE condition
*/
private $whereValues = array ();
/** The columns in GROUP BY condition
*/
private $groupByExpression = null;
/** The ORDER expression
*/
private $orderExpression = array ();
/** The LIMIT expression
*/
private $limitExpression = "";
/** The values to SET in INSERT/UPDATE
*/
private $setValues = array ();
/** The types to SET in INSERT/UPDATE
*/
private $setType = array ();
/** The dblayeroo object of the foreign keys tables to check
*/
private $setForeignObj = array ();
/** If we need to join this object with another one, save the second one in
* this property
*/
private $joinObject;
/** The debug depth (as we clone object, the depth is increased to debug
* easily the functions
*/
private $debugDepth = 1;
/** The sort order of select/order entries crossed the differents objects
*/
private static $sortOrder = 0;
/** The method to get a new sorti order acrossed the differents objects
*/
public function getSortOrder ()
/* {{{ */
{
++self::$sortOrder;
return "order".self::$sortOrder;
}
/* }}} */
/** Reinit the SQL request
*/
public function clearRequest ()
/* {{{ */
{
$this->debugLog ("Entering clearRequest ()");
if ($this->joinObject !== null)
{
foreach ($this->joinObject as $obj)
{
$obj->clearRequest ();
}
$this->joinObject = null;
}
$this->command = "";
$this->distinct = "";
$this->displayColumn = null;
$this->displayAlias = array ();
$this->joins = array ();
$this->whereExpression = array ();
$this->whereValues = array ();
$this->groupByExpression = null;
$this->orderExpression = array ();
$this->limitExpression = "";
$this->setValues = array ();
$this->setType = array ();
// The foreign keys can be defined in the constructor for all the
// modifications checks. No need to remove them
//$this->setForeignObj = array ();
}
/* }}} */
/** Define a new foreign object
* @param object $object The dblayeroo object to use for foreign constraint
* checks
*/
public function setForeignObj ($object)
/* {{{ */
{
$this->debugLog ("Entering setForeignObj (OBJECT)");
if (! is_object ($object))
$this->DBException ("Invalid setForeignObj parameter: not an object");
if (! is_subclass_of ($object, __CLASS__) &&
get_class ($object) !== "dblayeroo")
$this->DBException (
"Invalid object provided to setForeignObj (not dblayeroo object)");
if (! isset ($object->table))
$this->DBException (
"Invalid object provided to setForeignObj (no table defined)");
$this->setForeignObj[$object->tableprefix.$object->table] = $object;
return $this;
}
/* }}} */
/** Define the command to execute. Can be
* "SELECT", "INSERT", "DELETE", "UPDATE".
* @param string $command The command to execute
*/
public function command ($command)
/* {{{ */
{
$this->debugLog ("Entering command (",$command,")");
$allowed = array ("SELECT", "INSERT", "DELETE", "UPDATE");
if (! is_string ($command))
$this->DBException ("Invalid command provided (not string)");
$command = strtoupper ($command);
if (! in_array ($command, $allowed))
$this->DBException ("Invalid command provided (unknown command)");
$this->command = $command;
return $this;
}
/* }}} */
/** Alias of command ("SELECT")
*/
public function select ()
/* {{{ */
{
$this->command = "SELECT";
return $this;
}
/* }}} */
/** Alias of command ("INSERT")
*/
public function insert ()
/* {{{ */
{
$this->command = "INSERT";
return $this;
}
/* }}} */
/** Alias of command ("DELETE")
*/
public function delete ()
/* {{{ */
{
$this->command = "DELETE";
return $this;
}
/* }}} */
/** Alias of command ("UPDATE")
*/
public function update ()
/* {{{ */
{
$this->command = "UPDATE";
return $this;
}
/* }}} */
/** Set the DISTINCT option
*/
public function setDistinct ()
/* {{{ */
{
$this->distinct = "DISTINCT";
return $this;
}
/* }}} */
/** Changing the name displayColumns to displayAdd
* @param array|string|null $columnNames The columns name, separated by comma
* @deprecated 0.36
*/
public function displayColumn ($columnNames = array ())
/* {{{ */
{
return $this->displayAdd ($columnNames);
}
/* }}} */
/** Set the columns to display for the next SELECT request.
* The columns are ordered by the first added to the last added
* @param array|string|null $columnNames The columns name, separated by comma
* By default, display all the columns if this method is not called
* If the value is null or not provided or an empty array, do not display
* any field
* @param array|string|null $aliasNames Add the Aliases to the displayed
* columns
*/
public function displayAdd ($columnNames = array (), $aliasNames = array ())
/* {{{ */
{
$this->debugLog ("Entering displayAdd (", $columnNames, ",", $aliasNames,
")");
if (! is_string ($columnNames) && ! is_array ($columnNames))
$this->DBException (
"Invalid columnNames provided (not string and not array)");
if (! is_string ($aliasNames) && ! is_array ($aliasNames))
$this->DBException (
"Invalid aliasNames provided (not string and not array)");
if (is_string ($columnNames))
$columnNames = explode (",", $columnNames);
if (is_string ($aliasNames))
$aliasNames = explode (",", $aliasNames);
if (count ($aliasNames) && count ($aliasNames) !== count ($columnNames))
$this->DBException (
"The number of aliasNames are not the same as the number of columns");
// If there is no provided names, reset all the defined ones
if (count ($columnNames) === 0)
$this->displayColumn = array ();
foreach ($columnNames as $nb => $display)
{
$display = $name = trim ($display);
$pos = strpos ($display, "(");
if ($pos !== false)
{
$func = strtoupper (trim (substr ($name, 0, $pos)));
$name = trim (substr ($name, $pos+1, -1));
$separator = "";
if (in_array ($func, array ("AVG", "COUNT", "GROUP_CONCAT", "MAX",
"MIN","SUM")))
{
$aggregateFunction = true;
// Aggregate function. Add the non aggregate fields to the GROUP BY
// expression, if not already done
if ($this->groupByExpression === null)
{
$this->groupByExpression = $this->displayColumn;
}
if ($func === "GROUP_CONCAT" && ($pos = strpos ($name, ",'")))
{
// There is a comma: the developper add the separator string
$separator = addslashes (substr ($name, $pos + 2, -1));
$name = substr ($name, 0, $pos);
if ($this->driver === "sqlite" || $this->driver === "psql")
$separator = ",'$separator'";
elseif ($this->driver === "mysql")
$separator = " SEPARATOR '$separator'";
}
if ($func === "GROUP_CONCAT" && $this->driver === "psql")
$func = "string_agg";
}
}
$fieldName = $name;
$distinct = "";
if (stripos ($name, "DISTINCT ") === 0)
{
$distinct = "DISTINCT ";
$fieldName = substr ($name, strlen ("DISTINCT "));
}
if (! array_key_exists ($fieldName, $this->fields))
$this->DBException (sprintf (
"Invalid field to display '%s' : not defined in table", $fieldName));
$getSortOrder = $this->getSortOrder();
if (! isset ($func))
$this->displayColumn[$getSortOrder] =
$distinct.$this->sep.$fieldName.$this->sep;
else
$this->displayColumn[$getSortOrder] =
"$func($distinct$this->sep$fieldName$this->sep$separator)";
if ($this->groupByExpression !== null && ! isset ($aggregateFunction))
{
// Not a aggregate function, but groupBy is set : add the new field name
$this->groupByExpression[$getSortOrder] =
$this->sep.$fieldName.$this->sep;
}
unset ($aggregateFunction);
unset ($func);
if (key_exists ($nb, $aliasNames))
$this->displayAlias[$getSortOrder] = $aliasNames[$nb];
}
return $this;
}
/* }}} */
/** Return the name of the display field, with $this->sep
* Add the table prefix/name if full is set
* Allow :
* name,
* DISTINCT name,
* $this->sep.$name.$this->sep,
* DISTINCT $this->sep.$name.$this->sep,
* func(name),
* func(DISTINCT name),
* func($this->sep.$name.$this->sep),
* func(DISTINCT $this->sep.$name.$this->sep),
* func($this->sep.$this->tableprefix.$this->table.$this->sep.".".
* $this->sep.$name.$this->sep)
* func(DISTINCT $this->sep.$this->tableprefix.$this->table.$this->sep.".".
* $this->sep.$name.$this->sep)
* @param string $name The name of the field
* @param boolean|null $full Add the table prefix/name if set
*/
private function displayConvert ($name, $full = false)
/* {{{ */
{
$pos = strpos ($name, "(");
if ($pos !== false)
{
$func = strtoupper (trim (substr ($name, 0, $pos)));
$name = trim (substr ($name, $pos+1, -1));
}
$distinct = "";
if (strpos ($name, "DISTINCT ") === 0)
{
$distinct = "DISTINCT ";
$name = substr ($name, strlen ("DISTINCT "));
}
if ($name[0] !== $this->sep)
$name = $this->sep.$name;
if (! isset ($func) && substr ($name, -1) !== $this->sep)
$name = $name.$this->sep;
if ($full !== false)
$name = $this->sep.$this->tableprefix.$this->table.$this->sep.".".$name;
if (isset ($func))
$name = "$func($distinct$name)";
else
$name = "$distinct$name";
return $name;
}
/* }}} */
/** Get the columns set in the query by displayAdd. If the $full parameter
* is set, add the table prefix/name to the result.
* If the join object is set, ask to it the columns too
* @param boolean $full Add the table prefix/name if set
*/
public function displayGet ($full = false)
/* {{{ */
{
$columns = array ();
$displayColumn = array ();
if ($this->joinObject)
{
// The join object will be added at the end
$full = true;
}
// If empty $this->displayColumn list the fields of the table (like
// tablename.*)
if ($full !== false)
{
if ($this->displayColumn === null)
{
foreach (array_keys ($this->fields) as $name)
{
$displayColumn[$this->getSortOrder()] = $this->displayConvert ($name);
}
}
else
{
$displayColumn = $this->displayColumn;
}
}
else
{
if ($this->displayColumn === null)
{
foreach (array_keys ($this->fields) as $name)
{
$displayColumn[$this->getSortOrder()] = $this->displayConvert ($name);
}
}
else
{
$displayColumn = $this->displayColumn;
}
}
foreach ($displayColumn as $pos=>$name)
{
if ($full !== false)
$columns[$pos] = $this->displayConvert ($name, true);
else
$columns[$pos] = $this->displayConvert ($name);
if (key_exists ($pos, $this->displayAlias))
$columns[$pos] .= " AS ".
$this->sep.$this->displayAlias[$pos].$this->sep;
}
if ($this->joinObject)
{
foreach ($this->joinObject as $obj)
$columns = array_merge ($columns, $obj->displayGet (true));
}
ksort ($columns, SORT_NATURAL);
return $columns;
}
/* }}} */
/** Do a inner join between two dblayer objects
* The join array is a associated array with local field as key and distant
* field as value
* @param object $object The dblayeroo object to use for searching the join
* data
* @param array $joinArray The values to search for join
*/
public function joinInner ($object, $joinArray)
/* {{{ */
{
$this->debugLog ("Entering joinInner (OBJECT, JOINARRAY)");
return $this->joinReal ("INNER", $object, $joinArray);
}
/* }}} */
/** Do a left join between two dblayer objects
* The join array is a associated array with local field as key and distant
* field as value
* @param object $object The dblayeroo object to use for searching the join
* data
* @param array $joinArray The values to search for join
*/
public function joinLeft ($object, $joinArray)
/* {{{ */
{
$this->debugLog ("Entering joinLeft (OBJECT, JOINARRAY)");
return $this->joinReal ("LEFT", $object, $joinArray);
}
/* }}} */
/** Do a right join between two dblayer objects
* The join array is a associated array with local field as key and distant
* field as value
* @param object $object The dblayeroo object to use for searching the join
* data
* @param array $joinArray The values to search for join
*/
public function joinRight ($object, $joinArray)
/* {{{ */
{
$this->debugLog ("Entering joinRight (OBJECT, JOINARRAY)");
return $this->joinReal ("RIGHT", $object, $joinArray);
}
/* }}} */
/** Do the real join
* @param string $joinType The join type to use ("INNER", "LEFT", "RIGHT")
* @param object $object The dblayeroo object to use for searching the join
* data
* @param array $joinArray The values to search for join
*/
private function joinReal ($joinType, $object, $joinArray)
/* {{{ */
{
$this->debugLog ("Entering joinReal (",$joinType,", OBJECT, JOINARRAY)");
if (! is_string ($joinType))
$this->DBException ("Invalid joinType provided to join (not string)");
if (! in_array ($joinType, array ("INNER", "LEFT", "RIGHT")))
$this->DBException ("Invalid joinType provided to join (not known)");
if (! is_object ($object))
$this->DBException ("Invalid object provided to join (not object)");
if (! is_subclass_of ($object, __CLASS__) &&
get_class ($object) !== "dblayeroo")
$this->DBException (
"Invalid object provided to join (not dblayeroo object)");
if ($this->dsn !== $object->dsn)
$this->DBException (
"DSN different : don't support JOIN between databases");
if (! is_array ($joinArray))
$this->DBException ("Invalid joinArray provided (not array)");
if (empty ($joinArray))
$this->DBException ("Invalid joinArray provided (empty array)");
foreach ($joinArray as $fieldLocal=>$fieldToJoin)
{
if (! array_key_exists ($fieldLocal, $this->fields))
$this->DBException (sprintf (
"Invalid field to join '%s' : not defined in Local table",
$fieldLocal));
if (! array_key_exists ($fieldToJoin, $object->fields))
$this->DBException (sprintf (
"Invalid field to join '%s' : not defined in Distant table",
$fieldToJoin));
}
if (! isset ($object->table) || $object->table === null ||
trim ($object->table) === "")
$this->DBException ("No table defined in the Join object");
if (! isset ($this->table) || $this->table === null ||
trim ($this->table) === "")
$this->DBException ("No table defined in the local object");
if (! isset ($object->tableprefix) || $object->tableprefix === null)
$this->DBException ("No tableprefix defined in the Join object");
$this->joinObject[] = $object;
$tmp = "";
foreach ($joinArray as $fieldLocal=>$fieldToJoin)
{
if ($tmp !== "")
$tmp .= " AND ";
$tmp .=
$this->sep.$this->tableprefix.$this->table.$this->sep.".".
$this->sep.$fieldLocal.$this->sep.
"=".
$this->sep.$object->tableprefix.$object->table.$this->sep.".".
$this->sep.$fieldToJoin.$this->sep;
}
// Correct the displayQuery in the main display fields with the display
// fields of object
$this->joins[] = "$joinType JOIN ".
$this->sep.$object->tableprefix.$object->table.$this->sep." ON $tmp";
return $this;
}
/* }}} */
/** Get the join SQL part with recursive call of the child joins
* @param object|null $joinObject The joinObject to examine too
*/
public function joinsGet ($joinObject = null)
/* {{{ */
{
$joins = "";
if ($joinObject === null)
$joinObject = $this;
if ($joinObject->joins !== array ())
$joins = implode ("\n ", $joinObject->joins);
if ($joinObject->joinObject !== null)
{
foreach ($joinObject->joinObject as $obj)
$joins .= "\n ".$joinObject->joinsGet ($obj);
}
return trim ($joins);
}
/* }}} */
/** Set a new WHERE expression value
* @param string $field The field to check
* @param string $operator The operator ("=", "<=", ">=", "!=", "NOT LIKE",
* "LIKE", "IS NULL", "REGEXP", "NOT REGEXP")
* @param string|null $value The value to search ("" if not provided)
*/
public function whereAdd ($field, $operator, $value = "")
/* {{{ */
{
$this->debugLog ("Entering whereAdd (",$field,", ",$operator,", ",$value,
")");
if (! is_string ($field))
$this->DBException ("Invalid field provided (not string)");
if (! is_string ($operator))
$this->DBException ("Invalid operator provided (not string)");
if (! is_string ($value) && ! is_null ($value) && ! is_integer ($value))
$this->DBException ("Invalid value provided (not string nor null ".
"nor integer)");
if (! array_key_exists ($field, $this->fields))
$this->DBException (sprintf (
"Invalid field to whereAdd '%s' : not defined in table", $field));
$operator = strtoupper ($operator);
$allowed = array ("=", "<=", ">=", "!=", "LIKE", "NOT LIKE", "IS NULL",
"IS NOT NULL", "REGEXP", "NOT REGEXP");
if (! in_array ($operator, $allowed))
$this->DBException ("Invalid operator provided (unknown operator)");
// TODO : Check if the value is corresponding to the type of the column
if (count ($this->whereExpression) &&
end ($this->whereExpression) !== "AND" &&
end ($this->whereExpression) !== "OR" &&
end ($this->whereExpression) !== "(")
$this->whereExpression[] = "AND";
if ($this->driver === "pgsql" && $operator === "REGEXP")
$operator = "~";
if ($this->driver === "pgsql" && $operator === "NOT REGEXP")
$operator = "!~";
if ($operator === "IS NULL" || $operator === "IS NOT NULL")
{
// Operator without parameter
$this->whereExpression[] =
$this->sep.$this->tableprefix.$this->table.$this->sep.".".
$this->sep.$field.$this->sep." ".$operator;
}
else
{
// Operator with parameter
$hash = md5 ("$field, $operator, $value");
$this->whereExpression[] =
$this->sep.$this->tableprefix.$this->table.$this->sep.".".
$this->sep.$field.$this->sep." ".$operator." :$hash";
$this->whereValues[$hash] = array (
"field"=>$field,
"fieldfull"=> $this->tableprefix.$this->table.".".$field,
"operator"=>$operator,
"value"=>$value,
"hash"=>$hash,
"type"=>$this->fieldTypeLight ($field));
}
return $this;
}
/* }}} */
/** Add a new AND to the WHERE expression
*/
public function whereAddAND ()
/* {{{ */
{
$this->debugLog ("Entering whereAddAND ()");
if (count ($this->whereExpression) === 0)
$this->DBException ("Can not add AND as there is no previous expression");
$this->whereExpression[] = "AND";
return $this;
}
/* }}} */
/** Add a new OR to the WHERE expression
*/
public function whereAddOR ()
/* {{{ */
{
$this->debugLog ("Entering whereAddOR ()");
if (count ($this->whereExpression) === 0)
$this->DBException ("Can not add OR as there is no previous expression");
$this->whereExpression[] = "OR";
return $this;
}
/* }}} */
/** Add a new Open Parenthesis to the WHERE expression
*/
public function whereAddParenthesisOpen ()
/* {{{ */
{
$this->debugLog ("Entering whereAddParenthesisOpen ()");
if (count ($this->whereExpression) &&
end ($this->whereExpression) !== "AND" &&
end ($this->whereExpression) !== "OR" &&
end ($this->whereExpression) !== "(")
$this->whereExpression[] = "AND";
$this->whereExpression[] = "(";
return $this;
}
/* }}} */
/** Add a new Close Parenthesis to the WHERE expression
*/
public function whereAddParenthesisClose ()
/* {{{ */
{
$this->debugLog ("Entering whereAddParenthesisClose ()");
$this->whereExpression[] = ")";
return $this;
}
/* }}} */
/** Get the WHERE clause of the object.
* If the joinObject is set, return all the WHERE clauses
*/
public function whereGetExpression ()
/* {{{ */
{
$whereExpression = $this->whereExpression;
if ($whereExpression === null)
$whereExpression = array ();
if ($this->joinObject !== null)
{
foreach ($this->joinObject as $obj)
{
$exp = $obj->whereGetExpression ();
if (count ($whereExpression) && count ($exp))
$whereExpression[] = "AND";
if (count ($exp))
$whereExpression[] = "(";
$whereExpression = array_merge ($whereExpression,
$exp);
if (count ($exp))
$whereExpression[] = ")";
}
}
return $whereExpression;
}
/* }}} */
/** Get the WHERE values of the object.
* If the joinObject is set, return all the WHERE clauses with AND and
* parenthesis
*/
public function whereGetValues ()
/* {{{ */
{
$whereValues = $this->whereValues;
if ($whereValues === null)
$whereValues = array ();
if ($this->joinObject !== null)
{
foreach ($this->joinObject as $obj)
{
$whereValues = array_merge ($whereValues,
$obj->whereGetValues ());
}
}
return $whereValues;
}
/* }}} */
/** Add a new ORDER sort. The multiple ORDERS are used from the first added to
* the last added
* @param string $field The field to sort
* @param string|null $sort The sort order ("ASC", "DESC");
*/
public function orderAdd ($field, $sort = "ASC")
/* {{{ */
{
$this->debugLog ("Entering orderAdd (",$field,", ",$sort,")");
if (! is_string ($field))
$this->DBException ("Invalid field provided (not string)");
if (! is_string ($sort))
$this->DBException ("Invalid sort provided (not string)");
$sort = strtoupper ($sort);
if (! in_array ($sort, array ("ASC", "DESC")))
$this->DBException ("Invalid sort provided (not ASC nor DESC)");
if (! array_key_exists ($field, $this->fields))
$this->DBException (sprintf (
"Invalid field to orderAdd '%s' : not defined in table", $field));
$this->orderExpression[$this->getSortOrder()] =
$this->sep.$field.$this->sep." ".$sort;
return $this;
}
/* }}} */
/** Get the ORDER fields defined. If a joinObject is set with ORDER statement,
* return the joinObject order with its tableprefix/name in addition of
* the ones of this object
* If the parameter $full is set, add the table prefix/name to the result
* @param boolean|null $full Add the table prefix/name if set
*/
public function orderGet ($full=false)
/* {{{ */
{
$order = array ();
if ($this->joinObject)
$full = true;
foreach ($this->orderExpression as $pos=>$o)
{
if ($full !== false)
$order[$pos] = $this->sep.$this->tableprefix.$this->table.$this->sep.
".".$o;
else
$order[$pos] = $o;
}
if ($this->joinObject)
{
foreach ($this->joinObject as $obj)
$order = array_merge ($order, $obj->orderGet (true));
}
ksort ($order, SORT_NATURAL);
return $order;
}
/* }}} */
/** Get the GROUP BY fields defined. If a joinObject is set with GROUP BY
* statement, return the joinObject order with its tableprefix/name in
* addition of the ones of this object
* If the parameter $full is set, add the table prefix/name to the result
* @param boolean|null $full Add the table prefix/name if set
*/
public function groupByGet ($full=false)
/* {{{ */
{
$groupBy = array ();
if ($this->joinObject)
$full = true;
if (is_array ($this->groupByExpression))
{
foreach ($this->groupByExpression as $pos=>$o)
{
if ($full !== false)
$groupBy[$pos] = $this->sep.$this->tableprefix.$this->table.$this->sep
.".".$o;
else
$groupBy[$pos] = $o;
}
}
if ($this->joinObject)
{
foreach ($this->joinObject as $obj)
{
$ext = $obj->groupByGet (true);
if ($ext !== null)
$groupBy = array_merge ($groupBy, $obj->groupByGet (true));
}
}
ksort ($groupBy, SORT_NATURAL);
return $groupBy;
}
/* }}} */
/** Define a LIMIT for the request.
* To use only the nbLines, put a 0 on startLine
* @param integer $startLine The starting line in the result list
* @param integer $nbLines The number of lines to return
*/
public function limit ($startLine, $nbLines)
/* {{{ */
{
$this->debugLog ("Entering limit (",$startLine,", ",$nbLines,")");
if (! preg_match ("/^\d+$/", $startLine))
$this->DBException (sprintf (
"Invalid startLine to limit '%d': not numerical", $startLine));
if (! preg_match ("/^\d+$/", $nbLines))
$this->DBException (sprintf (
"Invalid nbLines to limit '%d': not numerical", $nbLines));
$startLine = intval ($startLine);
$nbLines = intval ($nbLines);
if ($startLine !== 0)
$this->limitExpression = "$startLine,$nbLines";
else
$this->limitExpression = "$nbLines";
return $this;
}
/* }}} */
/** Define a LIMIT for the request.
* @param integer $nbLines The number of lines to return
*/
public function limitLines ($nbLines)
/* {{{ */
{
$this->debugLog ("Entering limitLines (",$nbLines,")");
if (! preg_match ("/^\d+$/", $nbLines))
$this->DBException (sprintf (
"Invalid nbLines to limit '%d': not numerical", $nbLines));
$nbLines = intval ($nbLines);
$this->limitExpression = "$nbLines";
return $this;
}
/* }}} */
/** Set INSERT/UPDATE values
* - The provided array must be an associative array, the field name must be
* provided as key and the value as value. Each field=>val will be updated
* @param array $values The values to INSERT or UPDATE
*/
public function setValues ($values)
/* {{{ */
{
$this->debugLog ("Entering setValues (",$values,")");
if (! is_array ($values))
$this->DBException ("Invalid values to setValues : not an array");
$associative = null;
$tmpValues = array ();
$tmpType = array ();
foreach ($values as $key=>$val)
{
if (! array_key_exists ($key, $this->fields))
$this->DBException (sprintf (
"Invalid field to setValues '%s' : not defined in table", $key));
if (! is_string ($val) && ! is_int ($val) && ! is_null ($val))
$this->DBException (sprintf (
"Invalid field to setValues '%s': not string and not integer", $key));
$tmpValues[$key] = $val;
$tmpType[md5 ("$key, $val")] = $this->fieldTypeLight ($key);
$this->debugLog ("setValues : Type for $key = ".
$this->fieldTypeLight ($key));
}
$this->setValues = $tmpValues;
$this->setType = $tmpType;
return $this;
}
/* }}} */
/** Create the SQL request
*/
private function createRequest ()
/* {{{ */
{
if ($this->sep === "")
$this->DBException (dgettext ("domframework", "Database not connected"));
if ($this->table === null)
$this->DBException (dgettext ("domframework",
"No table name defined to insert in the table"));
if ($this->unique === null && $this->primary === null)
$this->DBException (dgettext ("domframework",
"Unique fields of table are not defined"));
if (! is_array ($this->unique) && $this->unique !== null)
$this->DBException (dgettext ("domframework",
"The unique configuration is not an array"));
switch ($this->command)
{
case "SELECT":
$sql = "SELECT";
if ($this->distinct !== "")
$sql .= " ".$this->distinct;
if ($this->joinObject)
$displayColumns = implode (",", $this->displayGet (true));
elseif (count ($this->displayGet (false)))
$displayColumns = implode (",", $this->displayGet (false));
else
$displayColumns = "*";
if ($this->joinObject)
$order = $this->orderGet (true);
else
$order = $this->orderGet (false);
$sql .= " $displayColumns\n FROM $this->sep$this->tableprefix".
"$this->table$this->sep";
$joinsExpression = $this->joinsGet ();
if ($joinsExpression !== "")
$sql .= "\n ".$this->joinsGet ();
$whereGetExpression = $this->whereGetExpression ();
if (! empty ($whereGetExpression))
$sql .= "\n WHERE ". implode (" ", $whereGetExpression);
$groupByExpression = $this->groupByGet ();
if (! empty ($groupByExpression))
$sql .= "\n GROUP BY ". implode (",", $groupByExpression);
if (count ($order))
$sql .= "\n ORDER BY ". implode (",", $order);
if (! empty ($this->limitExpression))
$sql .= "\n LIMIT $this->limitExpression";
// No set Values for SELECT
$this->setValues = array ();
break;
case "INSERT":
$sql = "INSERT INTO $this->sep$this->tableprefix$this->table$this->sep (";
if (empty ($this->setValues))
$this->DBException ("No values set to add in INSERT");
$i = 0;
foreach ($this->setValues as $key=>$val)
{
if ($i > 0)
$sql .= ",";
$sql .= $this->sep.$key.$this->sep;
$i++;
}
$sql .= ") VALUES (";
$i = 0;
foreach ($this->setValues as $key=>$val)
{
if ($i > 0)
$sql .= ",";
$sql .= ":".md5 ("$key, $val");
$i++;
}
$sql .= ")";
// No WHERE in INSERT : remove the WHERE parameters
$this->whereExpression = array ();
$this->whereValues = array ();
break;
case "DELETE":
$sql = "DELETE FROM $this->sep$this->tableprefix$this->table$this->sep";
$whereGetExpression = $this->whereGetExpression ();
if (! empty ($whereGetExpression))
$sql .= "\n WHERE ". implode (" ", $whereGetExpression);
if (! empty ($this->orderExpression))
$sql .= " ORDER BY ". implode (",", $this->orderExpression);
if (! empty ($this->limitExpression))
$sql .= " LIMIT $this->limitExpression";
// No set Values for DELETE
$this->setValues = array ();
break;
case "UPDATE":
$sql = "UPDATE $this->sep$this->tableprefix$this->table$this->sep";
if (empty ($this->setValues))
$this-> DBException ("No values to set in UPDATE");
$sql .= " SET ";
$i = 0;
foreach ($this->setValues as $key=>$val)
{
if ($i > 0)
$sql .= ",";
$hash = md5 ("$key, $val");
$sql .= $this->sep.$key.$this->sep."=:".$hash;
$i++;
}
$whereGetExpression = $this->whereGetExpression ();
if (! empty ($whereGetExpression))
$sql .= "\n WHERE ". implode (" ", $whereGetExpression);
if (! empty ($this->orderExpression))
$sql .= " ORDER BY ". implode (",", $this->orderExpression);
if (! empty ($this->limitExpression))
$sql .= " LIMIT $this->limitExpression";
break;
default:
$this->DBException ("No command specified");
}
return $sql;
}
/* }}} */
/** Prepare the request with the associated entries.
* If textForm is true, return a string to display what will be done
* If textForm is false, return a statement
* @param string $sql The SQL request to prepare
* @param boolean $textForm If true, return the result. If false prepare
* really the request
*/
private function prepareRequest ($sql, $textForm)
/* {{{ */
{
$text = "";
if (!$textForm)
{
try
{
$st = self::$instance[$this->dsn]->prepare ($sql);
}
catch (\Exception $e)
{
$this->DBException ($e->getMessage ());
}
}
foreach ($this->whereGetValues () as $hash=>$val)
{
$field = $val["field"];
$value = $val["value"];
$type = $val["type"];
$text .= "DEBUG BIND WHERE : $hash ($field)->$value ";
if ($value === null) $text .= "NULL (null)\n";
elseif ($type === "integer") $text .= "(integer)\n";
elseif ($type === "varchar") $text .= "(varchar)\n";
elseif ($type === "datetime") $text .= "(datetime)\n";
elseif ($type === "date") $text .= "(date)\n";
else
{
$text .= "(UNKNOWN)\n";
$this->DBException ("TO BE DEVELOPPED : type=".$type);
}
if (!$textForm)
{
if ($value === null)
$st->bindValue (":$hash", $value, \PDO::PARAM_NULL);
elseif ($type === "integer")
$st->bindValue (":$hash", $value, \PDO::PARAM_INT);
elseif ($type === "varchar")
$st->bindValue (":$hash", "$value", \PDO::PARAM_STR);
elseif ($type === "datetime")
$st->bindValue (":$hash", $value, \PDO::PARAM_STR);
elseif ($type === "date")
$st->bindValue (":$hash", $value, \PDO::PARAM_STR);
else
$this->DBException ("prepareRequest:whereValues TO BE DEVELOPPED : ".
"type=$type");
}
}
foreach ($this->setValues as $field=>$value)
{
$hash = md5 ("$field, $value");
if (! array_key_exists ($hash, $this->setType))
$this->DBException (sprintf ("Field '%s' not found in Type list",
$field));
$type = $this->setType[$hash];
$text .= "DEBUG BIND SET : $hash ($field)->$value ";
if ($value === null) $text .= "NULL (null)\n";
elseif ($type === "integer") $text .= "(integer)\n";
elseif ($type === "varchar") $text .= "(varchar)\n";
elseif ($type === "datetime") $text .= "(datetime)\n";
elseif ($type === "date") $text .= "(date)\n";
else
{
$text .= "(UNKNOWN)\n";
$this->DBException ("TO BE DEVELOPPED : ".$fields[$field][0]);
}
if (!$textForm)
{
if ($value === null)
$st->bindValue (":$hash", $value, \PDO::PARAM_NULL);
elseif ($this->setType[$hash] === "integer")
$st->bindValue (":$hash", $value, \PDO::PARAM_INT);
elseif ($this->setType[$hash] === "varchar")
$st->bindValue (":$hash", "$value", \PDO::PARAM_STR);
elseif ($this->setType[$hash] === "datetime")
$st->bindValue (":$hash", $value, \PDO::PARAM_STR);
elseif ($this->setType[$hash] === "date")
$st->bindValue (":$hash", $value, \PDO::PARAM_STR);
else
$this->DBException ("prepareRequest:setValues TO BE DEVELOPPED : ".
$this->fields[$field][0]);
}
}
if ($textForm)
return $text;
else
return $st;
}
/* }}} */
/** Return the query that will be executed
*/
public function getDisplayQuery ()
/* {{{ */
{
$text = "";
$sql = $this->createRequest ();
$text .= "$sql";
$prep = $this->prepareRequest ($sql, true);
if ($prep !== "")
$text .= "\n$prep";
return $text;
}
/* }}} */
/** Check the provided values which will be inserted or updated against the
* database structure.
* In update, do not forget to define the whereAdd parameters !
* @param array $values The values to test
* @param boolean|null $update if true UPDATE request, else INSERT request
* @return array The errors found by field
*/
public function verify ($values, $update = false)
/* {{{ */
{
$update = !! $update;
$errors = array ();
if ($this->table === null)
throw new \Exception ("No table name defined", 500);
if ($this->primary === null)
throw new \Exception ("No primary key defined for table '$this->table'",
500);
if ($update === false)
{
// INSERT mode
if (! array_key_exists ($this->primary, $values))
$values[$this->primary] = null;
// - Look if all the NOT NULL fields are filled
foreach ($this->fields as $field=>$params)
{
if (in_array ("not null", $params) &&
! array_key_exists ($field, $values))
$errors[$field] = sprintf (dgettext ("domframework",
"Mandatory field '%s' not provided"),
$field);
elseif (! in_array ("not null", $params) &&
! array_key_exists ($field, $values))
continue;
}
}
else
{
// UPDATE mode
}
// INSERT or UPDATE mode
// - Check the validity of the content of the fields. Should be already done
// by the application, so just throw an Exception if the error is raised
foreach ($this->fields as $field=>$params)
{
$this->debugLog (" verify the field validity [$field]");
if ($update !== false && ! array_key_exists ($field, $values))
// In case of UPDATE, the values are already stored. We can skip the
// test if the user don't want to modify a field (and do not provide the
// value
continue;
if (! in_array ("not null", $params) &&
! array_key_exists ($field, $values))
continue;
if (in_array ("not null", $params) &&
! array_key_exists ($field, $values))
{
$errors[$field] = sprintf (dgettext ("domframework",
"Field '%s' mandatory and not provided"), $field);
continue;
}
if (! is_string ($values[$field]) && ! is_integer ($values[$field]) &&
! is_null ($values[$field]))
$errors[$field] = sprintf (dgettext ("domframework",
"Field '%s' not a string nor a integer"), $field);
switch ($this->fieldTypeLight ($field))
{
case "integer":
if (strspn ($values[$field], "0123456789") !== strlen ($values[$field]))
$errors[$field] = sprintf (dgettext ("domframework",
"Field '%s' not in integer format"), $field);
break;
case "varchar":
$length = $this->fieldLength ($field);
if (mb_strlen ($values[$field]) > $length)
$errors[$field] = sprintf (dgettext ("domframework",
"Field '%s' : Data field too long"), $field);
break;
case "date":
if (! preg_match ("#^\d{4}-\d{2}-\d{2}$#", $values[$field]))
$errors[$field] = sprintf (dgettext ("domframework",
"Field '%s' not in date format"), $field);
break;
case "datetime":
if (! preg_match ("#^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$#",
$values[$field]))
$errors[$field] = sprintf (dgettext ("domframework",
"Field '%s' not in datetime format"), $field);
break;
case "time":
if (! preg_match ("#^\d{2}:\d{2}:\d{2}$#", $values[$field]))
$errors[$field] = sprintf (dgettext ("domframework",
"Field '%s' not in time format"), $field);
break;
default:
$errors[$field] = sprintf (dgettext ("domframework",
"Field '%s' : invalid SQL type (%s) in ".
"\$this->fields "), $field,
$this->fieldTypeLight ($field));
}
}
// - Check the unique entries (if defined)
// The primary key is always unique : add it if not set by the user
$this->debugLog (" verify the unique constraint");
$uniques = $this->unique;
if (! is_array ($uniques))
$uniques = array ();
if (! in_array ($this->primary, $uniques))
$uniques = array_merge (array ($this->primary), $uniques);
$setValues = $values;
$foundImpactedLines = 0;
foreach ($uniques as $k=>$columns)
{
if ($update)
{
// Can not update multiple UNIQUE rows with the same value
$cols = explode (",", $columns);
foreach ($cols as $col)
{
if (!key_exists ($col, $setValues))
continue;
// One column to set is a unique column : check if the where clause
// is filtering more than one entry. If there is more than one
// entry, generate an exception
$this->debugLog ("CLONE because of checking update unique fields");
$objTmp = clone $this;
$objTmp->debugDepth++;
$objTmp->clearRequest ();
$objTmp->Select ();
$objTmp->displayAdd ($this->primary);
$objTmp->displayAdd ($columns);
$objTmp->whereValues = $this->whereValues;
$objTmp->whereExpression = $this->whereExpression;
$objTmp->limitLines (3);
$resUpdate = $objTmp->execute ();
unset ($objTmp);
if (count ($resUpdate) > 1)
$this->DBException (sprintf (dgettext ("domframework",
"Can't update multiple rows with unique value on col '%s'"),
$col));
elseif (count ($resUpdate) === 1)
$foundImpactedLines++;
}
if ($foundImpactedLines === 0)
{
// There is no row available with the WHERE clause provided
// Skip all the UNIQUE tests as there will not have any modification
break;
}
}
if (! array_key_exists ($this->primary, $setValues))
$setValues[$this->primary] = null;
$this->debugLog ("CLONE to check primary and unique constraint");
$objTmp = clone $this;
$objTmp->debugDepth++;
$objTmp->clearRequest ();
$objTmp->Select ();
$objTmp->displayAdd ($this->primary);
if (is_array ($columns))
{
// Multiple columns in unique
$objTmp->debugLog (" verify the unique multiple [",
implode (",", $columns),"]");
foreach ($columns as $column)
{
if (! array_key_exists ($column, $setValues))
{
if ($update !== false && array_key_exists (0, $resUpdate))
{
// In UPDATE, if a column is not modified (doesn't appears in
// setValues), use the old value to search
$valTest = $resUpdate[0][$column];
$objTmp->whereAdd ($column, "=", $valTest);
}
else
$errors[$column] = sprintf (dgettext ("domframework",
"No column '%s' defined but must be unique !"), $column);
}
else
$objTmp->whereAdd ($column, "=", $setValues[$column]);
}
}
else
{
// One column in unique
$objTmp->debugLog (" verify the unique one column [$columns]");
if (! array_key_exists ($columns, $setValues))
$errors[$columns] = sprintf (dgettext ("domframework",
"No column '%s' defined but must be unique !"), $columns);
else
$objTmp->whereAdd ($columns, "=", $setValues[$columns]);
}
if ($update && array_key_exists (0, $resUpdate))
{
// If the value already exists, check if it is the same (the SQL can
// overwrite with the same value)
// If it is not the same, produce an error
$objTmp->whereAdd ($this->primary, "!=",
$resUpdate[0][$this->primary]);
}
if (count ($errors) == 0 && count ($objTmp->execute ()))
$this->DBException (dgettext ("domframework",
"An entry with these values already exists"));
unset ($objTmp);
}
// - If foreign keys, check if the value is set in the constraint
foreach ($this->foreign as $fields=>$params)
{
foreach (explode (",", $fields) as $field)
{
// Do not test in update if the field is not provided as it was already
// recorded
if ($update === true && ! array_key_exists ($field, $values))
continue 2;
if (! array_key_exists ($field, $values))
{
$errors[$field] = sprintf (dgettext ("domframework",
"The field '%s' must be test on foreign, but is not provided"),
$field);
continue;
}
if (! array_key_exists ($params[0], $this->setForeignObj))
$this->DBException (sprintf (dgettext ("domframework",
"No foreign object configured to test the foreign key for table ".
"'%s'"), $this->table));
}
$this->debugLog ("CLONE to check foreign constraint [$fields]");
$objTmp = clone $this->setForeignObj[$params[0]];
$objTmp->debug = $this->debug;
$objTmp->debugDepth++;
$objTmp->clearRequest ();
$objTmp->Select ();
$objTmp->displayAdd ($objTmp->primary);
$parentField = explode (",", $params[1]);
foreach (explode (",", $fields) as $key=>$field)
$objTmp->whereAdd ($parentField[$key], "=", $values[$field]);
if (count ($objTmp->execute ()) === 0)
$errors[$field] = sprintf (dgettext ("domframework",
"The value of the foreign key '%s' doesn't exists in foreign table"),
$field);
}
return $errors;
}
/* }}} */
/** Check the values before doing really the modification of the database
* @param boolean|null $update if true UPDATE request, else INSERT request
*/
public function checkValues ($update = false)
/* {{{ */
{
$this->debugLog ("Entering checkValues (",$update,")");
$update = !! $update;
$values = $this->setValues;
$errors = $this->verify ($values, $update);
if (count ($errors))
$this->DBException (reset ($errors));
$this->debugLog ("End of checkValues (",$update,") : Nothing in error");
}
/* }}} */
/** Execute the pre-defined query
* Return the content array if SELECT command is choosed
* Return the Last ID if INSERT command is choosed
* Return the number of modified lines for UPDATE/DELETE command
*/
public function execute ()
/* {{{ */
{
$this->debugLog ("Entering execute ()");
switch ($this->command)
{
case "SELECT":
break;
case "INSERT":
$this->checkValues (false);
break;
case "UPDATE":
$this->checkValues (true);
break;
case "DELETE":
break;
default:
$this->DBException ("execute : command not defined : no check");
}
$this->debugLog ("Entering createRequest ()");
$sql = $this->createRequest ();
$this->debugLog ("Entering prepareRequest (XXX, ",false,")");
$st = $this->prepareRequest ($sql, false);
$this->debugLog ("'",$this->getDisplayQuery (),"'");
$st->execute ();
switch ($this->command)
{
case "SELECT":
$result = $st->fetchAll (\PDO::FETCH_NUM);
// There is no fetchAll corresponding to the columnName->value. Assign the
// name to the value by index.
// FETCH_ASSOC doesn't work in empty left join (return NULL instead of
// the filled value)
$fieldsAll = $this->fieldsAll (false);
if ($this->joinObject === null && count ($this->displayGet (false)))
{
// Remove the table name as there is no collisions risk
$columns = array ();
foreach ($this->displayGet (false) as $col)
{
$col = str_replace ($this->sep.$this->tableprefix.$this->table.
$this->sep.".", "", $col);
if ($col[0] === $this->sep)
$col = substr ($col, 1);
if (substr ($col, -1) === $this->sep &&
strpos ($col, " AS ".$this->sep) === false)
{
$col = substr ($col, 0, -1);
}
$columns[] = $col;
}
}
else
{
$columns = $this->displayGet (true);
}
$columns = array_values ($columns);
foreach ($result as $rownb=>$row)
{
foreach ($row as $colNb=>$val)
{
// Harmonize the fetchAll result between all the databases drivers
$pos = strpos ($columns[$colNb], "(");
if ($pos)
{
// Function. The function that return an int must be added in this
// list, to cast correctely the value
$func = strtoupper (trim (substr ($columns[$colNb], 0, $pos)));
if (in_array ($func, array ("AVG", "COUNT", "MAX", "MIN", "SUM")))
$val = intval ($val);
}
elseif (strtolower ($fieldsAll[$columns[$colNb]][0]) ===
"integer")
$val = intval ($val);
if (($pos = strpos ($columns[$colNb], " AS ".$this->sep)) !== false)
{
$pos += strlen (" AS ".$this->sep);
$colName = substr ($columns[$colNb], $pos, -1);
}
else
$colName = str_replace ($this->sep, "", $columns[$colNb]);
$result[$rownb][$colName] = $val;
unset ($result[$rownb][$colNb]);
}
}
return $result;
case "INSERT":
// PostGres need the name of the column autoincrement to return something
// If there is no autoincrement column, do not request the lastInsertId
$autoInc = null;
if ($this->driver === "pgsql")
{
foreach ($this->fields as $col=>$params)
{
if (in_array ("autoincrement", $params))
{
$autoInc = $col;
break;
}
}
if ($autoInc !== null)
$autoInc = $this->table."_${col}_seq";
}
$this->debugLog ("INSERT: lastInsertId=",
self::$instance[$this->dsn]->lastInsertId ($autoInc));
return self::$instance[$this->dsn]->lastInsertId ($autoInc);
case "UPDATE":
case "DELETE":
return $st->rowCount ();
default:
$this->DBException ("execute : command not defined : no RC");
}
}
/* }}} */
/** Error management
* @param string $message The message to throw in the exception
*/
public function DBException ($message)
/* {{{ */
{
$backtrace = debug_backtrace ();
if ($this->debug)
{
require_once ("domframework/backtrace.php");
\backtrace::show (debug_backtrace ());
}
if (! array_key_exists (1, $backtrace))
unset ($backtrace);
else
{
$backtrace = end ($backtrace);
$filename = basename ($backtrace["file"]);
$line = $backtrace["line"];
$method = $backtrace["function"];
$message .= " ($filename:$line [$method])";
}
throw new \Exception ($message, 500);
}
/* }}} */
/** Debug function
* @param mixed ...$message The message to display in debug
*/
private function debugLog ($message)
/* {{{ */
{
if ((!!$this->debug) === false)
return;
echo str_repeat ("=", $this->debugDepth * 2)." ";
foreach (func_get_args() as $nb=>$arg)
{
if (is_string ($arg) || is_int ($arg))
echo $arg;
elseif (is_bool ($arg) && $arg === true)
echo "true";
elseif (is_bool ($arg) && $arg === false)
echo "false";
elseif (is_array ($arg))
print_r ($arg);
elseif (is_null ($arg))
echo "NULL";
else
die ("DEBUG TYPE UNKNOWN ".gettype ($arg)."\n");
}
echo "\n";
}
/* }}} */
}