comprendre les bases d'un framework en PHP




IV. Modèle MVC
IV-A. Requêtes HTTP
IV-B. Script de récupération des requêtes
IV-C. Partie contrôleur
IV-D. Conclusion


IV. Modèle MVC

Nous allons maintenant nous atteler à la définition d'un "cadre" permettant de respecter le modèle MVC. Pour ce faire, il nous faut séparer présentation (Pages HTML) et code métier (code PHP). Plusieurs façons de faire sont possible.
Nous allons procéder de la sorte : le code HTML sera dans un répertoire nommé modèle, le code PHP sera dans un répertoire nommé vue. Tous deux aurons le même nom de fichier à l'exception de l'extension : ".php" pour le PHP, ".hmtl" pour le HTML.

Afin d'ouvrir ces fichiers, il nous faudra du code PHP, ce sera, comme le nomme le modèle MVC, le contrôleur. Cette façon de procéder nous permet de n'avoir qu'une seule page à ouvrir (exemple : "index.php") et cette page s'occupera de choisir la vue et le modèle adaptés.

Mais comment allons nous procéder pour savoir, justement, la page adaptée? c'est là qu'entrent en jeux les fr requêtes HTTP. Fort heureusement, nous n'aurons pas a toutes les utiliser. Pour la navigation, nous allons nous contenter de requêtes "GET". Que va contenir cette requête? La réponse est : Qu'avons nous besoin qu'elle contienne, sachant que, pour l'instant, l'inconnue est le nom des fichiers du modèle et de la vue.
Nous allons donc utiliser la requête "GET", afin de savoir quelle page afficher. Voyons comment cette requête HTTP s'utilise :


IV-A. Requêtes HTTP

Résumons, nous allons utiliser la requête HTTP "GET" afin de naviguer de page en page.

Si vous voulez un cours complet sur ce sujet, cette page devrait vous plaire, si vous voulez en savoir plus, celle ci est faite pour vous. Pour les autres, voici une brève explication :
Vous avez probablement déjà utilisé google ceci est une url contenant, en bref :
le nom du protocole : "http"
l'adresse du serveur : "www.google.fr"
l'adresse du fichier sur le serveur : "/search"
les requêtes GET : "hl=fr&q=requete+http+GET&meta="

Les requêtes GET sont toujours introduites par un "?"
Elles constituent en réalité un passage de paramètre du client (vous) vers le serveur (www.google.fr). Il est évidemment possible de passer plusieurs paramètres au serveur, chacun étant séparé d'un "&".
Chaque paramètre est construit comme suit :

  • Nom du paramètre
  • Signe "="
  • Valeur du paramètre


Maintenant que nous savons utiliser une requête GET coté HTML apprenons a la lire coté serveur : PHP possède des variables superglobales. La variable $_GET En est un exemple. Cette variable est un tableau contenant tous les paramètres GET transmis au serveur. Pour les lire, il suffit de faire :
lecture d'un paramètre
$_GET['nom_du_parametre'];
Par exemple, pour charger le paramètre transmit par l'url suivante : :
lien avec passage de paramètre
http://www.google.fr/search?q=1-1
il suffit de faire, coté PHP, pour le charger dans une variable nommée $question :
lecture du parametre
$question = $_GET['q'];
Ainsi, $question prendra pour valeur "1-1". Un autre tableau superglobal permet de récupérer les paramètres GET : $_REQUEST sa particularité est qu'il récupère aussi les paramètres POST. Ceux ci sont transmis lorsque l'on utilise un formulaire :
code HTML d'un formulaire
<form action="test.html" method="post">
	Prénom : 
	<input type="text" name="prenom" value="Mickey" />
	<br />
	Nom : 
	<input type="text" name="nom" value="Mouse" />
	<br />
	<input type="submit" value="OK" />
</form>
Même si vous ne les connaissez pas, vous en avez déjà utilisés sans le savoir, en effet, tous les champs vous permettant d'écrire des données dans une page de votre navigateur est en réalité un formulaire. Si vous ne me croyez pas, copiez le code ci dessus dans un fichier texte vierge que vous renommerez "test.html", puis ouvrez le. Vous constaterez que bien que vous cliquiez sur le bouton "OK", aucune information n'est transmise dans la barre d'adresse. Si vous pensez que c'est du a une erreur de code, testez celui ci :
le meme code, mais avec la methode GET
<form action="test.html" method="get">
	Prénom : 
	<input type="text" name="prenom" value="Mickey" />
	<br />
	Nom : 
	<input type="text" name="nom" value="Mouse" />
	<br />
	<input type="submit" value="OK" />
</form>
Et vous constaterez que l'adresse est actualisée, et possède désormais les paires clef/valeur dans l'url, symptomatiques du passage de paramètre par la méthode GET. Généralement, la méthode GET est utilisée pour la navigation, et la méthode POST pour l'utilisation des formulaires. Cependant, afin de ne pas prendre de risques, nous allons utiliser la variable $_REQUEST qui permet de récupérer un paramètre qu'il soit passé en GET ou en POST.


IV-B. Script de récupération des requêtes

Reprenons google comme exemple, allez sur le site de ce moteur de recherche, et lancez plusieurs recherches puis analysez les paramètres affichés dans la barre d'adresse, vous constaterez que bien que la valeur change en fonction de vos demandes, c'est toujours la clef "q" qui contient cette valeur :

La raison est très simple, du coté PHP, il est bien plus pratique de connaître la variable a lire dans le tableau $_REQUEST. Faisons de même, et choisissons un nom de variable (Nous procéderons de cette façon pour toute nouveau type de variable a lire : fixer un nom).
page semble être un nom tout approprié, il est facilement compréhensible, non? Ainsi, une fois ce nom choisit, il suffit, coté PHP, de placer le code suivant :
tentative de lecture du paramètre 'page'
$page = $_REQUEST['page'];
Afin de ne pas avoir a écrire tout le long du code "$_REQUEST['page']", nous stockons cette valeur dans une variable "$page" bien plus rapide a écrire. Cependant, si vous testez ce code, vous réaliserez qu'il n'est pas fonctionnel. En effet, PHP ne créer l'élément 'page' du tableau $_REQUEST que si une requête contenant cette clef a été reçue. De façon plus explicite :
imaginons que vous ayez placé ce code dans un fichier nommé "test.php", et l'ayez placé dans un répertoire accessible a votre serveur web. ouvrez ce fichier (vous devriez avoir une URL ressemblant a :
http://localhost/test/test.php
Vous devriez avoir un beau message d'erreur. La solution est très simple : tester l'existence de l'élément page dans le tableau $_REQUEST. Pour ce faire, il existe la fonction isset elle retourne vrai lorsque l'élément demandé est définit, faux sinon.
Reprenons le code :
récupération d'un paramètre
if ( isset($_REQUEST['page']) ) 
	$page = $_REQUEST['page'];
Voila, vous avez enfin réussit a récupérer une requête HTTP sans risque de bug. Cependant, le travail n'est pas terminé, en effet, nous avons parlés précédemment des formulaires, sachez que valider un formulaire déclenche le chargement d'une nouvelle page définie dans le formulaire. Bien entendu, notre configuration "mono-page" nous permet de toujours faire recharger la page "index.php", cependant, la page a afficher (le paramètre GET ayant pour clef la chaîne "page") serait fastidieux, voir, dans certains cas, impossible a transmettre. Que faire? il faudrait, au cas où aucune page n'est spécifiée, savoir quelle est la dernière page qui a été demandée par le client... PHP est plein de ressources, les sessions sont là pour nous le prouver (pour avoir plus de ressources sur les sessions, renseignez vous par la, ou la). Brièvement, les sessions permettent de conserver des information propres au client lorsqu'il charge une nouvelle page, ainsi, a l'aide de ce mécanisme, il est possible de savoir quelle était la dernière page visitée.

Une page gérant les sessions doit toujours commencer par l'instruction session_start() en début de programme. Une fois cette fonction saisie, vous pourrez accéder aux variables de session a l'aide du tableau $_SESSION. Cependant, tout comme pour les autre variables de navigation, il se peut que le tableau soit vide, il convient donc de le tester. Par exemple, pour savoir si la variable $page a été stockée dans la session, il suffit de faire :
test de définition de la var
if ( isset($_SESSION['page']) ) {
	//serie de traitements
}
En prenant tous les paramètres énumérés précédemment en compte, il convient de récupérer la page a afficher a l'aide de $_REQUEST, si cette page n'est pas définie, il faut lire la page stockée en session, et, si elle n'existe pas...
...Et bien nous n'avons qu'a appeler une page par défaut (qui sera probablement la page d'accueil). Essayons de coder tout ça :
chois de la page en fonction des différentes possibilités
    //page a afficher :
    switch (true) {
	//1er cas : si on a une nouvelle page a afficher
	case ( isset($_REQUEST['page']) ) :
	    $page = $_REQUEST['page'];
		$_SESSION['page'] = $page;	
	    break;
	//sinon, on verifie si il y en a une stockée dans la session
	case ( isset( $_SESSION['page'] ) ) :
	    $page = $_SESSION['page'];
	    break;
	//sinon, on affiche la page par defaut
	default :
	    $page = "defaut";
	    break;

    }
    dbgStore("Page a afficher : '$page'", CONTROLEUR);
De même que des pages a afficher, il peut arriver qu'une action de l'utilisateur entraîne un traitement a déclencher (exemple : pour un forum, ajout d'un nouvel utilisateur). Bien entendu, nous pouvons intégrer ces traitement aux pages PHP de la vue (cf. : modèle MVC), cependant, il peut être pratique de permettre a plusieurs pages d'accéder au même traitement... Ainsi, une solution simple existe : en prenant exemple sur le paramètre "page", nous allons récupérer un paramètre "traitement". Le code a ajouter est :
code spécifique aux traitements
if (isset($_REQUEST['traitement'])  ) {

    dbgStore("Traitement a effectuer : '$traitement'", TRAITEMENT);
    require_once "traitements/"  . $traitement . ".php";
    
}
le code est bien plus simple que pour la page, car les traitements effectués n'ont pas besoin d'être conservés dans la sessions (si vous en doutez, essayez de trouver une bonne raison pour les conserver). Nous rencontrons pour la première fois la commande require_once qui, d'après le manuel PHP :
"se remplace elle-même par le fichier spécifié, un peu comme les commandes de préprocesseur C #include"
Ici, le fichier spécifié est obligatoirement dans le répertoire "traitements" et porte le nom de la valeur du paramètre transmis auquel on rajoute l'extension ".php". Cependant, un gros souci apparaît : n'importe qui peut déclencher un traitement! il suffit qu'il teste plusieurs valeurs pour le paramètre "traitement" transmis par l'url pour pouvoir déclencher des choses catastrophiques!
Il faut donc sécuriser l'accès aux traitements, et, pourquoi pas, aux pages. Nous allons donc créer deux fonction dédiées :
validDroitAcces($pageDemandee)
et
validDroitTraitement($traitementDemande)
dont voici le code :
nos deux fonction... un peu vides
function validDroitAcces($pageDemandee) {
    $pageResultante = $pageDemandee;
    
    return $pageResultante;
}


function validDroitTraitement($traitementDemande) {
    $traintementResultant = $traitementDemande;
    
    return $traintementResultant;
}
Comme vous pouvez le constater, ces fonction sont... un peu vides! La raison en est simple : elles devront être réécrites par l'utilisateur de notre framework, en effet, nous ne pouvons savoir quelle sera sa stratégie de sécurité :'(

a ceci, il peut être intéressant d'ajouter un petit "plus" : nous avons définit que nos traitements seront dans le répertoire générique "traitement", pourquoi ne pas rajouter un sous répertoire permettant de classer plus finement ces scripts, et par la même d'affiner la stratégie de sécurité :
version optimisée
function validDroitTraitement($typeTDemande, $traitementDemande) {
    $typeTResultant = $typeTDemande;
    $traintementResultant = $traitementDemande ;
    return array($typeTResultant, $traintementResultant);
}
Ainsi, notre réception des données se complexifie.
Nous allons en profiter pour la placer dans une fonction :
fonction recupRequest()
function recupRequest() {
    dbgStore("On est dans recupRequest", CONTROLEUR);
    //page a afficher :
    switch (true) {
	//1er cas : si on a une nouvelle page a afficher
	case ( isset($_REQUEST['page']) ) :
	    $page = validDroitAcces($_REQUEST['page']);
	    if ($page == null) {
		//si on est pas autorisé
		$page = "accesRefuse";
	    } else {
		//cas normal
		$_SESSION['page'] = $page;
		//session_register($page);	
	    }
	    break;
	//sinon, on verifie si il y en a une stockée dans la session
	case ( isset( $_SESSION['page'] ) ) :
	    $page = $_SESSION['page'];
	    break;
	//sinon, on affiche la page par defaut
	default :
	    $page = "defaut";
	    break;

    }
    dbgStore("Page a afficher : '$page'", CONTROLEUR);
    
    //traitement a effectuer : (execution des fonctionnalitées validées par l'utilisateur)
    if (isset($_REQUEST['traitement']) && isset($_REQUEST['typeTraitement']) ) {
	    list($typeTraitement, $traitement) =  validDroitTraitement($_REQUEST['typeTraitement'], $_REQUEST['traitement']);
        if ($traitement != null ) {	    
	        dbgStore("Traitement a effectuer : '$typeTraitement/$traitement'", TRAITEMENT);
	        require_once "traitements/" . $typeTraitement . "/" . $traitement . ".php";
	    }
    } else if(isset($_REQUEST['traitement']) ) {
        dbgStore("Un traitement a été demandé, mais aucun type de traitement n'a été defini", CONTROLEUR);
    } else if(isset($_REQUEST['typeTraitement']) ) {
        dbgStore("Un type de traitement a été recus, mais aucun  traitement n'a été defini", CONTROLEUR);
    }
    return $page;
}
Cette fonction utilise les deux autre fonctions que nous avons écrits afin de vérifier que l'utilisateur a bien les droits qu'il prétend, de plus la fonction php list fait son apparition. elle permet de récupérer le résultat d'un tableau dans plusieurs variables.


IV-C. Partie contrôleur

Avant d'écrire le contrôleur, nous allons procéder a un grand ménage. Si vous avez testés tous les codes qui ont étés énumérés tout au long de cet article, vous devez avoir une page PHP complètement incompréhensible!

Nous allons remédier a cela, en utilisant, encore une fois la commande include_once. Tout d'abord, nous allons séparer les différentes fonctionnalités du code en autant de fichiers, que nous allons placer dans un sous répertoire nommé "includes". De plus, nous allons suivre une logique de nommage des fichiers :

Nom du fichier fonctions
bdd.inc.php base de donnée
debuggeur.inc.php debug
droit_d_acces.inc.php droit d'accès
http_request.inc.php récupération des requêtes
template.inc.php moteur de template
(non implémenté pour le moment)
Nous allons donc nous retrouver avec les répertoires suivants :

nom du repertoire contenu
includes le code du framework
templates les fichiers HTML servant de modèle
(a définir par l'utilisateur)
traitements les traitement
(a définir par l'utilisateur)
vues les vues (fichiers PHP)
(a définir par l'utilisateur)
SQL les fichiers SQL utilisés par la base de donnée
(a définir par l'utilisateur)
Une fois tout le code exporté dans des fichiers a inclure, encore faut il l'inclure! C'est ce que nous allons faire :
inclusion du code
//	les scripts du TEMPLATE : Partie Modele

require_once "includes/template.inc.php";


//	Partie Debuggeur

require_once "includes/debuggeur.inc.php";



//	Partie BDD

require_once "includes/bdd.inc.php";


//	Partie Controleur

require_once "includes/droit_d_acces.inc.php";

require_once "includes/http_request.inc.php";
Ensuite, il ne nous reste plus qu'a écrire notre contrôleur. Vous allez voir, le plus dur est déjà fait :
le controleur
//initialisation des info de debug
dbgInit(true);

//recuperation des variables de navigation dont le fichier a afficher
$page = recupRequest();



///// affichage de la nouvelle page

//chargement du template (appel du modele)
$template =& chargeTemplate("templates\\" . $page . ".html");
if ($template == null) {
    //si page = null : le fichier de template demandé n'existe pas, on bloque la navigation
    echo "Erreure de navigation, le fichier demandé n'existe pas!";    
} else {
    //chargement de la partie dynamique (appel de la vue)
    require_once "vues\\" . $page . ".php";
	
    //affichage du resultat
    affiche($template);

    //affichage des messages de debug
    dbgAffiche(TOUS);
}

//a ce niveau, tous les affichages ont étés fait, on finit donc le programme.
Vous vous demandez sûrement pourquoi ce code n'est pas placé dans une fonction? Tout simplement car, lorsqu'on effectue un "include" le code inclut a la même portée que le code qui l'a appelé, cette partie ayant une portée globale, cela permet au code inclut d'en bénéficier, de même, si le code inclut effectue lui même des include, le chemin de fichier sera constant (celui de notre contrôleur, et non celui de localisation du script).

Reste une grande énigme : que peut bien signifier la fonction chargeTemplate()? Rendez vous au prochain et dernier chapitre afin de le savoir.


IV-D. Conclusion

Ce chapitre se termine très rapidement, en effet, respecter l'architecture MVC n'est pas bien compliqué, mais, surtout est fortement dépendant du chapitre suivant, du moins pour notre cas : en effet, nous allons utiliser un moteur de template.

Comme toujours, pour finir, vous pouvez retrouver le code complet de notre contrôleur ci-dessous. Remarquez que les define se retrouvent en début de programme, et non dans un fichier inclut, ceci afin de faciliter la création de nouveau types de messages pour le deboggueur.
<?php

// on gere les sessions :
session_start();

//definition des constantes :
//pour le debuggeur :
define("TOUS", 0);
define("INDEFINI", 1);//valeur par defaut, si le parametre est oublié, c'est ce niveau qui_ sera affiché
define("FASTBDD", 4);//les requetes rapide : dbInsert...
define( "REQUETE", 5);//tout ce quui est BDD de base
define( "CONTROLEUR", 6);//le controleur
define( "TRAITEMENT", 7);//pour les vues
define( "TEMPLATE", 8);// pour le moteur de template
define( "TEMPLATEDYN", 9);// aussi pour le moteur de template, mais specifique à la fusion

//	les scripts du TEMPLATE : Partie Modele

require_once "includes/template.inc.php";


//	Partie Debuggeur

require_once "includes/debuggeur.inc.php";



//	Partie BDD

require_once "includes/bdd.inc.php";


//	Partie Controleur

require_once "includes/droit_d_acces.inc.php";

require_once "includes/http_request.inc.php";



//on ne peut pas  mettre le reste dans une fonction car la partie 
//dynamique a besoin d'avoir une portée de ses variables globale.
//le code qui suit est donc le point d'entrée du programme :


//initialisation des info de debug
dbgInit(true);

//recuperation des variables de navigation dont le fichier a afficher
$page = recupRequest();



///// affichage de la nouvelle page

//chargement du template (appel du modele)
$template =& chargeTemplate("templates\\" . $page . ".html");
if ($template == null) {
    //si page = null : le fichier de template demandé n'existe pas, on bloque la navigation
    echo "Erreure de navigation, le fichier demandé n'existe pas!";    
} else {
    //chargement de la partie dynamique (appel de la vue)
    require_once "vues\\" . $page . ".php";
	
    //affichage du resultat
    affiche($template);

    //affichage des messages de debug
    dbgAffiche(TOUS);
}

//a ce niveau, tous les affichages ont etes fait, on finit donc le programme.



?>

CVweb de l'auteurprecedent    sommaire    suivant   

 

Les sources présentés sur cette pages sont libre de droits, et vous pouvez les utiliser à votre convenance. Par contre cette page de présentation de ces sources constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright ©2006 . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérets. .

Cet article devait etre a la base publié sur www.developpez.com