*/ // 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) { if (! is_string ($display)) $this->DBException ( "displayAdd: The display column name #$nb is not a string"); $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) { // Set to empty array to demonstrate that a GROUP BY is needed, but // there is no already defined displayed Columns. // Used if the group by is called from join object if ($this->displayColumn === null) $this->groupByExpression = array (); else $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 === "pgsql") $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 = 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 = null; if ($this->joinObject) $full = true; if ($this->joinObject) { foreach ($this->joinObject as $obj) { $ext = $obj->groupByGet (true); if ($ext !== null) { if (! is_array ($groupBy)) $groupBy = array (); $groupBy = array_merge ($groupBy, $ext); } } } // If there is some GROUP BY in join Object, but not in this object, all // the displayed fields must be added to be added in the GROUP BY if (is_array ($groupBy) && (is_null ($this->groupByExpression) || (is_array ($this->groupByExpression) && empty ($this->groupByExpression)) )) $this->groupByExpression = $this->displayColumn; if (is_array ($this->groupByExpression)) { if (! is_array ($groupBy)) $groupBy = array (); 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 (is_array ($groupBy)) 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 (in_array ("not null", $params) && ! in_array ("autoincrement", $params) && $values[$field] === null) { $errors[$field] = sprintf (dgettext ("domframework", "Field '%s' null and defined as NOT NULL and ". "not Autoincrement"), $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); // Do not check the format if the value to store is null. It will never // matche any format. if ($values[$field] === null) continue; 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 if (is_string ($columns)) $cols = explode (",", $columns); elseif (is_array ($columns)) $cols = $columns; else $this->DBException (dgettext ("domframework", "Unique def is not a string or an array")); 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 // In case of Alias, remove the $this->sep too $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); } if (strpos ($col, " AS ".$this->sep)) { // remove the separator before the AS $col = substr ($col, 0, strpos ($col, " AS ".$this->sep) -1). substr ($col, strpos ($col, " AS ".$this->sep)); } $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); } else { $name = $columns[$colNb]; $pos = strpos ($columns[$colNb], " AS "); if ($pos) $name = substr ($columns[$colNb], 0, $pos); if (strtolower ($fieldsAll[$name][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": // If the primary key is not autoincrement, return the provided value if // exists if (! in_array ("autoincrement", $this->fields[$this->primary]) && key_exists ($this->primary, $this->setValues)) return $this->setValues[$this->primary]; // If the primary key is autoincrement and the provided value is not null // Return it if (in_array ("autoincrement", $this->fields[$this->primary]) && key_exists ($this->primary, $this->setValues) && $this->setValues[$this->primary] !== null) return $this->setValues[$this->primary]; // If the primary key is autoincrement and the provided value is null or // not provided, return the autoincremented value // PostGres need the name of the column autoincrement to return something $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"); } } /* }}} */ /** Execute a non prepared query in the database context. * As there is no verification, this method is DANGEROUS and should not be * used ! * @param string $sql The SQL request to directely send to the database * @return PDOStatement The PDO Statement to traverse */ public function directQuery ($sql) /* {{{ */ { if (! key_exists ($this->dsn, self::$instance)) $this->DBException ("Direct query without established connection"); return self::$instance[$this->dsn]->query ($sql, PDO::FETCH_ASSOC); } /* }}} */ /** Error management * @param string $message The message to throw in the exception */ public function DBException ($message) /* {{{ */ { $message = $this->DBExceptionMsg ($message); throw new \Exception ($message, 500); } /* }}} */ /** Return the $message adapted with the debug trace if needed * @param string $message The message to throw in the exception */ public function DBExceptionMsg ($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])"; } return $message; } /* }}} */ /** 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"; } /* }}} */ }