* @license BSD */ namespace Domframework; /** 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 */ protected $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(); /** The real types of each fields. The real types are different of the SQL * types : a "mail" type is stored in a "VARCHAR(255)" in SQL. * The real types are optional : if not set, there is no check. * They are more strict than the SQL types */ private $realTypes = 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(); /** Store each executed requests meta data to analyze the times, number of * requests, debugging */ private static $meta = 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", 2); 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", 2); 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", 2); 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() { self::$meta[] = array( "command" => "BEGIN", "sql" => "BEGIN TRANSACTION", "sqltime" => 0, "displayQuery" => "BEGIN TRANSACTION", "nbrows" => 0, ); return self::$instance[$this->dsn]->beginTransaction(); } /** Commit (validate) a transaction */ public function commit() { self::$meta[] = array( "command" => "COMMIT", "sql" => "COMMIT TRANSACTION", "sqltime" => 0, "displayQuery" => "COMMIT TRANSACTION", "nbrows" => 0, ); return self::$instance[$this->dsn]->commit(); } /** RollBack a transaction */ public function rollback() { self::$meta[] = array( "command" => "ROLLBACK", "sql" => "ROLLBACK TRANSACTION", "sqltime" => 0, "displayQuery" => "ROLLBACK TRANSACTION", "nbrows" => 0, ); 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)) { if ($d["name"] !== "sqlite_sequence") { $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" )); } natsort($res); return array_values($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", 2); if ($this->sep === "") { $this->DBException( dgettext("domframework", "Database not connected"), 500 ); } if (count($this->fields) === 0) { $this->DBException(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 "blob": $sql .= "BLOB"; $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; case "float": $sql .= "FLOAT"; $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: $this->DBException(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: $this->DBException(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)) { $this->DBException( 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 "blob": $sql .= "BLOB"; $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; case "float": $sql .= "FLOAT"; $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: $this->DBException(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: $this->DBException(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 "blob": $sql .= "BLOB"; $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; case "float": $sql .= "FLOAT"; $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: $this->DBException(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: $this->DBException(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: $this->DBException(dgettext( "domframework", "PDO Engine not supported in dbLayeroo" ), 500); } $this->debugLog($sql, 1); return self::$instance[$this->dsn]->exec($sql); } /** Drop the table */ public function dropTable() { $this->debugLog("Entering dropTable ()", 2); if ($this->sep === "") { $this->DBException(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, 1); 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, ")", 2); 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" || $row["notnull"] === 1) { $fields[$row["name"]][] = "not null"; } if ($row["pk"] === "1" || $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 && $content2[0]["cid"] >= 1) { $index = $content2[0]["name"]; $unique[$content2[0]["cid"] - 1] = $index; } } ksort($unique); if (! in_array($primary, $unique)) { $unique[] = $primary; } 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; } $foreignUsed = array(); foreach ($this->listTables() as $tbl) { $st = self::$instance[$this->dsn]->prepare( "PRAGMA foreign_key_list($tbl)" ); $st->execute(); $content = $st->fetchAll(\PDO::FETCH_ASSOC); foreach ($content as $row) { if ($row["table"] !== $tableName) { continue; } $foreignUsed[$row["to"]][] = array( $tbl, $row["from"] ); } } return array("table" => $tableName, "fields" => $fields, "primary" => $primary, "unique" => $unique, "foreign" => $foreign, "foreignUsed" => $foreignUsed); 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); if (! in_array($primary, $unique)) { $unique[] = $primary; } $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 AND rCons.CONSTRAINT_SCHEMA=kColUsage.CONSTRAINT_SCHEMA"); $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; } $st = self::$instance[$this->dsn]->prepare(" SELECT TABLE_NAME,COLUMN_NAME, REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_NAME=:table AND TABLE_SCHEMA=:dbname"); $st->execute(array(':dbname' => $this->databasename(), ':table' => $tableName)); $foreignUsedTmp = $st->fetchAll(\PDO::FETCH_ASSOC); $foreignUsed = array(); foreach ($foreignUsedTmp as $f) { $foreignUsed[$f["REFERENCED_COLUMN_NAME"]][] = array( $f["TABLE_NAME"], $f["COLUMN_NAME"]); } return array("table" => $tableName, "fields" => $fields, "primary" => $primary, "unique" => $unique, "foreign" => $foreign, "foreignUsed" => $foreignUsed); break; case "pgsql": $fields = array(); $unique = array(); $foreign = array(); $primary = ""; // Get the defined primary key if not autoincrement $st = self::$instance[$this->dsn]->prepare(" SELECT a.attname, format_type(a.atttypid, a.atttypmod) AS data_type FROM pg_index i JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE i.indrelid = :table::regclass AND i.indisprimary; "); $st->execute(array(':table' => $tableName)); $content = $st->fetchAll(\PDO::FETCH_ASSOC); if (key_exists(0, $content)) { $primary = $content[0]["attname"]; } // Get the primary key if autoincrement $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); 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"]; } elseif (! in_array($col["column_name"], $unique)) { $unique[$col["constraint_name"]] = $col["column_name"]; } } $unique = array_values($unique); if (! in_array($primary, $unique)) { $unique[] = $primary; } $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; } $st = self::$instance[$this->dsn]->prepare(" SELECT kColUsage1.table_name TABLE_NAME, 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 kColUsage2.table_name=:table AND rCons.constraint_name=kColUsage1.constraint_name AND rCons.unique_constraint_name=kColUsage2.constraint_name ORDER BY kColUsage1.table_name "); $st->execute( array(':dbname' => $this->databasename(), ':table' => $tableName) ); $foreignUsedTmp = $st->fetchAll(\PDO::FETCH_ASSOC); $foreignUsed = array(); foreach ($foreignUsedTmp as $f) { $foreignUsed[$f["referenced_column_name"]][] = array( $f["table_name"], $f["column_name"]); } return array("table" => $tableName, "fields" => $fields, "primary" => $primary, "unique" => $unique, "foreign" => $foreign, "foreignUsed" => $foreignUsed); break; default: $this->DBException(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,")", 2); 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, ")", 2); 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, ")", 2); 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)", 2); 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( "#^(blob|date|datetime|float|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, ")", 2); 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)", 2); 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)", 2); 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, ")", 2); 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, ")", 2); 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)", 2); 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, ); } /** Define a real type array * Must be array ("field" => "realtype") * @param array $realTypes The realTypes to set * The allowed real types are defined in test method : * checkRealType_TYPE ($val, $definition) * The allowed real types are : mail, date, datetime, allowedchars(a23d), * uuid, time, array('val1','val2','val3'), regex(/^[a-zA-Z]{3,}$/i) * integer, integerPositive, * To be done : * integerNegative, * php_function(), function($val) { if ($val > 0) return "MESSAGE";} */ public function realTypes($realTypes = null) { if ($realTypes === null) { return $this->realTypes; } if (! is_array($realTypes)) { $this->DBException("Invalid setRealType provided : not an array", 500); } foreach ($realTypes as $field => $realType) { if (! key_exists($field, $this->fields)) { $this->DBException("Invalid setRealType provided : " . "field '$field' doesn't exists"); } @list($func, $param) = explode("(", $realType); $func = trim($func); $method = "checkRealType_" . $func; if (! method_exists($this, $method)) { $this->DBException("Invalid setRealType provided : " . "field '$field' : unknown realType provided '$func'"); } } $this->realTypes = $realTypes; return $this; } /** Get the meta data or clear them. * The meta data are all the requests done by the dblayeroo, the time needed * for each, the number of rows returned by the SQL server. * They allow to debug the app * @param boolean|null $meta If meta is set, clear the meta-data. If not set, * return the actual value of the meta-data * @return array or $this */ public function meta($meta = null) { if ($meta === null) { return self::$meta; } self::$meta = array(); return $this; } ///////////////////////////////////// /// 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 */ protected $debugDepth = 1; /** The sort order of select/order entries crossed the differents objects */ private static $sortOrder = 0; /** The method to get a new sort 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 ()", 2); 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 (); return $this; } /** 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)", 2); if (! is_object($object)) { $this->DBException("Invalid setForeignObj parameter: not an object"); } if ( ! is_subclass_of($object, __CLASS__) && get_class($object) !== "Dblayeroo" && get_class($object) !== __NAMESPACE__ . "\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, ")", 2); $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, ")", 2 ); 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)) { // A string must be separated by comma. But comma can be also in function // parameter like GROUP_CONCAT(group,','),field1,field2 // This block will explode on comma but only if not in a parenthesis block $tmpArr = array(); $parenthesis = false; $tmp = ""; for ($i = 0; $i < strlen($columnNames); $i++) { $char = $columnNames[$i]; if ($parenthesis === true) { $tmp .= $char; if ($char === ")") { $parenthesis = false; } } elseif ($char === "(") { $tmp .= $char; $parenthesis = true; } elseif ($char === ",") { $tmpArr[] = $tmp; $tmp = ""; } else { $tmp .= $char; } } if ($tmp !== "") { $tmpArr[] = $tmp; } $columnNames = $tmpArr; } 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 === "pgsql") { $separator = ",'$separator'"; } elseif ($this->driver === "mysql") { $separator = " SEPARATOR '$separator'"; } } if ($func === "GROUP_CONCAT" && $this->driver === "pgsql") { $func = "string_agg"; if ($separator === "") { $separator = ", ','"; } } } } $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; } elseif ($func === "string_agg") { // For Postgres, the entry must be : // string_agg(distinct "group"::character varying, ',' order by "group") $this->displayColumn[$getSortOrder] = "$func($distinct$this->sep$fieldName$this->sep" . "::character varying$separator " . "order by $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)", 2); 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)", 2); 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)", 2); return $this->joinReal("RIGHT", $object, $joinArray); } /** Convert a CONCAT('text',field) to CONCAT('text',`table`.`field`) or * generate an exception if the field is not available in table * If the provided string is not a concat, return the original one * @param string $concat The string to examine * @param object $object The dblayer object to use */ private function concat($concat, $object) { if (substr($concat, 0, 7) !== "CONCAT(") { return $concat; } if (substr($concat, -1) !== ")") { $this->DBException("CONCAT without ending parenthesis"); } $tmp = substr($concat, 7, -1); $new = "CONCAT("; foreach (explode(",", $tmp) as $part) { if ($new !== "CONCAT(") { $new .= ","; } if ( substr($part, 0, 1) === "'" && substr($part, -1) === "'" ) { $new .= $part; } elseif (! array_key_exists($part, $object->fields)) { $this->DBException(sprintf( "Invalid field to join in CONCAT : " . "'%s' not defined in Distant table", $part )); } else { $new .= $this->sep . $object->tableprefix . $object->table . $this->sep . "." . $this->sep . $part . $this->sep; } } $new .= ")"; return $new; } /** 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)", 2); 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" && get_class($object) !== __NAMESPACE__ . "\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)"); } $newJoinArray = array(); foreach ($joinArray as $fieldLocal => $fieldToJoin) { if (substr($fieldLocal, 0, 7) === "CONCAT(") { $fieldLocal = $this->concat($fieldLocal, $this); } elseif (array_key_exists($fieldLocal, $this->fields)) { $fieldLocal = $this->sep . $this->tableprefix . $this->table . $this->sep . "." . $this->sep . $fieldLocal . $this->sep; } else { $this->DBException(sprintf( "Invalid field to join '%s' : not defined in Local table", $fieldLocal )); } if (substr($fieldToJoin, 0, 7) === "CONCAT(") { $fieldToJoin = $this->concat($fieldToJoin, $object); } elseif (array_key_exists($fieldToJoin, $object->fields)) { $fieldToJoin = $this->sep . $object->tableprefix . $object->table . $this->sep . "." . $this->sep . $fieldToJoin . $this->sep; } else { $this->DBException(sprintf( "Invalid field to join '%s' : not defined in Distant table", $fieldToJoin )); } $newJoinArray[$fieldLocal] = $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 ($newJoinArray as $fieldLocal => $fieldToJoin) { if ($tmp !== "") { $tmp .= " AND "; } $tmp .= "$fieldLocal=$fieldToJoin"; } // 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, ")", 2 ); 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) && ! is_float($value) ) { $this->DBException("Invalid value provided (not string nor null " . "nor integer not float)"); } 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 ()", 2); 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 ()", 2); 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 ()", 2); 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 ()", 2); $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", "NATASC", * "NATDESC"); */ public function orderAdd($field, $sort = "ASC") { $this->debugLog("Entering orderAdd (", $field, ", ", $sort, ")", 2); 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", "NATASC", "NATDESC"))) { $this->DBException( "Invalid sort provided (not ASC nor DESC nor NATASC nor NATDESC)" ); } if ( ! array_key_exists($field, $this->fields) && ! in_array($field, $this->displayAlias) ) { $this->DBException(sprintf( "Invalid field to orderAdd '%s' : not defined in table nor in alias", $field )); } $plus = ""; if (substr($sort, 0, 3) === "NAT") { $plus = "+0"; $sort = substr($sort, 3); } $exp = $this->sep . $field . $this->sep . $plus . " " . $sort; if ($plus !== "") { $exp .= "," . $this->sep . $this->table . $this->sep . "." . $this->sep . $field . $this->sep . " " . $sort; } $this->orderExpression[$this->getSortOrder()] = $exp; 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; } /** Return true if this object or one of the join objects need GROUP BY SQL * part */ public function groupByNeeded() { $needGroupBy = false; if ($this->groupByExpression !== null) { $needGroupBy = true; } if ($this->joinObject) { foreach ($this->joinObject as $obj) { if ($obj->groupByNeeded() === true) { $needGroupBy = true; } } } return $needGroupBy; } /** 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) { if ($this->joinObject) { $full = true; } $localGroupBy = array(); // Manage the local object group by entries. In full mode, return the // groupByExpression if it is set, or the list of the displayed fields. if ($full) { if ($this->groupByExpression !== null) { foreach ($this->groupByExpression as $pos => $o) { if ($localGroupBy === null) { $localGroupBy = array(); } $localGroupBy[$pos] = $this->sep . $this->tableprefix . $this->table . $this->sep . "." . $o; } } elseif ($this->displayColumn !== null) { foreach ($this->displayColumn as $name) { $localGroupBy[] = $this->displayConvert($name, true); } } else { // Nothing to return if there is no groupBy and nothing to display } } else { if ($this->groupByExpression !== null) { $localGroupBy = $this->groupByExpression; } else { $localGroupBy = array(); } } // Add the distant entries $distantGroupBy = array(); if ($this->joinObject) { foreach ($this->joinObject as $obj) { $ext = $obj->groupByGet(true); $distantGroupBy = array_merge($distantGroupBy, $ext); } } $groupBy = array_merge($localGroupBy, $distantGroupBy); 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, ")", 2); 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, ")", 2); 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, ")", 2); if (! is_array($values)) { $this->DBException("Invalid values to setValues : not an array"); } $values = $this->normalize($values); $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) && ! is_float($val) ) { $this->DBException(sprintf( "Invalid field to setValues '%s': not string and not numeric", $key )); } $tmpValues[$key] = $val; $tmpType[md5("$key, $val")] = $this->fieldTypeLight($key); $this->debugLog("setValues : Type for $key = " . $this->fieldTypeLight($key), 1); } $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); } if ($this->groupByNeeded()) { $groupByExpression = $this->groupByGet(); if (count($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 === "blob") { $text .= "(blob)\n"; } elseif ($type === "integer") { $text .= "(integer)\n"; } elseif ($type === "float") { $text .= "(float)\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 === "blob") { $st->bindValue(":$hash", $value, \PDO::PARAM_STR); } elseif ($type === "integer") { $st->bindValue(":$hash", $value, \PDO::PARAM_INT); } elseif ($type === "float") { $st->bindValue(":$hash", $value, \PDO::PARAM_STR); } 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 === "blob") { $text .= "(blob)\n"; } elseif ($type === "integer") { $text .= "(integer)\n"; } elseif ($type === "float") { $text .= "(float)\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] === "blob") { $st->bindValue(":$hash", "$value", \PDO::PARAM_STR); } elseif ($this->setType[$hash] === "integer") { $st->bindValue(":$hash", $value, \PDO::PARAM_INT); } elseif ($this->setType[$hash] === "float") { $st->bindValue(":$hash", "$value", \PDO::PARAM_STR); } 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; } /** Normalize the values before using them. * The normalize is called by methods : verify, checkRealTypes and setValues * By default, remove the spaces (trim) at begin and end. * This method can be overloaded by extending the class * @param array $values The values to test or INSERT or UPDATE * @return array the updated values */ public function normalize($values) { if (! is_array($values)) { $this->DBException("Invalid values to normalize : not an array", 406); } foreach ($values as $key => $val) { if ($val !== null && ! is_array($val)) { $values[$key] = trim($val); } } return $values; } /** 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 ); } $values = $this->normalize($values); 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]", 2); 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] = dgettext( "domframework", "Field mandatory and not provided" ); continue; } if ( in_array("not null", $params) && ! in_array("autoincrement", $params) && $values[$field] === null ) { $errors[$field] = dgettext( "domframework", "Field null and defined as NOT NULL and " . "not Autoincrement" ); continue; } if ( ! is_string($values[$field]) && ! is_integer($values[$field]) && ! is_null($values[$field]) && ! is_float($values[$field]) ) { $errors[$field] = dgettext( "domframework", "Field not a string nor numeric" ); } // 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 "blob": // Blob can be anything. Do not test break; case "integer": if (strspn($values[$field], "0123456789") !== strlen($values[$field])) { $errors[$field] = dgettext( "domframework", "Field not in integer format" ); } break; case "float": if ( strspn($values[$field], "0123456789.") !== strlen($values[$field]) ) { $errors[$field] = dgettext( "domframework", "Field not in float format" ); } break; case "varchar": $length = $this->fieldLength($field); if (mb_strlen($values[$field]) > $length) { $errors[$field] = dgettext("domframework", "Field data too long"); } break; case "date": if (! preg_match("#^\d{4}-\d{2}-\d{2}$#", $values[$field])) { $errors[$field] = dgettext( "domframework", "Field not in date format" ); } break; case "datetime": if ( ! preg_match( "#^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$#", $values[$field] ) ) { $errors[$field] = dgettext( "domframework", "Field not in datetime format" ); } break; case "time": if (! preg_match("#^\d{2}:\d{2}:\d{2}$#", $values[$field])) { $errors[$field] = dgettext( "domframework", "Field not in time format" ); } 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", 2); $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", 2); $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", 2); $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), "]", 2 ); 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]", 2); 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())) { if (is_array($columns)) { foreach ($columns as $column) { $errors[$column] = sprintf( dgettext( "domframework", "An entry with this value '%s' already exists" ), mb_substr($setValues[$column], 0, 15) ); } } else { $errors[$columns] = sprintf( dgettext( "domframework", "An entry with this value '%s' already exists" ), mb_substr($setValues[$columns], 0, 15) ); } } 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) && in_array("not null", $this->fields[$field]) ) { $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)); } if ( ! array_key_exists($field, $values) && ! in_array("not null", $this->fields[$field]) ) { $values[$field] = null; } } $this->debugLog("CLONE to check foreign constraint [$fields]", 2); $objTmp = clone $this->setForeignObj[$params[0]]; $objTmp->debug = $this->debug; $objTmp->debugDepth++; $objTmp->clearRequest(); $objTmp->Select(); $objTmp->displayAdd($objTmp->primary); $parentField = explode(",", $params[1]); $i = 0; foreach (explode(",", $fields) as $key => $field) { if (key_exists($field, $values) && $values[$field] !== null) { $objTmp->whereAdd($parentField[$key], "=", $values[$field]); $i++; } } if ($i == 0) { // If the foreign is null, do not test as there is no WHERE clause, and // will return all the table continue; } 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 array $values The values to test * @param boolean|null $update if true UPDATE request, else INSERT request */ public function checkValues($values, $update = false) { $this->debugLog("Entering checkValues (", $update, ")", 2); if (! is_array($values)) { $this->DBException("checkValues fields : " . "values provided are not an array", 406); } $values = $this->normalize($values); $update = !! $update; $errors = array_merge( $this->verify($values, $update), $this->checkRealTypes($values, $update) ); if (count($errors) === 0) { $this->debugLog( "End of checkValues (", $update, ") : Nothing in error", 2 ); } else { $this->debugLog("End of checkValues (", $update, ") : " . count($errors) . "errors", 2); } return $errors; } /** Check the types of the data against the realTypes * Return an array with the field name in error and a message * If allowEmpty is set, do not test the "NOT NULL" feature * In UPDATE, the values don't need be not set : they are already in database * so $allowEmpty is true * @param array $values The values to test * @param boolean|null $allowEmpty Allow the "not null" to not be set * @return array empty array if no error */ public function checkRealTypes($values, $allowEmpty = false) { $this->debugLog( "Entering checkRealTypes (", $values, ",", $allowEmpty, ")", 2 ); if (! is_array($values)) { $this->DBException( "Invalid checkRealTypes provided : not an array", 500 ); } $values = $this->normalize($values); $errors = array(); foreach ($this->fields as $field => $params) { if (! key_exists($field, $values) || trim($values[$field]) === "") { if ( in_array("not null", $params) && ! in_array("autoincrement", $params) ) { if ($allowEmpty === false) { $errors[$field] = dgettext( "domframework", "The field can not be empty" ); } } else { // Empty value is not tested and do not generate an error } } elseif (! is_string($values[$field])) { $errors[$field] = dgettext( "domframework", "The value is not a string" ); } elseif (key_exists($field, $this->realTypes)) { $val = trim($values[$field]); @list($func, $param) = explode("(", $this->realTypes[$field]); $func = trim($func); $method = "checkRealType_" . $func; $res = $this->$method($val, $this->realTypes[$field]); if (is_string($res)) { $errors[$field] = $res; } } } $this->debugLog( "End checkRealTypes (", $values, ",", $allowEmpty, ") : ", $errors, 2 ); return $errors; } /** 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 ()", 2); switch ($this->command) { case "SELECT": break; case "INSERT": $errors = $this->checkValues($this->setValues, false); if (count($errors)) { $val = reset($errors); $this->DBException(key($errors) . " : $val", 406); } break; case "UPDATE": $errors = $this->checkValues($this->setValues, true); if (count($errors)) { $val = reset($errors); $this->DBException(key($errors) . " : $val", 406); } break; case "DELETE": break; default: $this->DBException("execute : command not defined : no check"); } $this->debugLog("Entering createRequest ()", 2); $sql = $this->createRequest(); $this->debugLog("Entering prepareRequest (XXX, ", false, ")", 2); $st = $this->prepareRequest($sql, false); $this->debugLog("'", $this->getDisplayQuery(), "'", 1); $startTime = microtime(true); $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(true); // If the displayed fields are all in the same table, remove the table // name in the columns $cleanable = true; $cleanTable = ""; foreach ($this->displayGet(true) as $display) { preg_match_all( "#" . $this->sep . "(.+)" . $this->sep . "\.#", $display, $matches ); if ($cleanTable === "") { $cleanTable = $matches[1][0]; } elseif ($cleanTable !== $matches[1][0]) { $cleanable = false; break; } } $columns = array_values($this->displayGet(true)); // Get the columns names that will be displayed to user. // If the cleanable is possible, remove the table names $columnNames = array_combine($columns, $columns); //if ($this->joinObject === null && count ($this->displayGet (false))) if ($cleanable) { // Remove the table name as there is no collisions risk // In case of Alias, remove the $this->sep too foreach ($columnNames as $key => $col) { // Remove the table and the separator if exists $col = preg_replace("#" . $this->sep . "[^" . $this->sep . "]+" . $this->sep . "\.#U", "", $col); // Remove the separator if not table exists $col = str_replace($this->sep, "", $col); if ($col[0] === $this->sep) { $col = substr($col, 1); } elseif (strpos($col, "DISTINCT " . $this->sep) === 0) { $col = "DISTINCT " . substr($col, 10); } 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)); } $columnNames[$key] = $col; } } else { foreach ($columnNames as $key => $col) { $columnNames[$key] = str_replace($this->sep, "", $col); } } 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); } $name = str_replace("DISTINCT ", "", $name); /*if ($cleanable) $name = str_replace ($this->sep.$this->tableprefix.$this->table. $this->sep.".", "", $name);*/ if ( strtolower($fieldsAll[$name][0]) === "integer" && $val !== null ) { $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 = $columnNames[$columns[$colNb]]; } $result[$rownb][$colName] = $val; unset($result[$rownb][$colNb]); } } self::$meta[] = array( "command" => $this->command, "sql" => $sql, "sqltime" => microtime(true) - $startTime, "displayQuery" => $this->getDisplayQuery(), "nbrows" => count($result), ); return $result; case "INSERT": self::$meta[] = array( "command" => $this->command, "sql" => $sql, "sqltime" => microtime(true) - $startTime, "displayQuery" => $this->getDisplayQuery(), "nbrows" => $st->rowCount(), ); // 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), 1 ); return self::$instance[$this->dsn]->lastInsertId($autoInc); case "UPDATE": case "DELETE": self::$meta[] = array( "command" => $this->command, "sql" => $sql, "sqltime" => microtime(true) - $startTime, "displayQuery" => $this->getDisplayQuery(), "nbrows" => $st->rowCount(), ); 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 * @param integer|null $code The error code to return to the user */ public function DBException($message, $code = 500) { $message = $this->DBExceptionMsg($message); throw new \Exception($message, $code); } /** 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) { 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 * @param integer priority The display message if $priority <= $this->debug */ public function debugLog($message, $priority) { if ((!!$this->debug) === false) { return; } $args = func_get_args(); $priority = array_pop($args); if ($priority > $this->debug) { return; } echo str_repeat("=", $this->debugDepth * 2) . " "; foreach ($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"; } ////////////////////////////////////////////// //// ALL THE CHECK REALTYPES METHODS //// ////////////////////////////////////////////// /** Check the type "integerPositive" * @param string $val The value to check * @param string $definition The definition of the type * @return string or null */ private function checkRealType_integerPositive($val, $definition) { if (substr($val, 0, 1) === "-") { return dgettext("domframework", "Invalid positive integer : " . "can not start by minus sign"); } if (strspn($val, "0123456789") !== strlen($val)) { return dgettext("domframework", "Invalid positive integer : " . "invalid char"); } if (substr($val, 0, 1) === "0" && $val !== "0") { return dgettext("domframework", "Invalid positive integer : " . "can not start by Zero"); } return; } /** Check the type "integer" * @param string $val The value to check * @param string $definition The definition of the type * @return string or null */ private function checkRealType_integer($val, $definition) { if (strspn($val, "0123456789-") !== strlen($val)) { return dgettext("domframework", "Invalid integer : " . "invalid char"); } if (substr($val, 0, 1) === "0" && $val !== "0") { return dgettext("domframework", "Invalid integer : " . "can not start by Zero"); } if (substr($val, 0, 2) === "-0") { return dgettext("domframework", "Invalid integer : " . "can not start by Minus Zero"); } return; } /** Check the type "allowedchars" * the chars must be in UTF-8 * @param string $val The value to check * @param string $definition The definition of the type * @return string or null */ private function checkRealType_allowedchars($val, $definition) { list($func, $param) = explode("(", $definition); if ($param === null) { $this->DBException("Invalid allowedchars definied : " . "must have a starting parenthesis"); } if (substr($param, -1) !== ")") { $this->DBException("Invalid allowedchars definied : " . "must have a ending parenthesis"); } $allowedChars = mb_substr($param, 0, -1); $allowedChars = preg_quote($allowedChars, "#"); preg_match('#^[' . $allowedChars . ']+#u', $val, $matches); if ( isset($matches[0]) && mb_strlen($matches[0]) === mb_strlen($val) ) { return; } return dgettext("domframework", "Invalid char provided"); } /** Check the type "array" * @param string $val The value to check * @param string $definition The definition of the type * @return string or null */ private function checkRealType_array($val, $definition) { list($func, $param) = explode("(", $definition); if ($param === null) { $this->DBException("Invalid array definied : " . "must have a starting parenthesis"); } if (substr($param, -1) !== ")") { $this->DBException("Invalid array definied : " . "must have a ending parenthesis"); } $param = mb_substr($param, 0, -1); $array = explode(",", $param); foreach ($array as $key => $tmp) { $array[$key] = mb_substr($tmp, 1, -1); } if (in_array($val, $array)) { return; } return dgettext("domframework", "Invalid value provided : " . "not in allowed list"); } /** Check the type "regex" * Do not forget to add the separators, the ^ and $ * @param string $val The value to check * @param string $definition The definition of the type * @return string or null */ private function checkRealType_regex($val, $definition) { list($func, $param) = explode("(", $definition); if ($param === null) { $this->DBException("Invalid regex definied : " . "must have a starting parenthesis"); } if (substr($param, -1) !== ")") { $this->DBException("Invalid regex definied : " . "must have a ending parenthesis"); } $param = mb_substr($param, 0, -1); if (@preg_match($param, $val) === false) { $this->DBException("Invalid regex definied : " . "must have a ending parenthesis"); } if (preg_match($param, $val)) { return; } return dgettext("domframework", "Invalid value provided : " . "do not match the regex"); } /** Check the type "mail" * @param string $val The value to check * @param string $definition The definition of the type * @return string or null */ private function checkRealType_mail($val, $definition) { if (!! filter_var($val, FILTER_VALIDATE_EMAIL)) { return; } return dgettext("domframework", "Invalid mail provided"); } /** Check the type "uuid" * @param string $val The value to check * @param string $definition The definition of the type * @return string or null */ private function checkRealType_uuid($val, $definition) { if (strlen($val) !== 36) { return dgettext("domframework", "Invalid UUID provided : " . "invalid length"); } if (strspn($val, "0123456789abcdefABCDEF-") !== strlen($val)) { return dgettext("domframework", "Invalid UUID provided : invalid char"); } if ( $val[8] !== "-" || $val[13] !== "-" || $val[18] !== "-" || $val[23] !== "-" ) { return dgettext("domframework", "Invalid UUID provided : missing dash"); } } /** Check the type "sqldate" * @param string $val The value to check * @param string $definition The definition of the type * @return string or null */ private function checkRealType_sqldate($val, $definition) { if (strlen($val) !== 10) { return dgettext("domframework", "Invalid date provided : " . "invalid length"); } if (strspn($val, "0123456789-") !== strlen($val)) { return dgettext("domframework", "Invalid date provided : " . "invalid chars"); } $arr = \date_parse($val); if ($arr["warning_count"] !== 0) { return dgettext("domframework", "Invalid date provided : " . "can not parse the date"); } if ($arr["error_count"] !== 0) { return dgettext("domframework", "Invalid date provided : " . "can not parse the date"); } if (isset($arr["tz_abbr"])) { return dgettext("domframework", "Invalid date provided : " . "can not parse the date"); } if (\DateTime::createFromFormat("Y-m-d", $val) === false) { return dgettext("domframework", "Invalid date provided : " . "the date doesn't exists"); } } /** Check the type "sqltime" * @param string $val The value to check * @param string $definition The definition of the type * @return string or null */ private function checkRealType_sqltime($val, $definition) { if (strlen($val) !== 8) { return dgettext("domframework", "Invalid time provided : " . "invalid length"); } if (strspn($val, "0123456789:") !== strlen($val)) { return dgettext("domframework", "Invalid time provided : " . "invalid chars"); } $arr = \date_parse($val); if ($arr["warning_count"] !== 0) { return dgettext("domframework", "Invalid time provided : " . "can not parse the time"); } if ($arr["error_count"] !== 0) { return dgettext("domframework", "Invalid time provided : " . "can not parse the time"); } if (isset($arr["tz_abbr"])) { return dgettext("domframework", "Invalid time provided : " . "can not parse the date"); } if (\DateTime::createFromFormat("H:i:s", $val) === false) { return dgettext("domframework", "Invalid time provided : " . "the time doesn't exists"); } } /** Check the type "sqldatetime" * @param string $val The value to check * @param string $definition The definition of the type * @return string or null */ private function checkRealType_sqldatetime($val, $definition) { if (strlen($val) !== 19) { return dgettext("domframework", "Invalid date and time provided : " . "invalid length"); } if (strspn($val, "0123456789 :-") !== strlen($val)) { return dgettext("domframework", "Invalid date and time provided : " . "invalid chars"); } $arr = \date_parse($val); if ($arr["warning_count"] !== 0) { return dgettext("domframework", "Invalid date and time provided : " . "can not parse the date"); } if ($arr["error_count"] !== 0) { return dgettext("domframework", "Invalid date and time provided : " . "can not parse the date"); } if (isset($arr["tz_abbr"])) { return dgettext("domframework", "Invalid date and time provided : " . "can not parse the date"); } if (\DateTime::createFromFormat("Y-m-d H:i:s", $val) === false) { return dgettext("domframework", "Invalid date and time provided : " . "the date doesn't exists"); } } }