1110 lines
35 KiB
PHP
1110 lines
35 KiB
PHP
<?php
|
|
|
|
namespace orm\core;
|
|
|
|
use \database\core\DatabaseDriver;
|
|
use \orm\core\SQLBuilder;
|
|
|
|
|
|
class Rows{
|
|
|
|
|
|
/* CONSTANTES */
|
|
|
|
// {1} Conditions //
|
|
const COND_EQUAL = '__=__';
|
|
const COND_NOTEQ = '__<>__';
|
|
const COND_INF = '__<__';
|
|
const COND_SUP = '__>__';
|
|
const COND_INFEQ = '__<=__';
|
|
const COND_SUPEQ = '__>=__';
|
|
const COND_LIKE = '__LIKE__';
|
|
const COND_IN = '__IN__';
|
|
|
|
// {2} Fonctions d'aggrégation //
|
|
const SEL_AVG = '__AVG__';
|
|
const SEL_SUM = '__SUM__';
|
|
const SEL_MAX = '__MAX__';
|
|
const SEL_MIN = '__MIN__';
|
|
const SEL_COUNT = '__COUNT__';
|
|
const SEL_CONCAT = '__GROUP_CONCAT__';
|
|
|
|
const SEL_DISTINCT = true;
|
|
|
|
// {3} Gestion du Order By //
|
|
const ORDER_ASC = '__ASC__';
|
|
const ORDER_DESC = '__DESC__';
|
|
|
|
// {3} Constantes d'insertion //
|
|
const INSERT_DEFAULT = '__DEFAULT__'; // Valeur DEFAULT (pour insertion)
|
|
|
|
/* Attributs */
|
|
private $driver; // Database driver label
|
|
private $where; // Tableau associatif contenant les conditions
|
|
private $select; // Tableau contenant la liste des champs à afficher
|
|
private $orderby; // Tableau contenant la liste des orderby
|
|
private $unique; // VRAI si on attend une valeur unique
|
|
private $schema; // Tableau contenant les informations associées aux données
|
|
private $joined; // Tableau contenant les Rows liés
|
|
|
|
|
|
/* CONSTRUCTEUR
|
|
*
|
|
* @schema<Array> Tableau contenant les informations de la requête
|
|
* @driver<String> [optional] DatabaseDriver label
|
|
*
|
|
*/
|
|
public function __construct($schema, $driver=null){
|
|
/* (1) Database Driver */
|
|
$this->driver = $driver;
|
|
|
|
/* (2) On récupère les informations */
|
|
$this->schema = $schema;
|
|
|
|
/* (3) On initialise les conditions */
|
|
$this->where = [];
|
|
|
|
/* (4) On initialise les champs à retourner */
|
|
$this->select = [];
|
|
|
|
/* (5) On initialise l'ordonnancement' */
|
|
$this->orderby = [];
|
|
|
|
/* (6) On initialise le caractère 'unique' du résultat */
|
|
$this->unique = false;
|
|
|
|
/* (7) On initialise les jointures */
|
|
$this->joined = [];
|
|
}
|
|
|
|
|
|
/* FILTRE LES ENTREES D'UNE TABLE AVEC LA CLE PRIMAIRE SPECIFIEE
|
|
*
|
|
* @primary<mixed> Clé primaire simple
|
|
* OU
|
|
* @primary<Array> Clé primaire composée
|
|
*
|
|
* @return Rows<Rows> Tableau contenant toutes les entrées de la table
|
|
*
|
|
*/
|
|
public function whereId($primary){
|
|
/* [0] Vérification des paramètres
|
|
=========================================================*/
|
|
if( $primary == null )
|
|
return $this;
|
|
|
|
/* [1] On récupère les clés primaires
|
|
=========================================================*/
|
|
$keys = [];
|
|
|
|
foreach($this->schema['columns'] as $k=>$v)
|
|
if( $v['primary'] ) $keys[] = $k;
|
|
|
|
|
|
/* [2] Si clé simple
|
|
=========================================================*/
|
|
/* (1) On met au même format qu'une clé composée */
|
|
if( count($keys) == 1 )
|
|
$primary = [ $primary ];
|
|
|
|
|
|
|
|
/* [3] Si clé composée
|
|
=========================================================*/
|
|
$defaultWhere = $this->where;
|
|
|
|
/* (1) Pour chaque clé, On vérifie les TYPES */
|
|
foreach($keys as $i=>$key){
|
|
|
|
$inCond = is_array($primary[$i]) && count($primary[$i]) >= 2 && is_array($primary[$i][0]) && $primary[$i][1] == self::COND_IN;
|
|
|
|
/* (1) Si c'est une condition "IN"
|
|
---------------------------------------------------------*/
|
|
if( $inCond ){
|
|
|
|
/* (1) On vérifie le type de chaque valeur du IN */
|
|
$type = $this->schema['columns'][$key]['type'];
|
|
|
|
foreach($primary[$i][0] as $value){
|
|
if( $type == 'int' && !is_numeric($value) ){ $this->where = $defaultWhere; return $this; }
|
|
if( $type == 'float' && !is_numeric($value) ){ $this->where = $defaultWhere; return $this; }
|
|
if( in_array($type, ['text', 'varchar']) && !is_string($value) ){ $this->where = $defaultWhere; return $this; }
|
|
}
|
|
|
|
/* (2) Si c'est une condition "simple"
|
|
---------------------------------------------------------*/
|
|
}else{
|
|
|
|
/* (1) Si le type de condition est manquant, on met EQUAL par défaut */
|
|
if( !is_array($primary[$i]) )
|
|
$primary[$i] = [ $primary[$i], self::COND_EQUAL ];
|
|
|
|
/* (2) On vérifie le type de chaque valeur */
|
|
$type = $this->schema['columns'][$key]['type'];
|
|
|
|
if( $type == 'int' && !is_numeric($primary[$i][0]) ){ $this->where = $defaultWhere; return $this; }
|
|
if( $type == 'float' && !is_numeric($primary[$i][0]) ){ $this->where = $defaultWhere; return $this; }
|
|
if( in_array($type, ['text', 'varchar']) && !is_string($primary[$i][0]) ){ $this->where = $defaultWhere; return $this; }
|
|
|
|
}
|
|
|
|
|
|
/* (6) Si type OK, on enregistre la condition */
|
|
if( !isset($this->where[$key]) )
|
|
$this->where[$key] = [];
|
|
|
|
/* (7) On ajoute la condition */
|
|
$this->where[$key][] = $primary[$i];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* [4] On renvoie l'object courant
|
|
=========================================================*/
|
|
return $this;
|
|
}
|
|
|
|
|
|
/* FILTRAGE DYNAMIQUES
|
|
*
|
|
* @method<String> Nom de la méthode
|
|
* @parameter<mixed> Valeur du paramètre
|
|
* @parameter<Array> Valeur du paramètre + type de vérification (tableau)
|
|
*
|
|
* @return this<Rows> Retourne l'object courant
|
|
*
|
|
*/
|
|
public function __call($m, $a){
|
|
/* [0] On vérifie que la requête est du type 'getBy{Attribute}'
|
|
=========================================================*/
|
|
if( !preg_match('/^where(.+)$/', $m, $regex) ) // si requête incorrecte, on ne fais rien
|
|
return $this;
|
|
|
|
|
|
/* [1] On récupère le nom de la colonne
|
|
=========================================================*/
|
|
$column_name = '';
|
|
|
|
/* (1) formatte la requête 'MyAttribute' -> 'my_attribute' */
|
|
for( $l = 0, $ll = strlen($regex[1]) ; $l < $ll ; $l++ ){
|
|
$letter = $regex[1][$l];
|
|
|
|
// Si la lettre est en majuscule mais que c'est pas la première ni un seul mot
|
|
if( strtoupper($letter) == $letter && $l > 0 )
|
|
$column_name .= '_';
|
|
|
|
$column_name .= strtolower($letter);
|
|
}
|
|
|
|
|
|
/* (2) On vérifie que la colonne existe */
|
|
if( !isset($this->schema['columns'][$column_name]) )
|
|
return $this; // si n'existe pas, on ne fait rien
|
|
|
|
|
|
/* [2] On vérifie le type du paramètre
|
|
=========================================================*/
|
|
// On délègue
|
|
$args = array_merge([$column_name], $a);
|
|
return call_user_func_array([$this, 'where'], $args);
|
|
}
|
|
|
|
|
|
public function where($field){
|
|
// get arguments
|
|
$args = array_slice(func_get_args(), 1);
|
|
|
|
/* [1] Vérification de l'argument @field
|
|
=========================================================*/
|
|
/* (1) Type de @field */
|
|
if( !is_string($field) )
|
|
return $this;
|
|
|
|
/* (2) On vérifie que la colonne existe */
|
|
if( !isset($this->schema['columns'][$field]) )
|
|
return $this; // si n'existe pas, on ne fait rien
|
|
|
|
/* [2] On vérifie le type du paramètre
|
|
=========================================================*/
|
|
/* (1) Si au moins 1 param */
|
|
if( count($args) < 1 )
|
|
return $this;
|
|
|
|
|
|
/* [3] If `IN` condition
|
|
=========================================================*/
|
|
$defaultWhere = $this->where;
|
|
$inCond = count($args[0]) > 1 && is_array($args[0][0]) && $args[0][1] == self::COND_IN;
|
|
|
|
// erreur
|
|
if( is_array($args[0][0]) && !$inCond )
|
|
return $this;
|
|
|
|
/* (1) Si c'est une condition "IN"
|
|
---------------------------------------------------------*/
|
|
if( $inCond ){
|
|
|
|
/* (1) On vérifie le type de chaque valeur du IN */
|
|
$type = $this->schema['columns'][$field]['type'];
|
|
|
|
foreach($args[0][0] as $value){
|
|
if( $type == 'int' && !is_numeric($value) ) return $this;
|
|
if( $type == 'float' && !is_numeric($value) ) return $this;
|
|
if( in_array($type, ['text', 'varchar']) && !is_string($value) ) return $this;
|
|
}
|
|
|
|
/* (2) Si c'est une condition "simple"
|
|
---------------------------------------------------------*/
|
|
}else{
|
|
|
|
/* (1) Si le type de condition est manquant, on met EQUAL par défaut */
|
|
if( !is_array($args[0]) )
|
|
$args[0] = [ $args[0], self::COND_EQUAL ];
|
|
|
|
/* (2) On vérifie le type de chaque valeur */
|
|
$type = $this->schema['columns'][$field]['type'];
|
|
|
|
if( $type == 'int' && !is_numeric($args[0][0]) ) return $this;
|
|
if( $type == 'float' && !is_numeric($args[0][0]) ) return $this;
|
|
if( in_array($type, ['text', 'varchar']) && !is_string($args[0][0]) ) return $this;
|
|
|
|
}
|
|
|
|
|
|
/* [3] Si type OK, on enregistre la condition
|
|
=========================================================*/
|
|
/* (1) Si aucune condition pour ce champ, on crée un tableau */
|
|
if( !isset($this->where[$field]) )
|
|
$this->where[$field] = [];
|
|
|
|
/* (2) On ajoute la condition */
|
|
$this->where[$field][] = $args[0];
|
|
|
|
// On retourne l'object courant
|
|
return $this;
|
|
}
|
|
|
|
/* SELECTIONNE UNIQUEMENT LE CHAMP SELECTIONNE
|
|
*
|
|
* @field<String> Libellé du champ à afficher
|
|
* @func<CONST> Fonction d'aggrégation (ou NULL)
|
|
* @distinct<Boolean> Clause DISTINCT
|
|
* @alias<String> Alias du champ
|
|
*
|
|
* @return this<Rows> Retourne l'object courant
|
|
*
|
|
*/
|
|
public function select($field=null, $func=null, $distinct=false, $alias=null){
|
|
/* [1] On formatte les champs
|
|
=========================================================*/
|
|
/* (1) On vérifie le type de @field */
|
|
if( !is_string($field) )
|
|
return $this;
|
|
|
|
/* (2) On vérifie que la colonne @field existe, sinon on quitte */
|
|
if( !isset($this->schema['columns'][$field]) && $field != '*' )
|
|
return $this;
|
|
|
|
/* (3) On vérifie @func */
|
|
$funcList = [self::SEL_AVG, self::SEL_SUM, self::SEL_MAX, self::SEL_MIN, self::SEL_COUNT, self::SEL_CONCAT];
|
|
|
|
// Si condition non nulle et pas référencée, on quitte
|
|
if( !is_null($func) && !in_array($func, $funcList) )
|
|
return $this;
|
|
|
|
// If CONCAT -> force type to TEXT
|
|
if( $func === Rows::SEL_CONCAT )
|
|
$this->schema['columns'][$field]['type'] = 'text';
|
|
|
|
/* (4) On met la valeur par défaut à @distinct si type mauvais */
|
|
$distinct = !is_bool($distinct) ? false : $distinct;
|
|
|
|
/* (5) Si @alias incorrect, on met @field par défaut */
|
|
if( !is_string($alias) )
|
|
$alias = $field;
|
|
|
|
/* [2] On enregistre le champ
|
|
=========================================================*/
|
|
/* (1) Si "SELECT *" on ajout tous les champs */
|
|
if( $field === '*' ){
|
|
|
|
foreach($this->schema['columns'] as $f=>$c)
|
|
if( !isset($this->select[$f]) )
|
|
$this->select[$f] = [$func, $distinct, $f];
|
|
|
|
/* (2) Si aucun SELECT pour ce champ, on le crée */
|
|
}else{
|
|
|
|
if( !isset($this->select[$field]) )
|
|
$this->select[$field] = [$func, $distinct, $alias];
|
|
}
|
|
|
|
|
|
/* [3] On retourne l'object courant
|
|
=========================================================*/
|
|
return $this;
|
|
}
|
|
|
|
|
|
/* SELECTIONNE L'ORDONNANCEMENT DES RESULTATS
|
|
*
|
|
* @field<String> Libellé du champ à afficher
|
|
* @order<CONST> Gestion de l'ordre ASC/DESC (ou NULL)
|
|
*
|
|
* @return this<Rows> Retourne l'object courant
|
|
*
|
|
*/
|
|
public function orderby($field=null, $order=null){
|
|
/* [1] On formatte les champs
|
|
=========================================================*/
|
|
/* (1) On vérifie le type de @field */
|
|
if( !is_string($field) )
|
|
return $this;
|
|
|
|
/* (2) On vérifie que la colonne @field existe, sinon on quitte */
|
|
if( !isset($this->schema['columns'][$field]) && $field != '*' )
|
|
return $this;
|
|
|
|
/* (3) On vérifie @order */
|
|
$orderList = [self::ORDER_ASC, self::ORDER_DESC];
|
|
|
|
// Valeur si NULL
|
|
$order = is_null($order) ? $orderList[0] : $order;
|
|
|
|
// Si ordre non référencée, on quitte
|
|
if( !in_array($order, $orderList) )
|
|
return $this;
|
|
|
|
|
|
/* [2] On enregistre le champ
|
|
=========================================================*/
|
|
/* (1) On crée le ORDER_BY pour ce champ */
|
|
$this->orderby[$field] = $order;
|
|
|
|
|
|
/* [3] On retourne l'object courant
|
|
=========================================================*/
|
|
return $this;
|
|
}
|
|
|
|
|
|
/* JOINT UNE SECONDE TABLE ()
|
|
*
|
|
* @localField<String> Nom d'une colonne locale
|
|
* @rows<Rows> Rows d'une autre table
|
|
*
|
|
* @return this<Rows> Retourne l'object courant
|
|
*
|
|
*/
|
|
public function join($localField, $rows){
|
|
/* [0] Vérification / Formattage des paramètres
|
|
=========================================================*/
|
|
/* (1) Si le champ n'est pas au bon format */
|
|
if( !is_string($localField) )
|
|
return $this;
|
|
|
|
/* (2) Si @rows n'est pas au bon format */
|
|
if( !($rows instanceof Rows) )
|
|
return $this;
|
|
|
|
/* (3) Si le champ n'existe pas dans la table */
|
|
if( !isset($this->schema['columns'][$localField]) )
|
|
return $this;
|
|
|
|
/* (4) On récupère les données du champ local dans une variable */
|
|
$localFieldData = $this->schema['columns'][$localField];
|
|
|
|
/* [1] On vérifie que la clé étrangère est correcte
|
|
=========================================================*/
|
|
/* (1) Si la colonne n'existe pas et qu'elle n'est pas primaire, on ne fait rien */
|
|
if( !isset($localFieldData['references']) && !$localFieldData['primary'] )
|
|
return $this;
|
|
|
|
/* (2) On vérifie que la colonne a une référence vers la table de @rows */
|
|
$referencesToRows = isset($localFieldData['references']) && $localFieldData['references'][0] == $rows->schema['table'];
|
|
$rowsField = null;
|
|
|
|
/* (3) On vérifie que la colonne est la référence d'un champ de @rows */
|
|
$referencesFromRows = false;
|
|
|
|
// On vérifie chaque champ de @rows
|
|
foreach($rows->schema['columns'] as $field=>$data)
|
|
// Si un champ de la table de @rows a pour référence le champ local
|
|
if( isset($data['references']) && $data['references'][0] == $this->schema['table'] && $data['references'][1] == $localField ){
|
|
$referencesFromRows = true;
|
|
$rowsField = $field;
|
|
break;
|
|
}
|
|
|
|
/* (4) On vérifie que la colonne a la même référence qu'une colonne de @rows */
|
|
$referencesSameTarget = false;
|
|
|
|
// On vérifie toutes les colonnes de @rows
|
|
foreach($rows->schema['columns'] as $field=>$data)
|
|
// Si on trouve un champ avec la même référence
|
|
if( isset($data['references']) && isset($localFieldData['references']) && count(array_diff($data['references'], $localFieldData['references'])) == 0 ){
|
|
$referencesSameTarget = true;
|
|
$rowsField = $field; // On enregistre le champ qui a la même cible
|
|
break;
|
|
}
|
|
|
|
/* (4) Si aucune référence en commun, on ne fait rien */
|
|
if( !$referencesToRows && !$referencesFromRows && !$referencesSameTarget )
|
|
return $this;
|
|
|
|
|
|
/* [2] On enregistre la référence
|
|
=========================================================*/
|
|
$this->joined[$localField] = [
|
|
'object' => $rows,
|
|
'field' => is_null($rowsField) ? $localFieldData['references'][1] : $rowsField // On met le nom du champ de @rows à lier
|
|
];
|
|
|
|
|
|
/* [3] On retourne l'object courant
|
|
=========================================================*/
|
|
return $this;
|
|
}
|
|
|
|
|
|
/* PERMET DE DIRE QUE L'ON VEUT UN RESULTAT UNIQUE
|
|
*
|
|
* @return this<Rows> Retourne l'object courant
|
|
*
|
|
*/
|
|
public function unique(){
|
|
/* [1] On enregistre le choix
|
|
=========================================================*/
|
|
$this->unique = true;
|
|
|
|
|
|
/* [2] On retourne l'object courant
|
|
=========================================================*/
|
|
return $this;
|
|
}
|
|
|
|
|
|
/* MODIFIE DES ENTREES (SANS MODIFICATION DE CLE PRIMAIRE POSSIBLE)
|
|
*
|
|
* @updates<Array> Tableau associatif contenant les nouvelles valeurs
|
|
*
|
|
* @return updated<Boolean> Retourne si TRUE/FALSE la modification a bien été faite
|
|
*
|
|
*/
|
|
public function edit($updates){
|
|
/* [0] Vérification des paramètres
|
|
=========================================================*/
|
|
/* (1) Si c'est pas un tableau, erreur */
|
|
if( !is_array($updates) )
|
|
return false;
|
|
|
|
/* (2) On retire les champ inconnus / clés primaires */
|
|
$cleared = [];
|
|
|
|
// Pour chaque entrée du tableau
|
|
foreach($updates as $field=>$value)
|
|
if( isset($this->schema['columns'][$field]) && !$this->schema['columns'][$field]['primary'] ) // Champ existe et n'est pas clé primaire
|
|
$cleared[$field] = $value;
|
|
|
|
/* (3) On vérifie les types des champs */
|
|
foreach($cleared as $field=>$value){
|
|
|
|
$type = $this->schema['columns'][$field]['type'];
|
|
|
|
// {1} Si de type INT/FLOAT et pas numérique, on retire le champ //
|
|
if( in_array($type, ['int', 'float']) && !is_numeric($value) )
|
|
unset($cleared[$field]);
|
|
|
|
// {2} Si de type TEXT/VARCHAR et pas string, on retire le champ //
|
|
if( in_array($type, ['text', 'varchar']) && !is_string($value) )
|
|
unset($cleared[$field]);
|
|
|
|
}
|
|
|
|
/* (4) Si on a plus de champ, on retourne l'object courant */
|
|
if( count($cleared) == 0 )
|
|
return false;
|
|
|
|
|
|
|
|
|
|
/* [1] Initialisation des paramètres
|
|
=========================================================*/
|
|
/* (1) On initialise la requête */
|
|
$requestS = [];
|
|
|
|
/* (2) On initialise les paramètres */
|
|
$bound = [];
|
|
|
|
|
|
|
|
/* [2] Rédaction de la clause UPDATE
|
|
=========================================================*/
|
|
$requestS['UPDATE'] = SQLBuilder::UPDATE($this->schema['table']);
|
|
|
|
|
|
/* [3] Rédaction de la clause SET
|
|
=========================================================*/
|
|
/* (1) On met tout les champs à modifier */
|
|
$requestS['SET'] = SQLBuilder::SET($cleared, $bound);
|
|
|
|
|
|
/* [4] On rédige la clause WHERE/AND
|
|
=========================================================*/
|
|
/* (1) On met les conditions locales */
|
|
$requestS['WHERE'] = [];
|
|
$c = 0;
|
|
foreach($this->where as $field=>$conditions)
|
|
foreach($conditions as $cdt=>$value){
|
|
|
|
if( $value[1] == self::COND_IN ) // Si condition de type IN
|
|
$requestS['WHERE'][$c] = SQLBuilder::IN([$this->schema['table'], $field], $value[0], $c, $bound);
|
|
else // Sinon
|
|
$requestS['WHERE'][$c] = SQLBuilder::WHERE([$this->schema['table'], $field], $value, $c, $bound);
|
|
|
|
$c++;
|
|
}
|
|
|
|
/* (2) On ajoute les jointures */
|
|
// Note: On ajoute les requêtes des tables de jointures dans la clause WHERE //
|
|
foreach($this->joined as $field=>$data){
|
|
// {1} On récupère la requête/les params de chaque jointure //
|
|
$joinedFetched = $data['object']->fetch(false);
|
|
|
|
// {2} On met la clé étrangère pour la clause SELECT //
|
|
$joinedFetched['request']['SELECT'] = [ $data['object']->schema['table'].'.'.$data['field'] ];
|
|
|
|
// {3} On construit la nouvelle requête //
|
|
$joinedRequest = SQLBuilder::BUILD($joinedFetched['request']);
|
|
|
|
// {4} On supprime les retours à la ligne //
|
|
$joinedRequest = str_replace("\n", " ", $joinedRequest);
|
|
|
|
// {5} On l'ajoute à la clause FROM avec comme alias le nom de la table de @data['object'] //
|
|
$requestS['WHERE'][] = $this->schema['table'].".$field in ($joinedRequest)";
|
|
|
|
// {6} On ajoute les variables à la requête courante //
|
|
$bound = array_merge($bound, $joinedFetched['bound']);
|
|
}
|
|
|
|
|
|
/* [5] Clause LIMIT
|
|
=========================================================*/
|
|
$requestS['LIMIT'] = ($this->unique) ? SQLBuilder::LIMIT(1) : SQLBuilder::LIMIT([]);
|
|
|
|
|
|
/* [6] On prépare et compose la requête
|
|
=========================================================*/
|
|
/* (1) On compose la requête */
|
|
$requestString = SQLBuilder::BUILD($requestS).';';
|
|
|
|
/* (2) On prépare la requête */
|
|
$request = DatabaseDriver::getPDO($this->driver)->prepare($requestString);
|
|
|
|
|
|
|
|
/* [7] On exécute la requête et retourne le résultat
|
|
=========================================================*/
|
|
/* (1) On exécute la requête */
|
|
$updated = $request->execute($bound);
|
|
|
|
/* (2) On retourne l'état de la requête */
|
|
return $updated;
|
|
}
|
|
|
|
/* AJOUTE UNE ENTREE DANS LA TABLE
|
|
*
|
|
* @entry<Array> Tableau associatif de la forme (colonne => valeur)
|
|
* OU
|
|
* @entries<Array> Tableau de la forme ([entry1, entry2])
|
|
*
|
|
* @return status<Boolean> Retourne si TRUE ou FALSE les entrées ont bien été supprimées
|
|
*
|
|
*/
|
|
public function insert($entry){
|
|
/* [0] On vérifie les paramètres
|
|
=========================================================*/
|
|
/* (1) Si c'est pas un tableau avec au moins une entrée, erreur */
|
|
if( !is_array($entry) || count($entry) == 0 )
|
|
return false;
|
|
|
|
// S'il n'y a qu'une entrée, on met au même format que s'il y en avait plusieurs
|
|
$firstIndex = array_keys($entry)[0];
|
|
if( !is_array($entry[$firstIndex]) )
|
|
$entry = [ $entry ];
|
|
|
|
/* (2) On retire les champ inconnus */
|
|
$cleared = [];
|
|
|
|
// Pour chaque entrée du tableau
|
|
foreach($entry as $i=>$set){
|
|
$cleared[$i] = [];
|
|
|
|
foreach($set as $field=>$value){
|
|
|
|
if( isset($this->schema['columns'][$field]) ) // Champ existe
|
|
$cleared[$i][$field] = $value;
|
|
}
|
|
|
|
}
|
|
|
|
/* (3) On vérifie les types des champs */
|
|
foreach($cleared as $i=>$set){
|
|
|
|
foreach($set as $field=>$value){
|
|
|
|
$type = $this->schema['columns'][$field]['type'];
|
|
|
|
// {1} Si de type INT/FLOAT et pas numérique, on retire le champ //
|
|
if( in_array($type, ['int', 'float']) && !is_numeric($value) && $value != self::INSERT_DEFAULT )
|
|
unset($cleared[$i][$field]);
|
|
|
|
// {2} Si de type TEXT/VARCHAR et pas string, on retire le champ //
|
|
if( in_array($type, ['text', 'varchar']) && !is_string($value) && $value != self::INSERT_DEFAULT )
|
|
unset($cleared[$i][$field]);
|
|
}
|
|
|
|
/* (4) Si il manque des données, erreur */
|
|
if( count($cleared[$i]) != count($this->schema['columns']) )
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
/* [1] On crée la requête
|
|
=========================================================*/
|
|
/* (1) Clause INSERT INTO table */
|
|
$requestS = 'INSERT INTO '.$this->schema['table']."(";
|
|
|
|
/* (2) Clause : table(col1, col2, ...) */
|
|
$c = 0;
|
|
foreach($this->schema['columns'] as $field=>$value){
|
|
if( $c > 0 ) $requestS .= ', ';
|
|
$requestS .= $field;
|
|
|
|
$c++;
|
|
}
|
|
|
|
// Fin de clause
|
|
$requestS .= ")\n";
|
|
|
|
|
|
/* (3) Clause : VALUES(val1, val2, ...) */
|
|
$v = 0;
|
|
foreach($cleared as $i=>$set){
|
|
if( $v == 0 ) $requestS .= 'VALUES(';
|
|
else $requestS .= ",\n\t(";
|
|
|
|
$c = 0;
|
|
foreach($this->schema['columns'] as $field=>$column){
|
|
if( $c > 0 ) $requestS .= ', ';
|
|
|
|
// Si l'entrée est donnée
|
|
if( isset($set[$field]) )
|
|
if( $set[$field] == self::INSERT_DEFAULT ) $requestS .= 'DEFAULT'; // On insère directement les valeurs 'DEFAULT'
|
|
else $requestS .= ':insert_'.$field.'_'.$i;
|
|
else
|
|
$requestS .= 'DEFAULT';
|
|
|
|
$c++;
|
|
}
|
|
|
|
// Fin de clause
|
|
$requestS .= ")";
|
|
$v++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* [2] On bind les paramètres et exécute la requête
|
|
=========================================================*/
|
|
/* (0) On initialise la requête et les paramètres */
|
|
$request = DatabaseDriver::getPDO($this->driver)->prepare($requestS.';');
|
|
$bound = [];
|
|
|
|
/* (1) On bind les paramètres */
|
|
foreach($cleared as $i=>$set)
|
|
foreach($this->schema['columns'] as $field=>$column)
|
|
if( isset($set[$field]) && $set[$field] != self::INSERT_DEFAULT )
|
|
$bound[':insert_'.$field.'_'.$i] = $set[$field];
|
|
|
|
|
|
/* [3] On exécute la requête et envoie le status
|
|
=========================================================*/
|
|
$inserted = $request->execute($bound);
|
|
|
|
// On retourne le status
|
|
return $inserted;
|
|
}
|
|
|
|
|
|
/* SUPPRIME LES ENTREES
|
|
*
|
|
* @return status<Boolean> Retourne si TRUE ou FALSE les entrées ont bien été supprimées
|
|
*
|
|
*/
|
|
public function delete(){
|
|
/* [0] Initialisation des paramètres
|
|
=========================================================*/
|
|
/* (1) On initialise la requête */
|
|
$requestS = [];
|
|
|
|
/* (2) On initialise les paramètres */
|
|
$bound = [];
|
|
|
|
|
|
/* [1] Clause DELETE FROM
|
|
=========================================================*/
|
|
$requestS['DELETE'] = SQLBuilder::DELETE($this->schema['table']);
|
|
|
|
|
|
/* [2] On rédige la clause WHERE/AND
|
|
=========================================================*/
|
|
/* (1) On met les conditions locales */
|
|
$requestS['WHERE'] = [];
|
|
$c = 0;
|
|
foreach($this->where as $field=>$conditions)
|
|
foreach($conditions as $cdt=>$value){
|
|
|
|
if( $value[1] == self::COND_IN ) // Si condition de type IN
|
|
$requestS['WHERE'][$c] = SQLBuilder::IN([$this->schema['table'], $field], $value[0], $c, $bound);
|
|
else // Sinon
|
|
$requestS['WHERE'][$c] = SQLBuilder::WHERE([$this->schema['table'], $field], $value, $c, $bound);
|
|
|
|
$c++;
|
|
}
|
|
|
|
|
|
/* (2) On ajoute les jointures */
|
|
// Note: On ajoute les requêtes des tables de jointures dans la clause WHERE //
|
|
foreach($this->joined as $field=>$data){
|
|
// {1} On récupère la requête/les params de chaque jointure //
|
|
$joinedFetched = $data['object']->fetch(false);
|
|
|
|
// {2} On met la clé étrangère pour la clause SELECT //
|
|
$joinedFetched['request']['SELECT'] = [ $data['object']->schema['table'].'.'.$data['field'] ];
|
|
|
|
// {3} On construit la nouvelle requête //
|
|
$joinedRequest = SQLBuilder::BUILD($joinedFetched['request']);
|
|
|
|
// {4} On supprime les retours à la ligne //
|
|
$joinedRequest = str_replace("\n", " ", $joinedRequest);
|
|
|
|
// {5} On l'ajoute à la clause FROM avec comme alias le nom de la table de @data['object'] //
|
|
$requestS['WHERE'][] = $this->schema['table'].".$field in ($joinedRequest)";
|
|
|
|
// {6} On ajoute les variables à la requête courante //
|
|
$bound = array_merge($bound, $joinedFetched['bound']);
|
|
}
|
|
|
|
|
|
/* [3] Clause LIMIT
|
|
=========================================================*/
|
|
$requestS['LIMIT'] = ($this->unique) ? SQLBuilder::LIMIT(1) : SQLBuilder::LIMIT([]);
|
|
|
|
|
|
/* [4] On prépare et compose la requête
|
|
=========================================================*/
|
|
/* (1) On compose la requête */
|
|
$requestString = SQLBuilder::BUILD($requestS).';';
|
|
|
|
/* (2) On prépare la requête */
|
|
$request = DatabaseDriver::getPDO($this->driver)->prepare($requestString);
|
|
|
|
/* [5] On exécute la requête et retourne le résultat
|
|
=========================================================*/
|
|
/* (1) On exécute la requête */
|
|
$deleted = $request->execute($bound);
|
|
|
|
/* (2) On retourne l'état de la requête */
|
|
return $deleted;
|
|
}
|
|
|
|
|
|
/* RETOURNE LES DONNEES / NULL si une erreur survient
|
|
*
|
|
* @execute<Boolean> VRAI si on veut exécuter la requête, sinon renvoie [requete, boundParams]
|
|
*
|
|
* @return data<Array> Tableau contenant les champs sélectionnés
|
|
* @return data<mixed> Valeur du champ sélectionné (si 1 seul champ)
|
|
* @return ERROR<FALSE> Retourne FALSE si rien n'est trouvé
|
|
*
|
|
*/
|
|
public function fetch($execute=true){
|
|
/* [0] On initialise
|
|
=========================================================*/
|
|
/* (1) On initialise la requête */
|
|
$requestS = [];
|
|
|
|
/* (2) On initialise le conteneur des variables "bindés" */
|
|
$bound = [];
|
|
|
|
/* (3) On récupère la requête générée par chaque @rows de jointure */
|
|
$joinedFetched = [];
|
|
foreach($this->joined as $field=>$data)
|
|
$joinedFetched[$field] = $data['object']->fetch(false);
|
|
|
|
|
|
/* [1] On rédige la clause SELECT
|
|
=========================================================*/
|
|
/* (1) On formatte les données */
|
|
$selectTables = [];
|
|
|
|
/* (2) On ajoute les champs locaux */
|
|
$selectTables[$this->schema['table']] = $this->select;
|
|
|
|
|
|
/* (4) On ajoute les champs des jointures (récursif)*/
|
|
foreach($joinedFetched as $field=>$data){
|
|
foreach($data['request']['SELECT'] as $table=>$fields)
|
|
foreach($fields as $field=>$sel){
|
|
// Si aucune entrée pour cette table, on l'ajoute
|
|
if( !isset($selectTables[$table]) )
|
|
$selectTables[$table] = [];
|
|
|
|
$selectTables[$table][$field] = $sel;
|
|
}
|
|
}
|
|
|
|
/* (3) On génère la clause SELECT */
|
|
$requestS['SELECT'] = SQLBuilder::SELECT($selectTables);
|
|
|
|
|
|
/* [2] On rédige la clause FROM
|
|
========================================================*/
|
|
/* (0) On initialise la clause */
|
|
$requestS['FROM'] = [];
|
|
|
|
/* (1) Table locale */
|
|
$requestS['FROM'][] = $this->schema['table'];
|
|
|
|
/* (2) On ajoute les tables de jointures */
|
|
// Note: On ajoute les tables de jointures dans la clause FROM avec comme alias le nom de la table
|
|
foreach($joinedFetched as $field=>$data)
|
|
// On ajoute la clause FROM de jointure à la clause FROM locale //
|
|
$requestS['FROM'] = array_merge($data['request']['FROM'], $requestS['FROM']);
|
|
|
|
/* [5] On rédige la clause WHERE/AND
|
|
=========================================================*/
|
|
/* (1) On met les conditions locales */
|
|
$c = 0;
|
|
$requestS['WHERE'] = [];
|
|
foreach($this->where as $field=>$conditions)
|
|
foreach($conditions as $cdt=>$value){
|
|
|
|
if( $value[1] === self::COND_IN ) // Si condition IN
|
|
$requestS['WHERE'][$c] = SQLBuilder::IN([$this->schema['table'], $field], $value[0], $c, $bound);
|
|
else // Sinon
|
|
$requestS['WHERE'][$c] = SQLBuilder::WHERE([$this->schema['table'], $field], $value, $c, $bound);
|
|
|
|
$c++;
|
|
}
|
|
|
|
/* (2) On ajoute les jointures */
|
|
foreach($this->joined as $localField=>$data){
|
|
$requestS['WHERE'][$c] = $this->schema['table'].".$localField = ".$data['object']->schema['table'].".".$data['field'];
|
|
$c++;
|
|
}
|
|
|
|
/* (3) On ajoute les conditions des jointures */
|
|
foreach($joinedFetched as $field=>$data){
|
|
/* On ajoute la clause WHERE de jointure à la clause WHERE locale */
|
|
$requestS['WHERE'] = array_merge($data['request']['WHERE'], $requestS['WHERE']);
|
|
|
|
/* On ajoute les variables à la requête courante */
|
|
$bound = array_merge($bound, $data['bound']);
|
|
}
|
|
|
|
|
|
/* [6] Clause GROUP BY
|
|
=========================================================*/
|
|
/* (0) On initialise la liste des @rows non aggrégés */
|
|
$groupBy = [];
|
|
|
|
/* (1) On cherche dans les champs locaux local */
|
|
foreach($selectTables as $table=>$fields)
|
|
foreach($fields as $field=>$sel)
|
|
// Si aucune fonction d'aggrégation
|
|
if( is_null($sel[0]) ){
|
|
if( !isset($groupBy[$table]) )
|
|
$groupBy[$table] = [];
|
|
|
|
// Si le champ est *, on trouve les clés primaires
|
|
if( $field == '*' ){
|
|
$columns = Table::get($table)->schema['columns'];
|
|
foreach($columns as $col=>$data)
|
|
if( $data['primary'] )
|
|
$groupBy[$table][] = $col;
|
|
}else
|
|
$groupBy[$table][] = $field;
|
|
|
|
|
|
$groupBy[$table] = array_unique($groupBy[$table]);
|
|
}
|
|
|
|
|
|
/* (2) On rédige la clause GROUP BY */
|
|
if( count($groupBy) > 0)
|
|
$requestS['GROUPBY'] = SQLBuilder::GROUPBY($groupBy);
|
|
|
|
/* [6] Clause ORDER BY
|
|
=========================================================*/
|
|
/* (1) On formatte les données */
|
|
$orderTables = [];
|
|
|
|
/* (2) On ajoute les champs locaux */
|
|
if( count($this->orderby) > 0 )
|
|
$orderTables[$this->schema['table']] = $this->orderby;
|
|
|
|
/* (4) On ajoute les champs des jointures (récursif)*/
|
|
foreach($joinedFetched as $field=>$data){
|
|
foreach($data['request']['ORDERBY'] as $table=>$fields) // pour chaque ensemble de champ de chaque table
|
|
foreach($fields as $field=>$orderBy) // Pour chaque orderby de chaque champ
|
|
|
|
if( count($orderBy) > 0 )
|
|
$orderTables[$table][$field] = $orderBy;
|
|
|
|
}
|
|
|
|
/* (3) On génère la clause SELECT */
|
|
$requestS['ORDERBY'] = SQLBuilder::ORDERBY($orderTables);
|
|
|
|
/* [6] Clause LIMIT
|
|
=========================================================*/
|
|
$requestS['LIMIT'] = ($this->unique) ? SQLBuilder::LIMIT(1) : SQLBuilder::LIMIT([]);
|
|
|
|
|
|
/* [7] On compose/prépare la requête
|
|
=========================================================*/
|
|
/* (1) Si on veut pas exécuter on renvoie la requête + boundParams */
|
|
if( !$execute )
|
|
return [ 'request' => $requestS, 'bound' => $bound];
|
|
|
|
/* (2) On compose la requête */
|
|
$requestString = SQLBuilder::BUILD($requestS).';';
|
|
// var_dump($requestString);
|
|
|
|
/* (3) On prépare la requête */
|
|
$request = DatabaseDriver::getPDO($this->driver)->prepare($requestString);
|
|
|
|
|
|
/* [8] On exécute la requête et retourne le résultat
|
|
=========================================================*/
|
|
/* (1) On exécute la requête */
|
|
$request->execute($bound);
|
|
|
|
/* (2) Si unique */
|
|
if( $this->unique )
|
|
return $this->format( $request->fetch() );
|
|
|
|
/* (3) Si tout */
|
|
return $this->format( $request->fetchAll() );
|
|
}
|
|
|
|
|
|
/* ON FORMATTE LES DONNEES DE SORTIE
|
|
*
|
|
* @data<Array> Données / Tableau de données
|
|
*
|
|
* @return formatted<Array> Données formattées / Tableau de données formatté
|
|
*
|
|
*/
|
|
private function format($data){
|
|
/* [0] On initialise le processus
|
|
=========================================================*/
|
|
/* (0) Initialisation du retour */
|
|
$formatted = $data;
|
|
|
|
/* (1) On vérifie qu'il s'agit d'un tableau (non vide) */
|
|
if( !is_array($formatted) || count($formatted) < 1 )
|
|
return $formatted;
|
|
|
|
/* (2) On regarde si c'est des données simples */
|
|
$twoDimensions = is_array($formatted[0]);
|
|
|
|
/* (3) On regarde s'il s'agit d'un Tableau de données en bonne et due forme */
|
|
if( $twoDimensions ){
|
|
$sameKeys = true; // VRAI si chaque entrée a les mêmes clés
|
|
$last_keys = null; // Clés de l'entrée précédente
|
|
|
|
foreach($formatted as $i=>$entry){
|
|
if( !is_null($last_keys) && count(array_diff(array_keys($entry), $last_keys)) > 0 ){ // Si différent du précédent, ducoup on est pas bon
|
|
$sameKeys = false;
|
|
break;
|
|
}
|
|
|
|
$last_keys = array_keys($entry);
|
|
}
|
|
|
|
// Si pas les mêmes clés, on a une erreur
|
|
if( !$sameKeys )
|
|
return $formatted;
|
|
}
|
|
|
|
|
|
|
|
/* [1] On retire les doublons à indices numériques
|
|
=========================================================*/
|
|
/* (1) Si 1 dimensions, on met en 2 pour traiter tout de la même manière */
|
|
if( !$twoDimensions )
|
|
$formatted = [$formatted];
|
|
|
|
|
|
/* (2) On récupère les noms des champs à partir des select (alias) */
|
|
// {1} On récupère les colonnes locales //
|
|
$existingColumns = [];
|
|
|
|
foreach($this->select as $field=>$data)
|
|
$existingColumns[$data[2]] = $this->schema['columns'][$field];
|
|
|
|
|
|
// {2} On ajoute les colonnes des jointures //
|
|
foreach($this->joined as $j)
|
|
foreach($j['object']->select as $field=>$data)
|
|
$existingColumns[$data[2]] = $j['object']->schema['columns'][$field];
|
|
|
|
|
|
// {3} On vérifie chaque clé, si c'est une colonne qui existe //
|
|
foreach($formatted as $i=>$entry)
|
|
|
|
// Pour chaque champ
|
|
foreach($entry as $index=>$value){
|
|
|
|
// Si la colonne existe on applique le type
|
|
if( isset($existingColumns[$index]) ){
|
|
|
|
if( $existingColumns[$index]['type'] == 'int' )
|
|
$formatted[$i][$index] = intval( $value );
|
|
elseif( $existingColumns[$index]['type'] == 'float' )
|
|
$formatted[$i][$index] = floatval( $value );
|
|
// String utf8 management
|
|
elseif( \mb_detect_encoding($value) === 'UTF-8' )
|
|
$formatted[$i][$index] = utf8_encode($value);
|
|
else
|
|
$formatted[$i][$index] = utf8_encode(utf8_decode($value));
|
|
|
|
// Si pas non plus une aggrégation et si indice numérique, on le retire
|
|
}else if( !preg_match('/^agg_.+/', $index) && is_numeric($index) )
|
|
unset($formatted[$i][$index]);
|
|
|
|
}
|
|
|
|
/* (3) On remet 1 dimension si 1 dimension à la base */
|
|
if( !$twoDimensions )
|
|
$formatted = $formatted[0];
|
|
|
|
/* [2] On retourne le résultat
|
|
=========================================================*/
|
|
return $formatted;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
?>
|