4056 lines
156 KiB
PHP
4056 lines
156 KiB
PHP
<?php
|
|
|
|
/** DomFramework
|
|
* @package domframework
|
|
* @author Dominique Fournier <dominique@fournier38.fr>
|
|
* @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");
|
|
}
|
|
}
|
|
}
|