[AngularJs + PHP] Créer des filtres dans le back-end avec des requêtes SQL additives, pour trier des coureurs cyclistes par groupes.

php_1_angularjs

Introduction


Avec AngularJs, lorsque l’on liste plus de 2000 objets, cela devient difficile en terme de performances, de calculer les filtres avec le front end, ce qui est normal.

En d’autres termes, admettons que j’ai 5000 coureurs cyclistes à trier par un critère, il faut alors le faire dans le back end avec SQL, bien que le tri par colonne d’angularJS fonctionne encore. Ceci pour soulager grandement le front-end, et pour plus de confort utilisateur.

On doit filtrer dans le Back end, afin que le navigateur ne rame pas.

Pour cela, on ajoute des « morceaux de requêtes SQL » de type WHERE la plupart du temps, en fonction de ce que l’utilisateur va choisir dans les menus déroulants des filtres.

A noter que le système est identique, dans l’esprit, avec du Jquery Ajax, mais la syntaxe sera légèrement différente. Anoter aussi que ce système est relativement similaire dans l’esprit à des scripts en VBA que j’avais vu en professionel, ce qui indique qu’il est conforme dans l’esprit.

Il faut bien comprendre que les critères doivent être additifs, qu’il y ait 1 critère de choisi ou 15 de choisis. Autrement dit, je veux pouvoir utiliser 15 filtres en même temps si j’en ai envie … Avec cette technique, c’est possible et cela fonctionne !

POUR TESTER LA MINI APPLICATION CLIQUER ICI

Sommaire



  1. Création de la table SQL coureurs
  2. Mise en place basique d’une application AngularJS avec un tableau de coureurs cyclistes.
  3. Ajout de la vue des 2 filtres : Ville et Groupe.
  4. Ajout des requêtes AJAX (liste des cyclistes et liste pour les filtres)
  5. Elaboration du fichier back end en PHP MYSQL qui convertit et envoie le tout au format JSON.
  6. Conclusion.
  7. Annexe : Code pour Postgre.

Création de la table SQL coureurs

On crée la table suivante dans MYSQl :

CREATE TABLE `test`.`coureurs` (
`id` INT NOT NULL AUTO_INCREMENT,
`nom` VARCHAR(45) NULL,
`prenom` VARCHAR(45) NULL,
`ville` VARCHAR(45) NULL,
`groupe` VARCHAR(45) NULL,
PRIMARY KEY (`id`));

On la remplit avec des données bateau :

LOCK TABLES `coureurs` WRITE;
/*!40000 ALTER TABLE `coureurs` DISABLE KEYS */;
INSERT INTO `coureurs` VALUES (1,'Durand','Henri','Marseille','junior'),(2,'Levy','Patrick','Lille','junior'),(3,'José','Conzola','Marseilles','anciens'),(4,'Shivaa','roberto','Marseilles','junior');
/*!40000 ALTER TABLE `coureurs` ENABLE KEYS */;
UNLOCK TABLES;

Voilà, rien de plus à faire, si ce n’est remarquer la présence des groupe junior et anciens, et les villes. Ce sont ces critères qui vont nous servir à filtrer notre liste.

Mise en place basique d’une application AngularJS avec un tableau de coureurs cyclistes.

Comme d’hab on créée un arborescence de ce type sur notre serveur web :

liste1.jpg

Je n’ai pas inclus de module de routing de vues pour cet exercice.

  • Le répertoire backend contient les fichiers qui gèrent les opérations PHP-MYSQL
  • Le répertoire bower_component permet de télécharger et gérer les versions des modules optionnels (non utilisé dans cet exo) ou de télécharger bootstrap par exemple. Bower est un logiciel optionnel.)
  • Le répertoire css contient le fichier css de l’application.
  • Le répertoire js contient le code js et angularjs dans un seul fichier app.js pour cet exercice.

Voici la « vue  » principale : index.html, qui charge les libriairies, et affiche la table des coureurs cyclistes. En bas, on peut constater la présence de la vue filtres, qui peut être inclue à loisir dans d’autres vues. Normalement, la vue filtre devrait être dans un répetoire vues contenant toutes les vues.


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Neutre</title>
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
		<!-- CHARGEMENT DES LIBRAIRIES -->
		
		<!-- JQUERY ET BOOTSRAP -->
        <script src="bower_components/jquery/dist/jquery.min.js"></script>
        <script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
        <link rel="stylesheet" type="text/css" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
		<link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.min.css" type="text/css">
 
        <!-- ANGULARJS -->
        <script src='bower_components/angular/angular.min.js'></script>
		
		<!-- APPLICATION PERSO -->
		<script src="js/app.js"></script> <!-- Contient pour l'exemple le controleur et les appels aux modules. -->
		<link rel="stylesheet" type="text/css" href="css/style.css">
		
		<!-- FIN DE CHARGEMENT DES LIBRAIRIES -->
  </head>

<body ng-app="neutre" ng-controller="neutreCtrl">
	
	<!-- BARRE DE NAVIGATION -->
	<nav class="navbar navbar-inverse">
		<div class="container-fluid">
			<!-- for better mobile affichage-->
			<div class="navbar-header">
				<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
					<span class="sr-only">Toggle navigation</span>
				</button>
				<a class="navbar-brand" href="#">Neutre</a>
			</div>
			<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
			</div><!-- /.navbar-collapse -->
		</div><!-- /.container-fluid -->
	</nav>
	
	
	<!-- VUE UNIQUE  -->
	<div class="row ">
		<div class="col-lg-8 " >
			 <div class="panel panel panel-warning">
				
				<div class="panel-heading"> 
					<i class="fa fa-picture-o"></i> Liste
				</div>
				
				<div class="panel-body table-responsive" style="min-height:350px;" ng-show="!collapsed" >
					
					<table  class="table table-bordered table-hover  " border="1"  >
						<thead fix-head>
						  <tr> 
							<th>Nom</th>
							<th>Prénom</th>
							<th>Ville</th>
							<th>Groupe</th>
						  </tr>
						</thead>
			 
						<tbody >
						  <tr  ng-repeat="coureur in coureurs"   >
							 <td>{{coureur.nom}}</td>
							 <td >{{coureur.prenom}}</td>
							 <td >{{coureur.ville}}</td>
							 <td >{{coureur.groupe}}</td>
							 <td ><button class="btn btn-primary">Fiche</button></td>
						  </tr>
						</tbody>
					</table>
				
				</div>
				
				<div class="panel-footer ">
				</div>
				
			 </div>
		</div>
		
		<!-- VUE DES FILTRES -->
		<div class="col-lg-4 " ng-include="'filtres.html'" ></div>
	</div>
		

</body>
</html>

Ajout de la vue des 2 filtres : Ville et Groupe.

Les filtres sont des select HTML qui seront alimentés par des requêtes SQL AJAX $http :

Voici la vue des filtres filtres.html, remarquer $parent qui permet au controleur de la vue principale de récupérer les variables ng-model correctement :

         <div class="panel panel-warning">
 
            <div class="panel-heading">
                <span class="glyphicon glyphicon-filter" aria-hidden="true"></span>  Filtres 
            </div>
 
            <div  class="panel-body " > <!-- TOUS LES FILTRES  -->
			
				<select ng-model="$parent.filtres.ville" class="form-control">
					<option value="">Villes</option>
					<option ng-repeat="ville in $parent.villes | orderBy:'villes'" value="{{ville.nom}}">{{ville.nom}}</option>
				</select><br> 
				
            
				<select ng-model="$parent.filtres.groupe" class="form-control">
					<option value="">Groupe</option>
					<option ng-repeat="groupe in $parent.groupes | orderBy:'groupe'" value="{{groupe.nom}}">{{groupe.nom}}</option>
				</select><br>
			
			</div>
 
			<div class="panel-footer text-right">
			</div>
 
         </div>

Voilà, à ce stade on a ce look dans le navigateur :

rqtsadditives1.jpg

Ajout des requêtes AJAX (liste des cyclistes et liste pour les filtres)

Voici le fichier app.js qui contient mon controleur des vues précédentes, qui va lancer des requêtes AJAX  et récupérer des données:

  • Une requête ajax qui remplit le tableau des coureurs cyclistes ( Celle ci est relancée lors de chaque nouveau choix d’un critère par un utilisateur grâce à $watch() de angular. La requête Ajax contient alors en paramêtres les critères choisis par l’utilisateur, pour que le back end PHP crée à la volée la bonne requête SQL puis retourne les données au format JSON dans la fonction de rappel de la requête Ajax $http.
  • 2 Requêtes Ajax qui remplissent tout simplement les filtres (Listes de critères), Le Back end php retourne alors une requête SQL de type de DISTINCT pour éliminer les doublons.
angular.module('neutre', [])

.controller('neutreCtrl', function($scope,$http) {

	$scope.filtres 		= {}; 	// L'objet filtre contient en temps réel les choix de l'utilisateur, dans la vue filtres.html

	/* CHARGEMENT AJAX INITIAL DE LA LISTE DES COUREURS */
   $http.post('backend/backend.php?action=get_coureurs',$scope.filtres).success(function(data){
		$scope.coureurs = data;
	}).error(function(data){console.log(data);}).finally(function() {});

	/* CHARGEMENT DES LISTES DE FILTRES */

   /* CETTE REQUETE VA REMPLIR LE SELECT AVEC LES NOMS DES VILLES */
   $http.get('backend/backend.php?action=get_villes').success(function(data){
		$scope.villes = data;
	}).error(function(data){console.log(data);}).finally(function() {});

	/* CETTE REQUETE VA REMPLIR LE SELECT AVEC LES NOMS DES GROUPES */
   $http.get('backend/backend.php?action=get_groupes').success(function(data){
		$scope.groupes = data;
	}).error(function(data){console.log(data);}).finally(function() {});

	/* WATCHER DES FILTRES */
	/* ON RELANCE LE CHARGEMENT DU MODELE DE DONNEES DANS LE BACK END LORS DE CHAQUE CHANGEMENT DE FILTRES PAR L UTILISATEUR DANS LE NAVIGATEUR*/
	$scope.$watchCollection('filtres', function () {
		$http.post('backend/backend.php?action=get_coureurs',$scope.filtres).success(function(data){
			$scope.coureurs = data;
		}).error(function(data){ $scope.infos = data}).finally(function() {});

	});

/* FIN DU CONTROLEUR */
});

Remarque importante : $scope.filtres est un objet JSON qui stocke en temps réel les choix de l’utilisateur dans les select des filtres… C’est ça l’astuce parce que , lors de chaque choix, $scope.filtres est renvoyé au back end afin de recréer la requête SQL additive !!

Elaboration du fichier back end en PHP MYSQL qui convertit et envoie le tout au format JSON.

Le fichier backend.php permet de créer la requête SQL évolutive qui permet d’ajouter les critères de tri, indépendamment.

<?php

/* ---------------------------------------------BONJOUR BIENVENUE DANS LE BACK END EN PDO--------------------------------------------------  */
/* -------CE FICHIER PERMET A PHP D'EXECUTER DES REQUETES SQL ET DE LES ENVOYER AU FRONT END ANGULARJS OU JQUERY ENCODES EN JSON------------  */
/* --------------------------------------------IL PERMET DE FAIRE DU CRUD (CREE LIRE METTRE A JOUR SUPPRIMER -------------------------------  */


/* ---------------------------------------Paramêtres de connexion à la bdd et php--------------------------------  */
include('connexion_config.php');
header( 'content-type: text/html; charset=utf-8' );
error_reporting(E_ALL ^ E_NOTICE); // important pour ne pas afficher les notice PHP sans cela le script ne marche pas.
 

/* ------------------------Switch Case pour récupérer la l'action demandée par le controleur  AngularJs (appel $http ajax) ou par un appel ajax provenant de jquery-----------  */
 
switch($_GET['action'])  {
    case 'get_coureurs' 	:   get_coureurs();
    break;
	case 'get_villes' 		:   get_villes();
    break;
	case 'get_groupes' 		:   get_groupes();
    break;

}
 
 
/* ------------------------Fonctions qui récupère le résultat d'une requête SQL et encodent en JSON pour AngularJS ou Jquery qui le reçois dans la fonction de rappel des requêtes $HTPP ou les services -----------  */ 
 
function get_coureurs() {
	 
	  /* Récupération des données POST */
	 $data = json_decode(file_get_contents("php://input")); 

	try 
	{   
		$DB = connection();	
		
		/* Requête de base  */
		$sql = "SELECT * FROM coureurs WHERE 1=1";
		
		/* Requêtes Imbriquées à la requête de base avec les critères de filtres provenant du front end, on colle des morceaux de requêtes en fonction des critères*/
		if($data->ville){
			$sql .= " AND ville='".$data->ville."'";
		}
		if($data->groupe){
			$sql .= " AND groupe='".$data->groupe."'";
		}
		
		
		$data = $DB->query($sql);
		
		/* Convertit en JSON le résultat */    
		print_r(json_encode($data->fetchAll(PDO::FETCH_ASSOC)));
		
		/* ferme la connexion ? */
		$DB=null;
	}  
	catch(PDOException $e) 
	{
		file_put_contents('PDOErreurs.txt', $e->getMessage(), FILE_APPEND);  
	} 		
	
} 
 

/*  FONCTIONS QUI REMPLISSENT LES FILTRES */
function get_villes() {
	try 
	{   
		$DB = connection();	
		$data = $DB->query('SELECT distinct(ville) AS nom FROM coureurs');
		/* Convertit en JSON  */    
		print_r(json_encode($data->fetchAll(PDO::FETCH_ASSOC)));
		/* Ferme la connexion ? */
		$DB=null;
	}  
	catch(PDOException $e) 
	{
		file_put_contents('PDOErreurs.txt', $e->getMessage(), FILE_APPEND);  
	} 	
} 

function get_groupes() {
	try 
	{   
		$DB = connection();	
		$data = $DB->query('SELECT distinct(groupe) AS nom FROM coureurs');
		/* Convertit en JSON  */    
		print_r(json_encode($data->fetchAll(PDO::FETCH_ASSOC)));
		/* Ferme la connexion ? */
		$DB=null;
	}  
	catch(PDOException $e) 
	{
		file_put_contents('PDOErreurs.txt', $e->getMessage(), FILE_APPEND);  
	} 	
} 
 


?>

Bien sur ne pas oublier le script qui permet la connexion à mysql connexion_config.php:

<?php
function connection(){
    static $DBH;
	$DBH = new PDO('mysql:host=localhost;dbname=test', 'root', 'root');
          $DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
		  /* $DBH->query("SET CHARACTER SET utf8"); */
	return $DBH;
}
?>

Conclusion.

Avec cette architecture, il nous est possible d’exploiter les formidables ressources angularJs et ses modules additionnels, ainsi que le super back end PHP, d’une manière très structurée, et facile à dépanner, tout semble logique. Je n’ai pas mentionné qu’il faut de préférence créer une factory angularJs pour les requêtes ajax parce que c’est facultatif.

Finalement, on a une application basée sur le framework AngularJs, mais on peut filtrer les données dans le back end si on a des soucis de performance.

Si on ne veut pas utiliser AngularJs, il est également possible de faire ecela avec Jquery Ajax.

Annexe : exemple de code Postgre.

Avec Post gre, le code du back end est un peu différent :

Voici un exemple RANDOM de fonction qui retourne un resultat en tenant compte des requêtes additives :

function get_etudes() {
	// Récupère les critères des filtres provenant du front end.
	$data = json_decode(file_get_contents("php://input")); 
	
	/* Connexion à la base de données */
	$linkdb = connexion();
	
	/* Requête de base  */
	$select = "SELECT Etude.Identifiant AS Id_Etude,Enseignes.Nom AS EnseigneE,Id_CRM,Etude.Ville,Etude.Nom,Type_Etude.Nom AS TypeE,Etude.Date_Etude,Etude.CA,Etude.departement,Etude.region,Etude.secteur,Etude.surface,zone.population_zone,zone.foyer_zone FROM ((base.Etude LEFT JOIN base.Enseignes ON Etude.Enseigne=Enseignes.Identifiant) LEFT JOIN base.Type_Etude ON Etude.Type=Type_Etude.Identifiant)LEFT JOIN base.Zone ON Etude.Identifiant=Zone.Id_Etude WHERE Etude.Domaine='Alimentaire' " ;
	
	/* Requêtes Imbriquées à la requête de base avec les critères de filtres provenant du front end*/
	if($data->enseigne){
		$select .= " AND enseignes.nom='".$data->enseigne."'";
	}
	if($data->filtre_dpt){
		$select .= " AND etude.departement='".$data->filtre_dpt."'";
	}
	if($data->filtre_region){
		$select .= " AND etude.region='".$data->filtre_region."'";
	}
	if($data->filtre_secteur){
		$select .= " AND etude.secteur='".$data->filtre_secteur."'";
	}
	
	$select .= " ORDER BY Etude.Date_Etude DESC";

	$result = pg_query($linkdb, $select);
	
	if (!$result) {
	  echo "Une erreur est survenue.\n";
	  echo ($data->enseigne);
	  exit;
	}
	
	$arr = pg_fetch_all($result);
	print_r(json_encode($arr));
	pg_close($linkdb);
} 

et le fichier connexion_config pour se connecter :

<?php

function connexion(){
	session_start();

	$linkdb=pg_connect("host='192.168.0.234' port='5432' user='jojo' password='*!6SM*J9' dbname='coureurs'");

	if (!$linkdb) {
	  echo "Une erreur de connexion à la base de données s'est produite.\n";
	  exit;
	}
	
	return $linkdb;
}


?>


Publicités