ORM: Refactor complet gestion du FETCH avec possibilité de select parmi toutes les jointures + de faire des jointures si la cible est la même, si la jointure est la cible et inversement + gestion des jointures + gestion des conditions des jointures dans la clause FROM

This commit is contained in:
xdrm-brackets 2016-07-24 17:42:54 +02:00
parent 4d09388155
commit 2a6b8dd935
4 changed files with 271 additions and 114 deletions

View File

@ -270,33 +270,75 @@
/* JOINT UNE SECONDE TABLE () /* JOINT UNE SECONDE TABLE ()
* *
* @field<String> Nom d'une colonne * @localField<String> Nom d'une colonne locale
* @rows<Rows> Rows d'une autre table * @rows<Rows> Rows d'une autre table
* *
* @return this<Rows> Retourne l'object courant * @return this<Rows> Retourne l'object courant
* *
*/ */
public function join($field, $rows){ public function join($localField, $rows){
/* [0] Vérification des paramètres /* [0] Vérification / Formattage des paramètres
=========================================================*/ =========================================================*/
if( !is_string($field) || !($rows instanceof Rows) ) /* (1) Si le champ n'est pas au bon format */
if( !is_string($localField) )
return $this; 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] On vérifie que la clé étrangère est correcte
=========================================================*/ =========================================================*/
/* (1) On vérifie que la colonne existe et qu'elle est étrangère*/ /* (1) Si la colonne n'existe pas et qu'elle n'est pas primaire, on ne fait rien */
if( !isset($this->schema['columns'][$field]['references']) ) if( !isset($localFieldData['references']) && !$localFieldData['primary'] )
return $this; return $this;
/* (2) On vérifie que la table de @rows est correcte */ /* (2) On vérifie que la colonne a une référence vers la table de @rows */
if( $this->schema['columns'][$field]['references'][0] != $rows->schema['table'] ) $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; return $this;
/* [2] On enregistre la référence /* [2] On enregistre la référence
=========================================================*/ =========================================================*/
$this->joined[$field] = $rows; $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 /* [3] On retourne l'object courant
@ -416,7 +458,7 @@
$requestS .= $this->schema['table'].'.'.$localField." in (\n\t"; $requestS .= $this->schema['table'].'.'.$localField." in (\n\t";
// {2} On récupère la requête // // {2} On récupère la requête //
$RowsFetched = $rows->fetch(false); $RowsFetched = $rows[0]->fetch(false);
// {3} On ajoute la requête // // {3} On ajoute la requête //
$requestS .= implode("\n\t", explode("\n", $RowsFetched['request'])); $requestS .= implode("\n\t", explode("\n", $RowsFetched['request']));
@ -625,7 +667,7 @@
$requestS .= $this->schema['table'].'.'.$localField." in (\n\t"; $requestS .= $this->schema['table'].'.'.$localField." in (\n\t";
// {2} On récupère la requête // // {2} On récupère la requête //
$RowsFetched = $rows->fetch(false); $RowsFetched = $rows[0]->fetch(false);
// {3} On ajoute la requête // // {3} On ajoute la requête //
$requestS .= implode("\n\t", explode("\n", $RowsFetched['request'])); $requestS .= implode("\n\t", explode("\n", $RowsFetched['request']));
@ -676,6 +718,16 @@
* *
*/ */
public function fetch($execute=true){ public function fetch($execute=true){
/* [0] On initialise
=========================================================*/
/* (1) On initialise la requête */
$requestS = [];
/* (2) On initialise le conteneur des variables "bindés" */
$binded = [];
/* [1] On rédige la clause SELECT /* [1] On rédige la clause SELECT
=========================================================*/ =========================================================*/
/* (1) On formatte les données */ /* (1) On formatte les données */
@ -686,81 +738,95 @@
/* (3) On ajoute les champs des jointures */ /* (3) On ajoute les champs des jointures */
foreach($this->joined as $rows) foreach($this->joined as $rows)
$selectTables[$rows->schema['table']] = $rows->select; $selectTables[$rows['object']->schema['table']] = $rows['object']->select;
/* (4) On génère la clause SELECT */ /* (4) On génère la clause SELECT */
$requestS = SQLBuilder::SELECT($selectTables)."\n"; $requestS['SELECT'] = SQLBuilder::SELECT($selectTables);
/* [2] On rédige la clause FROM /* [2] On rédige la clause FROM
========================================================*/ ========================================================*/
/* (0) On initialise la clause */
$requestS['FROM'] = [];
/* (1) Table locale */ /* (1) Table locale */
$requestS .= SQLBuilder::FROM( array_keys($selectTables) )."\n"; $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($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 '*' pour la clause SELECT //
$joinedFetched['request']['SELECT'] = [ $data['object']->schema['table'].'.*' ];
// {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['FROM'][] = "($joinedRequest) as ".$data['object']->schema['table'];
// {6} On ajoute les variables à la requête courante //
$binded = array_merge($binded, $joinedFetched['binded']);
}
/* [5] On rédige la clause WHERE/AND /* [5] On rédige la clause WHERE/AND
=========================================================*/ =========================================================*/
/* (0) On initialise le conteneur des variables "bindés" */
$binded = [];
/* (1) On met les conditions locales */ /* (1) On met les conditions locales */
$c = 0; $c = 0;
$requestS['WHERE'] = [];
foreach($this->where as $field=>$conditions) foreach($this->where as $field=>$conditions)
foreach($conditions as $cdt=>$value){ foreach($conditions as $cdt=>$value){
if( $value[1] === self::COND_IN ) // Si condition IN if( $value[1] === self::COND_IN ) // Si condition IN
$requestS .= SQLBuilder::IN([$this->schema['table'], $field], $value[0], $c, $binded)."\n"; $requestS['WHERE'][$c] = SQLBuilder::IN([$this->schema['table'], $field], $value[0], $c, $binded);
else // Sinon else // Sinon
$requestS .= SQLBuilder::WHERE([$this->schema['table'], $field], $value, $c, $binded)."\n"; $requestS['WHERE'][$c] = SQLBuilder::WHERE([$this->schema['table'], $field], $value, $c, $binded);
$c++; $c++;
} }
/* (2) On ajoute les jointures */ /* (2) On ajoute les jointures */
foreach($this->joined as $localField=>$rows){ foreach($this->joined as $localField=>$data){
if( $c == 0 ) $requestS .= 'WHERE '.$this->schema['table'].'.'.$localField.' = '.$this->schema['columns'][$localField]['references'][0].'.'.$this->schema['columns'][$localField]['references'][1]."\n"; $requestS['WHERE'][$c] = $this->schema['table'].".$localField = ".$data['object']->schema['table'].".".$data['field'];
else $requestS .= 'AND '.$this->schema['table'].'.'.$localField.' = '.$this->schema['columns'][$localField]['references'][0].'.'.$this->schema['columns'][$localField]['references'][1]."\n";
$c++; $c++;
} }
/* (3) On ajoute les conditions des jointures */
foreach($this->joined as $rows)
foreach($rows->where as $field=>$conditions)
foreach($conditions as $cdt=>$value){
if( $value[1] === self::COND_IN ) // Si condition IN
$requestS .= SQLBuilder::IN([$rows->schema['table'], $field], $value[0], $c, $binded)."\n";
else // Sinon
$requestS .= SQLBuilder::WHERE([$rows->schema['table'], $field], $value, $c, $binded)."\n";
$c++;
}
/* [6] Clause LIMIT /* [6] Clause LIMIT
=========================================================*/ =========================================================*/
if( $this->unique ) $requestS['LIMIT'] = ($this->unique) ? SQLBuilder::LIMIT(1) : SQLBuilder::LIMIT([]);
$requestS .= "LIMIT 1";
/* [7] On exécute la requête /* [7] On compose/prépare la requête
=========================================================*/ =========================================================*/
/* (1) Si on veut pas exécuter on renvoie la requête + bindedParams */ /* (1) Si on veut pas exécuter on renvoie la requête + bindedParams */
if( !$execute ) if( !$execute )
return [ 'request' => $requestS, 'binded' => $binded]; return [ 'request' => $requestS, 'binded' => $binded];
/* (2) On prépare la requête */ /* (2) On compose la requête */
$request = Database::getPDO()->prepare($requestS.';'); $requestString = SQLBuilder::BUILD($requestS).';';
// var_dump($requestString);
// var_dump($binded);
// exit();
/* (3) On prépare la requête */
$request = Database::getPDO()->prepare($requestString);
/* [8] On exécute la requête et retourne le résultat /* [8] On exécute la requête et retourne le résultat
=========================================================*/ =========================================================*/
/* (1) On exécute la requête */ /* (1) On exécute la requête */
$request->execute($binded); $request->execute($binded);
/* (2) Si unique */ /* (2) Si unique */
if( $this->unique ) if( $this->unique )
return $this->format( $request->fetch() ); return $this->format( $request->fetch() );
@ -826,8 +892,8 @@
$existingColumns = $this->schema['columns']; $existingColumns = $this->schema['columns'];
// {2} On ajoute les colonnes des jointures // // {2} On ajoute les colonnes des jointures //
foreach($this->joined as $rows) foreach($this->joined as $j)
$existingColumns = array_merge( $existingColumns, $rows->schema['columns'] ); $existingColumns = array_merge( $existingColumns, $j['object']->schema['columns'] );
// {3} On vérifie chaque clé, si c'est une colonne qui existe // // {3} On vérifie chaque clé, si c'est une colonne qui existe //
foreach($formatted as $i=>$entry) foreach($formatted as $i=>$entry)

View File

@ -9,26 +9,24 @@
class SQLBuilder{ class SQLBuilder{
/* CONSTRUIT LA REQUETE TEXTUELLE "SELECT" AVEC UNE LISTE DE CHAMPS /* CONSTRUIT LA REQUETE FORMATTEE "SELECT" AVEC UNE LISTE DE CHAMPS
* *
* @tables<Array> Liste de tables contenant des champs * @sqlFields<Array> Liste de champ/tables contenant des champs
* *
* @return sql<String> Renvoie la clause remplie * @return sql<Array> Renvoie un tableau formatté
* *
*/ */
public static function SELECT($tables){ public static function SELECT($sqlFields){
/* [0] Initialisation /* [0] Initialisation
=========================================================*/ =========================================================*/
$sql = 'SELECT '; $sql = [];
/* [1] On construit la requête /* [1] On construit la requête
=========================================================*/ =========================================================*/
$c = 0; $c = 0;
foreach($tables as $table=>$fields) foreach($sqlFields as $table=>$fields)
foreach($fields as $field){ foreach($fields as $field){
if( $c > 0 ) $sql .= ', '; $sql[$c] = $table.'.'.$field;
$sql .= $table.'.'.$field;
$c++; $c++;
} }
@ -41,30 +39,15 @@
/* CONSTRUIT LA REQUETE TEXTUELLE "FROM" AVEC UNE LISTE DE TABLES /* CONSTRUIT LA REQUETE FORMATTEE "FROM" AVEC UNE LISTE DE TABLES
* *
* @tables<Array> Liste de tables * @tables<Array> Liste de tables OU SQL PUR
* *
* @return sql<String> Renvoie la clause remplie * @return sql<Array> Renvoie un tableau formatté
* *
*/ */
public static function FROM($tables){ public static function FROM($tables){
/* [0] Initialisation return $tables;
=========================================================*/
$sql = 'FROM ';
/* [1] On construit la requête
=========================================================*/
$c = 0;
foreach($tables as $table){
if( $c > 0 ) $sql .= ', ';
$sql .= $table;
$c++;
}
return $sql;
} }
@ -72,21 +55,21 @@
/* CONSTRUIT LA REQUETE TEXTUELLE "UPDATE" AVEC LA TABLE EN QUESTION /* CONSTRUIT LA REQUETE FORMATTEE "UPDATE" AVEC LA TABLE EN QUESTION
* *
* @table<String> Table en question * @table<String> Table en question
* *
* @return sql<String> Renvoie la clause remplie * @return sql<Array> Renvoie un tableau formatté
* *
*/ */
public static function UPDATE($table){ public static function UPDATE($table){
/* [0] Initialisation /* [0] Initialisation
=========================================================*/ =========================================================*/
$sql = 'UPDATE '; $sql = [];
/* [1] On construit la requête /* [1] On construit la requête
=========================================================*/ =========================================================*/
$sql .= $table; $sql[] = $table;
return $sql; return $sql;
} }
@ -103,13 +86,13 @@
* @offset<int> Permet de rendre la condition unique (nommage des variables) * @offset<int> Permet de rendre la condition unique (nommage des variables)
* @binded<Arary> Tableau associatif contenant les variables "bindés" -> ajout des champs * @binded<Arary> Tableau associatif contenant les variables "bindés" -> ajout des champs
* *
* @return sql<String> Renvoie la clause remplie * @return sql<String> Renvoie le textuel formatté
* *
*/ */
public static function IN($field, $array, $offset=0, &$binded){ public static function IN($field, $array, $offset=0, &$binded){
/* [0] Initialisation /* [0] Initialisation
=========================================================*/ =========================================================*/
$sql = ( $offset == 0 ) ? 'WHERE ' : 'AND '; $sql = '';
/* [1] On construit la requête /* [1] On construit la requête
=========================================================*/ =========================================================*/
@ -142,13 +125,13 @@
* @offset<int> Permet de rendre la condition unique (nommage des variables) * @offset<int> Permet de rendre la condition unique (nommage des variables)
* @binded<Arary> Tableau associatif contenant les variables "bindés" -> ajout des champs * @binded<Arary> Tableau associatif contenant les variables "bindés" -> ajout des champs
* *
* @return sql<String> Renvoie la clause remplie * @return sql<String> Renvoie le textuel formatté
* *
*/ */
public static function WHERE($field, $value, $offset=0, &$binded){ public static function WHERE($field, $value, $offset=0, &$binded){
/* [0] Initialisation /* [0] Initialisation
=========================================================*/ =========================================================*/
$sql = ( $offset == 0 ) ? 'WHERE ' : 'AND '; $sql = '';
/* [1] On construit la requête /* [1] On construit la requête
@ -172,18 +155,18 @@
/* CONSTRUIT LA REQUETE TEXTUELLE "SET" AVEC UNE LISTE DE TABLES /* CONSTRUIT LA REQUETE FORMATTEE "SET" AVEC UNE LISTE DE TABLES
* *
* @values<Array> Tableau de la forme [ field=>value, field2=>value2 ] * @values<Array> Tableau de la forme [ field=>value, field2=>value2 ]
* @binded<Arary> Tableau associatif contenant les variables "bindés" -> ajout des champs * @binded<Arary> Tableau associatif contenant les variables "bindés" -> ajout des champs
* *
* @return sql<String> Renvoie la clause remplie * @return sql<Array> Renvoie un tableau formatté
* *
*/ */
public static function SET($values, &$binded){ public static function SET($values, &$binded){
/* [0] Initialisation /* [0] Initialisation
=========================================================*/ =========================================================*/
$sql = 'SET '; $sql = [];
/* [1] On construit la requête /* [1] On construit la requête
@ -191,11 +174,12 @@
$c = 0; $c = 0;
foreach($values as $field=>$value){ foreach($values as $field=>$value){
/* (1) Champ */ /* (1) Champ */
if( $c > 0 ) $sql .= "\n, "; $sql[$c] = ($c>0) ? "\n, " : '';
$sql .= $field.' = ';
$sql[$c] .= $field.' = ';
/* (2) Variable */ /* (2) Variable */
$sql .= ':update_'.$field; $sql[$c] .= ':update_'.$field;
$binded[':update_'.$field] = $value; $binded[':update_'.$field] = $value;
@ -207,6 +191,97 @@
/* CONSTRUIT LA REQUETE FORMATTEE "LIMIT" AVEC UN NOMBRE D'ENTREES
*
* @count<int> Nombre limite
*
* @return sql<Array> Renvoie un sql formatté
*
*/
public static function LIMIT($count=null){
/* [0] Initialisation
=========================================================*/
$sql = [];
/* [1] On construit la requête
=========================================================*/
if( intval($count) == $count )
$sql = intval($count);
return $sql;
}
/* CONSTRUIT LA REQUETE A PARTIR D'UNE REQUETTE FORMATTEE
*
* @request<Arary> Requête formattée
*
* @return sql<String> Requête formattée en SQL
*
*/
public static function BUILD($request){
/* [1] Clause SELECT
=========================================================*/
$sql = 'SELECT ';
$c = 0;
foreach($request['SELECT'] as $field){
$sql .= ($c==0) ? $field : ", $field";
$c++;
}
$sql .= "\n";
/* [2] Clause FROM
=========================================================*/
$sql .= 'FROM ';
$c = 0;
foreach($request['FROM'] as $field){
$sql .= ($c==0) ? $field : ", $field";
$c++;
}
$sql .= "\n";
/* [3] Clause WHERE
=========================================================*/
$c = 0;
foreach($request['WHERE'] as $field){
$sql .= ($c==0) ? "WHERE $field\n" : "AND $field\n";
$c++;
}
$sql .= ($c==0) ? '' : "\n";
/* [4] Clause LIMIT
=========================================================*/
if( is_numeric($request['LIMIT']) )
$sql .= 'LIMIT '.intval($request['LIMIT']);
/* [5] On retourne le résultat
=========================================================*/
return $sql;
}
} }

View File

@ -345,24 +345,25 @@
/* [1] FETCH /* [1] FETCH
=========================================================*/ =========================================================*/
// $warehouse = $warehouse =
// Table::get('warehouse') // Access to table 'warehouse' Table::get('warehouse') // Access to table 'warehouse'
// ->whereName(['stef-montauban', Rows::COND_EQUAL]); // condition : name = 'my-warehouse' ->whereName(['stef-montauban', Rows::COND_EQUAL]) // condition : name = 'my-warehouse'
// ->select('name');
//
// $myUser =
// Table::get('user') // Access to table 'user' $myUser =
// ->whereId([100, Rows::COND_INF]) // PRIMARY KEY (other condition on same field) Table::get('user') // Access to table 'user'
// ->whereId([[1,4,6,9], Rows::COND_IN]) // PRIMARY KEY (other condition on same field) ->whereId([100, Rows::COND_INF]) // PRIMARY KEY (other condition on same field)
// ->whereUsername(['%e%', Rows::COND_LIKE]) // Dynamic getter 'getByMySuperColumn' -> 'my_super_column' ->whereId([[92, 93], Rows::COND_IN]) // PRIMARY KEY (other condition on same field)
// ->select(['mail', 'username', 'firstname']) // Select clause ->whereUsername(['%', Rows::COND_LIKE]) // Dynamic getter 'getByMySuperColumn' -> 'my_super_column'
// ->select('id_user') // Select clause (added) ->select(['mail', 'username', 'firstname']) // Select clause
// ->join('id_warehouse', $warehouse) // joins warehouse (with name 'my-warehouse') to column 'id_warehouse' ->select('id_user') // Select clause (added)
// // ->unique() // unique result ->join('id_warehouse', $warehouse) // joins warehouse (with name 'my-warehouse') to column 'id_warehouse'
// // ->unique() // unique result
// // SELECT
// ->fetch(); // Result // SELECT
// var_dump($myUser); ->fetch(); // Result
var_dump($myUser);
/* [2] Modification /* [2] Modification
=========================================================*/ =========================================================*/
@ -436,17 +437,34 @@
// var_dump($myUser); // var_dump($myUser);
$module = Table::get('module')
->select('id_module');
$warehouse = Table::get('warehouse') // $warehouse = Table::get('warehouse')
->whereId(7); // ->whereName(['stef%', Rows::COND_LIKE]);
//
// $module_merge = Table::get('module_merge')
// ->join('id_warehouse', $warehouse);
//
// $module = Table::get('module')
// ->join('id_module', $module_merge);
//
// $chip = Table::get('chip')
// ->select('id_chip')
// ->join('id_module', $module);
//
// // $chip = Table::get('chip')
// // ->join('id_module', $module_merge)
//
// var_dump($chip->fetch());
$module_merge = Table::get('module_merge') // SELECT module.id_module
->join('id_module', $module) // FROM chip,
->join('id_warehouse', $warehouse); // (SELECT * FROM module) as module,
// (SELECT * FROM module_merge) as module_merge,
// (SELECT * FROM warehouse WHERE warehouse.name LIKE 'stef%') as warehouse
// WHERE chip.id_module = module.id_module
// AND module.id_module = module_merge.id_module
// AND module_merge.id_warehouse = warehouse.id_warehouse
var_dump($module_merge->fetch(false));
// $a = new ModuleRequest('authentificationDefault/warehouse', [ // $a = new ModuleRequest('authentificationDefault/warehouse', [

View File

@ -1,5 +1,3 @@
// TODO: Supprimer les retours vers la cible lors de 'création'/'modification'
// On referencie toutes les sections // On referencie toutes les sections
var section = { var section = {
view: { view: {