480 lines
18 KiB
PHP
480 lines
18 KiB
PHP
<?php
|
|
namespace descartes;
|
|
|
|
/**
|
|
* Cette classe sert de mère à tous les modèles, elle permet de gérer l'ensemble des fonction necessaires aux requetes en base de données
|
|
* @param $pdo : Une instance de \PDO
|
|
*/
|
|
class Model
|
|
{
|
|
//Les variables internes au Model
|
|
var $pdo;
|
|
|
|
//Les constantes des différents types de retours possibles
|
|
const NO = 0; //Pas de retour
|
|
const FETCH = 1; //Retour de type fetch
|
|
const FETCHALL = 2; //Retour de type fetchall
|
|
const ROWCOUNT = 3; //Retour de type rowCount()
|
|
|
|
/**
|
|
* Model constructor
|
|
* @param \PDO $pdo : \PDO connect to use
|
|
*/
|
|
public function __construct(\PDO $pdo)
|
|
{
|
|
$this->pdo = $pdo;
|
|
}
|
|
|
|
/**
|
|
* Cette fonction permet créer une connexion à une base SQL via \PDO
|
|
* @param string $host : L'host à contacter
|
|
* @param string $dbname : Le nom de la base à contacter
|
|
* @param string $user : L'utilisateur à utiliser
|
|
* @param string $password : Le mot de passe à employer
|
|
* @return mixed : Un objet \PDO ou false en cas d'erreur
|
|
*/
|
|
public static function _connect ($host, $dbname, $user, $password, ?string $charset = 'UTF8', ?array $options = null)
|
|
{
|
|
$options = $options ?? [
|
|
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
|
|
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION
|
|
];
|
|
|
|
// On se connecte à MySQL
|
|
$pdo = new \PDO('mysql:host=' . $host . ';dbname=' . $dbname . ';charset=' . $charset , $user, $password, $options);
|
|
|
|
if ($pdo === false)
|
|
{
|
|
throw new DescartesExceptionDatabaseConnection('Cannot connect to database ' . $dbname . '.');
|
|
}
|
|
|
|
return $pdo;
|
|
}
|
|
|
|
/**
|
|
* Run a query and return result
|
|
* @param string $query : Query to run
|
|
* @param array $data : Data to pass to query
|
|
* @param const $return_type : Type of return, by default all results, see Model constants
|
|
* @param const $fetch_mode : Format of result from db, by default array, FETCH_ASSOC
|
|
* @param boolean $debug : If we must return debug info instead of data, by default false
|
|
* @return mixed : Result of query, depend of $return_type | null | array | object | int
|
|
*/
|
|
protected function _run_query (string $query, array $data = array(), int $return_type = self::FETCHALL, int $fetch_mode = \PDO::FETCH_ASSOC, bool $debug = false)
|
|
{
|
|
try
|
|
{
|
|
//Must convert bool to 1 or 0 because of some strange inconsistent behavior between PHP versions
|
|
foreach ($data as $key => $value)
|
|
{
|
|
if (is_bool($value))
|
|
{
|
|
$data[$key] = (int) $value;
|
|
}
|
|
}
|
|
|
|
$query = $this->pdo->prepare($query);
|
|
$query->setFetchMode($return_type);
|
|
$query->execute($data);
|
|
|
|
if ($debug)
|
|
{
|
|
return $query->errorInfo();
|
|
}
|
|
|
|
switch ($return_type)
|
|
{
|
|
case self::NO :
|
|
$return = NULL;
|
|
break;
|
|
|
|
case self::FETCH :
|
|
$return = $query->fetch();
|
|
break;
|
|
|
|
case self::FETCHALL :
|
|
$return = $query->fetchAll();
|
|
break;
|
|
|
|
case self::ROWCOUNT :
|
|
$return = $query->rowCount();
|
|
break;
|
|
|
|
default :
|
|
$return = $query->fetchAll();
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
catch (\PDOException $e)
|
|
{
|
|
$error = $query->errorInfo();
|
|
|
|
//Get query string and params
|
|
ob_start();
|
|
$query->debugDumpParams();
|
|
$debug_string = ob_get_clean();
|
|
|
|
throw new \descartes\exceptions\DescartesExceptionSqlError(
|
|
'SQL Error : ' . "\n" .
|
|
'SQLSTATE : ' . $error[0] . "\n" .
|
|
'Driver Error Code : ' . $error[1] . "\n" .
|
|
'Driver Error Message : ' . $error[2] . "\n" .
|
|
'SQL QUERY DEBUG :' . "\n" .
|
|
'-----------------' . "\n" .
|
|
$debug_string . "\n" .
|
|
'-----------------' . "\n"
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return last inserted id
|
|
* return int : Last inserted id
|
|
*/
|
|
protected function _last_id() : int
|
|
{
|
|
return $this->pdo->lastInsertId();
|
|
}
|
|
|
|
/*
|
|
Fonctions d'execution des requetes ou de génération
|
|
*/
|
|
|
|
|
|
/**
|
|
* Generate IN query params and values
|
|
* @param string $values : Values to generate in array from
|
|
* @return array : Array ['QUERY' => string 'IN(...)', 'PARAMS' => [parameters to pass to execute]]
|
|
*/
|
|
protected function _generate_in_from_array ($values)
|
|
{
|
|
$return = array(
|
|
'QUERY' => '',
|
|
'PARAMS' => array(),
|
|
);
|
|
|
|
$flags = array();
|
|
|
|
$values = count($values) ? $values : array();
|
|
|
|
foreach ($values as $key => $value)
|
|
{
|
|
$key = preg_replace('#[^a-zA-Z0-9_]#', '', $key);
|
|
$return['PARAMS']['in_value_' . $key] = $value;
|
|
$flags[] = ':in_value_' . $key;
|
|
}
|
|
|
|
$return['QUERY'] .= ' IN(' . implode(', ', $flags) . ')';
|
|
return $return;
|
|
}
|
|
|
|
|
|
/**
|
|
* Evaluate a condition to generate query string and params array for
|
|
* @param string $fieldname : fieldname possibly preceed by '<, >, <=, >=, ! or ='
|
|
* @param $value : value of field
|
|
* @return array : array with QUERY and PARAMS
|
|
*/
|
|
protected function _evaluate_condition (string $fieldname, $value) : array
|
|
{
|
|
$first_char = mb_substr($fieldname, 0, 1);
|
|
$second_char = mb_substr($fieldname, 1, 1);
|
|
|
|
switch(true)
|
|
{
|
|
//Important de traiter <= & >= avant < & >
|
|
case ('<=' == $first_char . $second_char) :
|
|
$true_fieldname = mb_substr($fieldname, 2);
|
|
$operator = '<=';
|
|
break;
|
|
|
|
case ('>=' == $first_char . $second_char) :
|
|
$true_fieldname = mb_substr($fieldname, 2);
|
|
$operator = '>=';
|
|
break;
|
|
|
|
case ('!=' == $first_char . $second_char) :
|
|
$true_fieldname = mb_substr($fieldname, 2);
|
|
$operator = '!=';
|
|
break;
|
|
|
|
case ('!' == $first_char) :
|
|
$true_fieldname = mb_substr($fieldname, 1);
|
|
$operator = '!=';
|
|
break;
|
|
|
|
case ('<' == $first_char) :
|
|
$true_fieldname = mb_substr($fieldname, 1);
|
|
$operator = '<';
|
|
break;
|
|
|
|
case ('>' == $first_char) :
|
|
$true_fieldname = mb_substr($fieldname, 1);
|
|
$operator = '>';
|
|
break;
|
|
|
|
case ('=' == $first_char) :
|
|
$true_fieldname = mb_substr($fieldname, 1);
|
|
$operator = '=';
|
|
break;
|
|
|
|
default :
|
|
$true_fieldname = $fieldname;
|
|
$operator = '=';
|
|
}
|
|
|
|
//Protect against injection in fieldname
|
|
$true_fieldname = preg_replace('#[^a-zA-Z0-9_]#', '', $true_fieldname);
|
|
|
|
$query = '`' . $true_fieldname . '` ' . $operator . ' :where_' . $true_fieldname;
|
|
$param = ['where_' . $true_fieldname => $value];
|
|
|
|
return ['QUERY' => $query, 'PARAM' => $param];
|
|
}
|
|
|
|
|
|
/**
|
|
* Get from table, posssibly with some conditions
|
|
* @param string $table : table name
|
|
* @param array $conditions : Where conditions to use, format 'fieldname' => 'value', fieldname can be preceed by operator '<, >, <=, >=, ! or = (by default)' to adapt comparaison operator
|
|
* @param ?string $order_by : name of column to order result by, null by default
|
|
* @param string $desc : L'ordre de tri (asc ou desc). Si non défini, ordre par défaut (ASC)
|
|
* @param string $limit : Le nombre maximum de résultats à récupérer (par défaut pas le limite)
|
|
* @param string $offset : Le nombre de résultats à ignorer (par défaut pas de résultats ignorés)
|
|
* @return mixed : False en cas d'erreur, sinon les lignes retournées
|
|
*/
|
|
protected function _select (string $table, array $conditions = [], ?string $order_by = null, bool $desc = false, ?int $limit = null, ?int $offset = null)
|
|
{
|
|
try
|
|
{
|
|
$wheres = array();
|
|
$params = array();
|
|
foreach ($conditions as $label => $value)
|
|
{
|
|
$condition = $this->_evaluate_condition($label, $value);
|
|
$wheres[] = $condition['QUERY'];
|
|
$params = array_merge($params, $condition['PARAM']);
|
|
}
|
|
|
|
$query = "SELECT * FROM `" . $table . "` WHERE 1 " . (count($wheres) ? 'AND ' : '') . implode(' AND ', $wheres);
|
|
|
|
if ($order_by !== null)
|
|
{
|
|
$query .= ' ORDER BY ' . $order_by;
|
|
|
|
if ($desc)
|
|
{
|
|
$query .= ' DESC';
|
|
}
|
|
}
|
|
|
|
if ($limit !== null)
|
|
{
|
|
$query .= ' LIMIT :limit';
|
|
if ($offset !== null)
|
|
{
|
|
$query .= ' OFFSET :offset';
|
|
}
|
|
}
|
|
|
|
|
|
$query = $this->pdo->prepare($query);
|
|
|
|
if ($limit !== null)
|
|
{
|
|
$query->bindParam(':limit', $limit, \PDO::PARAM_INT);
|
|
|
|
if ($offset !== null)
|
|
{
|
|
$query->bindParam(':offset', $offset, \PDO::PARAM_INT);
|
|
}
|
|
}
|
|
|
|
foreach ($params as $label => &$param)
|
|
{
|
|
$query->bindParam(':' . $label, $param);
|
|
}
|
|
|
|
$query->setFetchMode(\PDO::FETCH_ASSOC);
|
|
$query->execute();
|
|
|
|
return $query->fetchAll();
|
|
}
|
|
catch (\PDOException $e)
|
|
{
|
|
$error = $query->errorInfo();
|
|
|
|
//Get query string and params
|
|
ob_start();
|
|
$query->debugDumpParams();
|
|
$debug_string = ob_get_clean();
|
|
|
|
throw new \descartes\exceptions\DescartesExceptionSqlError(
|
|
'SQL Error : ' . "\n" .
|
|
'SQLSTATE : ' . $error[0] . "\n" .
|
|
'Driver Error Code : ' . $error[1] . "\n" .
|
|
'Driver Error Message : ' . $error[2] . "\n" .
|
|
'SQL QUERY DEBUG :' . "\n" .
|
|
'-----------------' . "\n" .
|
|
$debug_string . "\n" .
|
|
'-----------------' . "\n"
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Get one line from table, posssibly with some conditions
|
|
* see get
|
|
*/
|
|
protected function _select_one (string $table, array $conditions = [], ?string $order_by = null, bool $desc = false, ?int $limit = null, ?int $offset = null)
|
|
{
|
|
$result = $this->_select($table, $conditions, $order_by, $desc, $limit, $offset);
|
|
|
|
if (empty($result[0]))
|
|
{
|
|
return $result;
|
|
}
|
|
|
|
return $result[0];
|
|
}
|
|
|
|
/**
|
|
* Count line from table, posssibly with some conditions
|
|
* @param array $conditions : conditions of query Les conditions pour la mise à jour sous la forme "label" => "valeur". Un operateur '<, >, <=, >=, !' peux précder le label pour modifier l'opérateur par défaut (=)
|
|
*/
|
|
protected function _count (string $table, array $conditions = []) : int
|
|
{
|
|
try
|
|
{
|
|
$wheres = array();
|
|
$params = array();
|
|
foreach ($conditions as $label => $value)
|
|
{
|
|
$condition = $this->_evaluate_condition($label, $value);
|
|
$wheres[] = $condition['QUERY'];
|
|
$params = array_merge($params, $condition['PARAM']);
|
|
}
|
|
|
|
$query = "SELECT COUNT(*) as `count` FROM `" . $table . "` WHERE 1 " . (count($wheres) ? 'AND ' : '') . implode(' AND ', $wheres);
|
|
|
|
$query = $this->pdo->prepare($query);
|
|
|
|
foreach ($params as $label => &$param)
|
|
{
|
|
$query->bindParam(':' . $label, $param);
|
|
}
|
|
|
|
$query->setFetchMode(\PDO::FETCH_ASSOC);
|
|
$query->execute();
|
|
|
|
return $query->fetch()['count'];
|
|
}
|
|
catch (\PDOException $e)
|
|
{
|
|
$error = $query->errorInfo();
|
|
|
|
//Get query string and params
|
|
ob_start();
|
|
$query->debugDumpParams();
|
|
$debug_string = ob_get_clean();
|
|
|
|
throw new \descartes\exceptions\DescartesExceptionSqlError(
|
|
'SQL Error : ' . "\n" .
|
|
'SQLSTATE : ' . $error[0] . "\n" .
|
|
'Driver Error Code : ' . $error[1] . "\n" .
|
|
'Driver Error Message : ' . $error[2] . "\n" .
|
|
'SQL QUERY DEBUG :' . "\n" .
|
|
'-----------------' . "\n" .
|
|
$debug_string . "\n" .
|
|
'-----------------' . "\n"
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Update data from table with some conditions
|
|
* @param string $table : table name
|
|
* @param array $data : new data to set
|
|
* @param array $conditions : conditions of update, Les conditions pour la mise à jour sous la forme "label" => "valeur". Un operateur '<, >, <=, >=, !' peux précder le label pour modifier l'opérateur par défaut (=)
|
|
* @param array $conditions : conditions to use, format 'fieldname' => 'value', fieldname can be preceed by operator '<, >, <=, >=, ! or = (by default)' to adapt comparaison operator
|
|
* @return mixed : Number of line modified
|
|
*/
|
|
protected function _update (string $table, array $data, array $conditions = array()) : int
|
|
{
|
|
$params = array();
|
|
$sets = array();
|
|
|
|
|
|
foreach ($data as $label => $value)
|
|
{
|
|
$label = preg_replace('#[^a-zA-Z0-9_]#', '', $label);
|
|
$params['set_' . $label] = $value;
|
|
$sets[] = '`' . $label . '` = :set_' . $label . ' ';
|
|
}
|
|
|
|
|
|
$wheres = array();
|
|
foreach ($conditions as $label => $value)
|
|
{
|
|
$condition = $this->_evaluate_condition($label, $value);
|
|
$wheres[] = $condition['QUERY'];
|
|
$params = array_merge($params, $condition['PARAM']);
|
|
}
|
|
|
|
|
|
$query = "UPDATE `" . $table . "` SET " . implode(', ', $sets) . " WHERE 1 " . (count($wheres) ? " AND " : "") . implode(' AND ', $wheres);
|
|
return $this->_run_query($query, $params, self::ROWCOUNT);
|
|
}
|
|
|
|
/**
|
|
* Delete from table according to certain conditions
|
|
* @param string $table : Table name
|
|
* @param array $conditions : conditions to use, format 'fieldname' => 'value', fieldname can be preceed by operator '<, >, <=, >=, ! or = (by default)' to adapt comparaison operator
|
|
* @return mixed : Number of line deleted
|
|
*/
|
|
protected function _delete (string $table, array $conditions = []) : int
|
|
{
|
|
//On gère les conditions
|
|
$wheres = array();
|
|
$params = array();
|
|
foreach ($conditions as $label => $value)
|
|
{
|
|
$condition = $this->_evaluate_condition($label, $value);
|
|
$wheres[] = $condition['QUERY'];
|
|
$params = array_merge($params, $condition['PARAM']);
|
|
}
|
|
|
|
$query = "DELETE FROM `" . $table . "` WHERE 1 " . (count($wheres) ? " AND " : "") . implode(' AND ', $wheres);
|
|
return $this->_run_query($query, $params, self::ROWCOUNT);
|
|
}
|
|
|
|
/**
|
|
* Insert new line into table
|
|
* @param string $table : table name
|
|
* @param array $data : new data
|
|
* @return mixed : null on error, number of line inserted else
|
|
*/
|
|
protected function _insert (string $table, array $data) : ?int
|
|
{
|
|
$params = array();
|
|
$field_names = array();
|
|
|
|
foreach ($data as $field_name => $value)
|
|
{
|
|
//Protect against injection in fieldname
|
|
$field_name = preg_replace('#[^a-zA-Z0-9_]#', '', $field_name);
|
|
$params[$field_name] = $value;
|
|
$field_names[] = $field_name;
|
|
}
|
|
|
|
$query = "INSERT INTO `" . $table . "` (`" . implode('`, `', $field_names) . "`) VALUES(:" . implode(', :', $field_names) . ")";
|
|
|
|
//On retourne le nombre de lignes insérées
|
|
return $this->_run_query($query, $params, self::ROWCOUNT);
|
|
}
|
|
|
|
}
|