2023-01-30 11:23:35 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace EesyPHP;
|
|
|
|
|
|
|
|
use Exception;
|
|
|
|
use PDO;
|
|
|
|
use \Envms\FluentPDO\Query;
|
|
|
|
|
|
|
|
class Db {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The PDO object of the database connection
|
|
|
|
* @var PDO
|
|
|
|
*/
|
|
|
|
public $pdo;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The PDO object of the database connection
|
|
|
|
* @var \Envms\FluentPDO\Query
|
|
|
|
*/
|
|
|
|
public $fpdo;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Date format as returned by database
|
|
|
|
* @var string
|
|
|
|
*/
|
2023-02-16 01:53:08 +01:00
|
|
|
protected $date_format = '%Y-%m-%d';
|
2023-01-30 11:23:35 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Datetime format as returned by database
|
|
|
|
* @var string
|
|
|
|
*/
|
2023-02-16 01:53:08 +01:00
|
|
|
protected $datetime_format = '%Y-%m-%d %H:%M:%S';
|
2023-01-30 11:23:35 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Locale for time (as expected by LC_TIME)
|
|
|
|
* @var string|null
|
|
|
|
*/
|
|
|
|
protected $locale_time = null;
|
|
|
|
|
2023-03-22 18:17:57 +01:00
|
|
|
/**
|
|
|
|
* Keep trace of total queries times (in ns)
|
|
|
|
* @var int;
|
|
|
|
*/
|
|
|
|
public static $total_query_time = 0;
|
|
|
|
|
2023-01-30 11:23:35 +01:00
|
|
|
/**
|
|
|
|
* Connect to database and return FluentPDO Query object
|
|
|
|
* @param string $dsn Database DSN
|
|
|
|
* @param string|null $user Username (optional)
|
|
|
|
* @param string|null $password password (optional)
|
|
|
|
* @param string|array $options Connection options (optional)
|
|
|
|
* @param string|null $date_format Date format in DB (optional)
|
|
|
|
* @param string|null $datetime_format Datetime format in DB (optional)
|
|
|
|
* @param string|null $locale_time Locale for time (optional)
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function __construct($dsn, $user=null, $password=null, $options=null,
|
|
|
|
$date_format=null, $datetime_format=null, $locale_time=null) {
|
2023-07-26 16:51:16 +02:00
|
|
|
if (!$dsn)
|
2023-01-30 11:23:35 +01:00
|
|
|
Log :: fatal('Database DSN not configured');
|
|
|
|
if ($date_format) $this -> date_format = $date_format;
|
|
|
|
if ($datetime_format) $this -> datetime_format = $datetime_format;
|
|
|
|
if ($locale_time) $this -> locale_time = $locale_time;
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Connect to database
|
|
|
|
$this -> pdo = new PDO($dsn, $user, $password, $options);
|
|
|
|
$this -> fpdo = new Query($this -> pdo);
|
|
|
|
|
|
|
|
// Register the debug query handler to log it
|
2023-03-22 18:17:57 +01:00
|
|
|
$this -> fpdo -> debug = array(self :: class, 'debug_query');
|
2023-01-30 11:23:35 +01:00
|
|
|
|
|
|
|
Log :: trace("DB connection established (DSN: '%s')", $dsn);
|
|
|
|
}
|
|
|
|
catch(Exception $e) {
|
|
|
|
Log :: error("Fail to connect to DB (DSN : '%s') : %s", $dsn, $e->getMessage());
|
2023-02-14 01:21:52 +01:00
|
|
|
Log :: fatal(I18n::_('Unable to connect to the database.'));
|
2023-01-30 11:23:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Debug a query
|
|
|
|
* @param \Envms\FluentPDO\Queries\Base $q
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public static function debug_query($q) {
|
2023-03-22 18:17:57 +01:00
|
|
|
self :: $total_query_time += intval(ceil($q->getExecutionTime() * 1000000000));
|
2023-01-30 11:23:35 +01:00
|
|
|
$msg = "# DB query";
|
|
|
|
if ($q->getResult())
|
|
|
|
$msg .= sprintf(
|
|
|
|
" (%0.3f ms; rows = %d)",
|
|
|
|
$q->getExecutionTime() * 1000,
|
|
|
|
$q->getResult()->rowCount()
|
|
|
|
);
|
|
|
|
$msg .= ": ".$q->getQuery();
|
|
|
|
|
|
|
|
$parameters = $q->getParameters();
|
|
|
|
if ($parameters) {
|
|
|
|
if (is_array($parameters)) {
|
|
|
|
$msg .= "\n# Parameters: '" . implode("', '", $parameters) . "'";
|
|
|
|
}
|
|
|
|
else { // @phpstan-ignore-line
|
|
|
|
$msg .= "\n# Parameters: '" . vardump($parameters) . "'";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-26 13:14:42 +01:00
|
|
|
Log :: debug($msg);
|
2023-01-30 11:23:35 +01:00
|
|
|
}
|
|
|
|
|
2023-03-01 19:07:19 +01:00
|
|
|
/**
|
|
|
|
* Set autocommit (only available on OCI, Firebird or MySQL connection)
|
|
|
|
* @param bool $value
|
|
|
|
* @see https://www.php.net/manual/en/pdo.setattribute.php
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function set_autocommit($value) {
|
|
|
|
$this -> pdo -> setAttribute(PDO::ATTR_AUTOCOMMIT, $value);
|
|
|
|
}
|
|
|
|
|
2023-03-02 19:16:54 +01:00
|
|
|
/*
|
|
|
|
* Simple request helpers
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper to retreive one row from a table of the database
|
|
|
|
* @param string $table The table name
|
|
|
|
* @param array|string $where WHERE clause(s) as expected by Envms\FluentPDO\Query
|
|
|
|
* @param array|string|null $fields The expected fields as string (separeted by comma) or an
|
|
|
|
* array (optional, default: all table fields will be returned)
|
|
|
|
* @return array|false
|
|
|
|
*/
|
|
|
|
public function get_one($table, $where, $fields=null) {
|
|
|
|
try {
|
|
|
|
$query = $this -> fpdo -> from($table) -> where($where);
|
|
|
|
if ($fields)
|
|
|
|
$query -> select(null) -> select($fields);
|
|
|
|
if ($query->execute() === false) // @phpstan-ignore-line
|
|
|
|
return false;
|
|
|
|
$return = $query->fetchAll();
|
|
|
|
if (is_array($return) && count($return) == 1)
|
|
|
|
return $return[0];
|
|
|
|
}
|
|
|
|
catch (Exception $e) {
|
|
|
|
Log :: error(
|
|
|
|
"Error occured getting one row of the table %s in database (where %s): %s",
|
|
|
|
$table,
|
|
|
|
is_array($where)?
|
|
|
|
preg_replace("/\n */", " ", print_r($where, true)):
|
|
|
|
vardump($where),
|
|
|
|
$e->getMessage()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper to retreive multiple rows from a table of the database
|
|
|
|
* @param string $table The table name
|
|
|
|
* @param array|string $where WHERE clause(s) as expected by Envms\FluentPDO\Query
|
|
|
|
* @param array|string|null $fields The expected fields as string (separeted by comma) or an
|
|
|
|
* array (optional, default: all table fields will be returned)
|
|
|
|
* @param string|null $order_by An optional ORDER clause as a string
|
|
|
|
* @param string|null $limit An optional LIMIT clause as a string
|
|
|
|
* @return array|false
|
|
|
|
*/
|
|
|
|
public function get_many(
|
|
|
|
$table, $where=null, $fields=null, $order_by=null, $limit=null
|
|
|
|
) {
|
|
|
|
try {
|
|
|
|
$query = $this -> fpdo -> from($table);
|
|
|
|
if ($fields)
|
|
|
|
$query -> select(null) -> select($fields);
|
|
|
|
if ($where)
|
|
|
|
$query -> where($where);
|
|
|
|
if ($order_by)
|
|
|
|
$query -> orderBy($order_by);
|
|
|
|
if ($query->execute() === false) // @phpstan-ignore-line
|
|
|
|
return false;
|
|
|
|
$return = $query->fetchAll();
|
|
|
|
if (is_array($return))
|
|
|
|
return $return;
|
|
|
|
}
|
|
|
|
catch (Exception $e) {
|
|
|
|
Log :: error(
|
|
|
|
"Error occured getting rows of the table %s in database (where %s): %s",
|
|
|
|
$table,
|
|
|
|
is_array($where)?
|
|
|
|
preg_replace("/\n */", " ", print_r($where, true)):
|
|
|
|
vardump($where),
|
|
|
|
$e->getMessage()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper to insert a row in a table
|
|
|
|
* @param string $table The table name
|
|
|
|
* @param array<string,mixed> $values The values of the row
|
|
|
|
* @param boolean $want_id Set to true if you want to retreive the ID of the inserted row
|
|
|
|
* @return bool|int The ID of the inserted row if $want_id, or true/false in case of success/error
|
|
|
|
*/
|
|
|
|
public function insert($table, $values, $want_id=false) {
|
|
|
|
try {
|
|
|
|
$id = $this -> fpdo -> insertInto($table)
|
|
|
|
-> values($values)
|
|
|
|
-> execute();
|
|
|
|
}
|
|
|
|
catch (Exception $e) {
|
|
|
|
Log :: error(
|
|
|
|
"Error occured inserting row in the table %s of the database: %s\nValues:\n%s",
|
|
|
|
$table,
|
|
|
|
$e->getMessage(),
|
|
|
|
vardump($values)
|
|
|
|
);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ($id !== false) {
|
|
|
|
Log::debug('Row insert in table %s with ID #%s', $table, $id);
|
|
|
|
return $want_id?$id:true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper to update a row in database
|
|
|
|
* @param string $table The table name
|
|
|
|
* @param array<string,mixed> $changes Associative array of changes
|
|
|
|
* @param array|string $where WHERE clause(s) as expected by Envms\FluentPDO\Query
|
|
|
|
* @param null|int|false $expected_row_changes The number of expected row affected by the query
|
|
|
|
* or false if you don't want to check it (optional,
|
|
|
|
* default: null == 1)
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function update($table, $changes, $where, $expected_row_changes=null) {
|
|
|
|
if (is_null($expected_row_changes)) $expected_row_changes = 1;
|
|
|
|
try {
|
|
|
|
$result = $this -> fpdo -> update($table)
|
|
|
|
-> set($changes)
|
|
|
|
-> where($where)
|
|
|
|
-> execute();
|
|
|
|
}
|
|
|
|
catch (Exception $e) {
|
|
|
|
Log :: error(
|
|
|
|
"Error occured updating %s in the table %s of the database (where %s): %s\nChanges:\n%s",
|
|
|
|
$expected_row_changes == 1?'row':'rows',
|
|
|
|
$table,
|
|
|
|
is_array($where)?
|
|
|
|
preg_replace("/\n */", " ", print_r($where, true)):
|
|
|
|
vardump($where),
|
|
|
|
$e->getMessage(),
|
|
|
|
vardump($changes)
|
|
|
|
);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!is_int($result))
|
|
|
|
return false;
|
|
|
|
if ($expected_row_changes === false)
|
|
|
|
return true;
|
|
|
|
return $expected_row_changes == $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper to delete one or multiple rows in a table of the database
|
|
|
|
* @param string $table The table name
|
|
|
|
* @param array|string $where WHERE clause(s) as expected by Envms\FluentPDO\Query
|
|
|
|
* @param null|int|false $expected_row_changes The number of expected row affected by the query
|
|
|
|
* or false if you don't want to check it (optional,
|
|
|
|
* default: null == 1)
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function delete($table, $where, $expected_row_changes=null) {
|
|
|
|
if (is_null($expected_row_changes)) $expected_row_changes = 1;
|
|
|
|
try {
|
|
|
|
$result = $this -> fpdo -> deleteFrom($table)
|
|
|
|
-> where($where)
|
|
|
|
-> execute();
|
|
|
|
}
|
|
|
|
catch (Exception $e) {
|
|
|
|
Log :: error(
|
|
|
|
"Error occured deleting %s in the table %s of the database (where %s): %s",
|
|
|
|
$expected_row_changes == 1?'one row':'some rows',
|
|
|
|
$table,
|
|
|
|
is_array($where)?
|
|
|
|
preg_replace("/\n */", " ", print_r($where, true)):
|
|
|
|
vardump($where),
|
|
|
|
$e->getMessage()
|
|
|
|
);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Bad return type declared in \Envms\FluentPDO\Queries\Delete
|
|
|
|
// @phpstan-ignore-next-line
|
|
|
|
if (!is_int($result))
|
|
|
|
return false;
|
|
|
|
// @phpstan-ignore-next-line
|
|
|
|
if ($expected_row_changes === false)
|
|
|
|
return true;
|
|
|
|
return $expected_row_changes == $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-01-30 11:23:35 +01:00
|
|
|
/*
|
|
|
|
* Handle date/datetime format
|
|
|
|
*/
|
|
|
|
public function set_locale() {
|
|
|
|
if ($this -> locale_time)
|
|
|
|
setlocale(LC_TIME, $this -> locale_time);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function date2time($date) {
|
|
|
|
$this -> set_locale();
|
|
|
|
$pdate = strptime($date, $this -> date_format);
|
|
|
|
return mktime(
|
|
|
|
$pdate['tm_hour'], $pdate['tm_min'], $pdate['tm_sec'],
|
|
|
|
$pdate['tm_mon'] + 1, $pdate['tm_mday'], $pdate['tm_year'] + 1900
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function time2date($time) {
|
|
|
|
$this -> set_locale();
|
|
|
|
return strftime($this -> date_format, $time);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function datetime2time($date) {
|
|
|
|
$this -> set_locale();
|
|
|
|
$pdate = strptime($date, $this -> datetime_format);
|
|
|
|
return mktime(
|
|
|
|
$pdate['tm_hour'], $pdate['tm_min'], $pdate['tm_sec'],
|
|
|
|
$pdate['tm_mon'] + 1, $pdate['tm_mday'], $pdate['tm_year'] + 1900
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function time2datetime($time) {
|
|
|
|
$this -> set_locale();
|
|
|
|
return strftime($this -> datetime_format, $time);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper method to format row info
|
|
|
|
* @param array<string,mixed> $row The raw row info
|
|
|
|
* @param array<string> $datetime_fields List of field in datetime format
|
|
|
|
* @param array<string> $date_fields List of field in date format
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function format_row_info($row, $datetime_fields=null, $date_fields=null) {
|
|
|
|
// Convert datetime fields
|
|
|
|
if (is_array($datetime_fields))
|
|
|
|
foreach($datetime_fields as $field)
|
|
|
|
if ($row[$field])
|
|
|
|
$row[$field] = $this -> datetime2time($row[$field]);
|
|
|
|
// Convert date fields
|
|
|
|
if (is_array($date_fields))
|
|
|
|
foreach($date_fields as $field)
|
|
|
|
if ($row[$field])
|
|
|
|
$row[$field] = $this -> date2time($row[$field]);
|
|
|
|
return $row;
|
|
|
|
}
|
|
|
|
}
|