Architecture d'un site web

Youssef Chahir
GREYC — Université de Caen
TODO:
  • Préciser les contextes d'utilisation divers qui orientent les choix de conception :
    • HTTP vs ligne de commande : on veut swapper le routeur, et la vue
    • HTML vs JSON : on veut swapper juste la vue
  • Un truc qui va pas : les appels directs à la vue depuis le routeur (pour éviter une méthode de contrôleur qui ne fait quasi rien), comme la page d'accueil ou la page d'erreur. Ça crée un couplage : on ne peut pas swapper vraiment tranquillement le routeur (il me semble). Ou alors c'est juste que le routeur ne devrait pas décider que le contrôleur n'a pas de travail à faire.
  • Un autre truc : le storage ne doit pas faire partie du modèle. C'est un aspect indépendant du reste. En particulier, les identifiants (en BD) ne devraient (idéalement) pas être dans la classe Animal.
  • Encore un truc : c'est absurde qu'on puisse appeler plusieurs fois makeXYZPage de la vue.
  • Dernier truc, mais très important : il faudrait utiliser un système un minimum robuste pour l'autoéchappement dans les vues (voir TODO cours crud).
  • Aussi, c'est pourri de devoir arrêter les poèmes et de passer aux couleurs en cours de route ! C'est impossible de s'y retrouver dans le code existant (avec plein de petits trucs qui ont changé). Il faut commencer direct sur les couleurs.
  • set_include_path et require_once.
  • utiliser plutôt PATH_INFO ?
  • structures répertoires (voir slides commentés à la fin)
  • diagramme de classes…
  • config ?

Objectifs :

  • séparation traitement/données
  • modèle MVC simplifié*
  • implémentation en PHP (introduction) :
    • arborescence des répertoires
    • squelette HTML des pages
    • fragments HTML à inclure si besoin
    • architecture des fichiers PHP gérés par un switch
    • passage de paramètres dans l'URL, tableau PHP $_GET
    • configuration d'un site : utilisation de constantes prédéfinies permettant de faire facilement évoluer le site

Création d'un site : phase d'identification des besoins

Identifier :
  • le graphisme déjà existant (à reprendre ou non)
  • les différentes parties du site
  • les utilisateurs du site et les fonctionnalités qui leur sont offertes
  • les données à manipuler et leur nature
Exemple : site d'entreprise avec
  • vitrine
  • espace clients
  • espace VIP
  • administration du site

Création d'un site : phase de conception

Définir un site : pour quoi, pour qui, comment ? (fonctionnalités, publics, moyens)
La phase d'identification sert à déterminer :
  • le stockage des données (arborescence des répertoires, base de données, XML…)
  • la charte graphique du site
  • l'ergonomie du site :
    • navigation
    • compréhension de l'interface
    • facilité d'utilisation des fonctionnalités
    • lisibilité du contenu

Création d'un site : phase de développement

La phase de conception est la base sur laquelle le développement va s'appuyer, sans oublier de penser à
  • l'évolution future du site
  • la maintenance du site (qui, quoi, comment ?)
Pour cela, on s'efforce de
  • réutiliser des composants existants (bibliothèques)
  • comme toujours, factoriser et modulariser au maximum
  • en particulier, séparer la logique de l'affichage

Séparation traitement des données & affichage

  • Séparer au maximum le PHP du HTML
  • PHP génère le contenu sans l'afficher
  • Fichier HTML ne contient que des instructions echo pour afficher le contenu

Exemple simplissime

Page PHP : hello.php

<?php
$titre 
"Une page PHP simple";
$info "Bonjour le monde !";

include(
"squelette.php");
?>

Squelette : squelette.php

<!DOCTYPE html> 
<html lang="fr">
<head>
  <title><?php echo $titre?></title>
</head>
<body>
<h1><?php echo $titre?></h1>
<p>L'information à délivrer est simple :</p>
<div>
  <?php echo $info?>
</div>
<p>Voilà.</p>
</body>
</html>
Résultat

Exemple à peine moins simple

Page PHP : hello.php

<?php
if (key_exists('toto'$_GET)) {
  
$titre "Une page sur toto";
  
$info "toto est une variable " .
    
"métasyntaxique utilisée " .
    
"dans les exemples de programmes.";
} else {
  
$titre "Une page PHP simple";
  
$info "Bonjour le monde !";
}
$menu =
  
'<a href="hello.php">Accueil</a> |' .
  
' <a href="hello.php?toto">toto</a>';

include(
"squelette.php");
?>

Squelette : squelette.php

<!DOCTYPE html> 
<html lang="fr">
<head>
  <title><?php echo $titre?></title>
</head>
<body>
<nav>Navigation : <?php echo $menu?></nav>
<h1><?php echo $titre?></h1>
<p>L'information à délivrer est simple :</p>
<div>
  <?php echo $info?>
</div>
<p>Voilà.</p>
</body>
</html>
Résultat

Avantages de cette architecture

  • Le script PHP gère toute la logique à un seul endroit, pas au milieu du code HTML
    • plus facile à maintenir
    • plus propre, notamment aucun risque d'erreur headers already sent si on manipule des en-têtes HTTP (cookies, redirections…)
  • Squelette : fichier avec du HTML « à trous » (template), facile à maintenir séparément (potentiellement par une personne différente)
  • Un problème de notre exemple ? C'est le script PHP qui décide comment afficher le menu

Gestion du menu

  • L'affichage du menu doit être géré par le squelette…
  • … mais on ne peut pas simplement tout passer de l'autre côté, car le nom des paramètres GET sera dupliqué
  • C'est le PHP qui gère l'analyse des URL, il doit donc garder la main sur leur forme
  • Solution : le script PHP donne un tableau avec les liens au squelette, qui les affiche comme il l'entend

Exemple avec gestion du menu

Page PHP : hello.php

<?php
if (key_exists('toto'$_GET)) {
  
$titre "Une page sur toto";
  
$info "toto est une variable " .
    
"métasyntaxique utilisée " .
    
"dans les exemples de programmes.";
} else {
  
$titre "Une page PHP simple";
  
$info "Bonjour le monde !";
}
$menu = array(
  
"Accueil" => "hello.php",
  
"toto" => "hello.php?toto",
);

include(
"squelette.php");
?>

Squelette : squelette.php

<!DOCTYPE html> 
<html lang="fr">
<head>
  <title><?php echo $titre?></title>
</head>
<body>
<nav>Navigation : <ul>
<?php
foreach ($menu as $texte => $lien) {
  echo 
"<li>";
  echo 
"<a href=\"$lien\">$texte</a>";
  echo 
"</li>\n";
?>
</ul></nav>
<h1><?php echo $titre?></h1>
<p>L'information à délivrer est simple :</p>
<div>
  <?php echo $info?>
</div>
<p>Voilà.</p>
</body>
</html>
Résultat

Modularité

  • Le script ne transmet que l'information nécessaire au squelette, il ne fait aucun choix d'affichage
  • Le squelette n'a pas besoin de connaître le nombre de liens à créer, ni la forme des liens
  • Rajouter une nouvelle page sur le même modèle ne nécessite que de rajouter les contenus dans le script PHP
  • C'est ce type de modularité que l'on recherche, a fortiori pour des applications complexes

Problématique

  • Application moins triviale, où l'internaute peut faire des actions autres qu'afficher telle ou telle page
  • On va préciser notre organisation modulaire
  • Il faut séparer :
    • le fonctionnement de l'application
    • les affichages qui sont produits
    • le pilotage de l'application
  • On va utiliser une architecture inspirée de MVC

Données de l'URL

  • Informations contenues dans le tableau $_GET
  • Attention, il faut tester si la clé codeAction est présente ou non dans le tableau :
    if (isset($_GET['codeAction']) {
       $codeAction = $_GET['codeAction'];
    } else {
       $codeAction = "home";
    }
    
  • Faire ensuite un switch sur $codeAction

Inclusion du squelette

  • Utilisation des fonctions de output buffer
  • ob_start();
      require_once($squelette);
      $html = ob_get_contents();
    ob_end_clean();
    
  • require_once envoie directement ce qu'il inclut, il faut donc capter ce contenu
  • Permet d'éventuels changements avant l'envoi au navigateur
  • Reste à faire echo $html;

Exemple Agenda :

Squelette : frag_deb.php

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Agenda</title>
  <link rel="stylesheet" href="style.css">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
</head>
<body>
  

Page PHP : index.php

<?php
include("frag_deb.php");
require_once(
"Lib.php");
$action key_exists('action'$_GET)? trim($_GET['action']): null;
switch (
$action) {
    case 
"calendrier"://calendrier vide
        
$zonePrincipale=Calendrier(date('Y'), date('m'));
        break;
    case 
"saisir"//Saisie  via le formulaire        
        
if (!isset($_POST["nom"])    && !isset($_POST["prenom"]) ) /* et autres champs*/
        
{
            include(
"formulairePersonne.html");
            
$zonePrincipale=$corps ;
        }
        else{
            
$nom key_exists('nom'$_POST)? trim($_POST['nom']): null;
            
$prenom key_exists('prenom'$_POST)? trim($_POST['prenom']): null;
            if (
$nom=="")     $erreur["nom"] ="il manque un nom"
            if (
$prenom==""$erreur["prenom"] ="il manque un prenom";     
            
$compteur_erreur=count($erreur);
            
            foreach (
$erreur as $cle=>$valeur){
                if (
$valeur==null$compteur_erreur=$compteur_erreur-1;
            }

            if (
$compteur_erreur == 0) {
                
$nom_encode htmlspecialchars($nom);
                
$prenom_encode htmlspecialchars($prenom);
                
$corps $nom_encode." ".$prenom_encode."<br>";
                
//Création d'objets
                //$personne = new Personne($nom,$prenom);
                
$zonePrincipale=$corps ;
            }
            else {
                include(
"formulairePersonne.html");
                
$zonePrincipale=$corps ;
            }
        }
        break;
    
 default:
   
$zonePrincipale="" ;
   break;
   
}
include(
"frag_fin.php");

?>

Squelette : frag_fin.php

<h1>Agenda</h1>
<hr>
<div class="Ycontainer">
  <div class="Ymain">
    <?php echo $zonePrincipale?>
  </div>
  <div class="Ysidebar">
    <p>
    <a href="index.php?action=saisir">Saisie d'une personne</a><br>
    <a href="index.php?action=calendrier">Calendrier vide</a><br>
    </p>
  </div>
</div>
<hr>
</body>
</html>
Résultat

Conclusion

  • Mise en place d'une architecture réutilisable
  • Séparation traitements-affichage
  • Architecture à compléter avec un vrai modèle d'application (classes PHP)

Structure des répertoires

  • Nécessité de rendre l'application lisible en regardant la hiérarchie des répertoires
  • Séparer :
    • le contenu textuel (informations ou bases de données)
    • les illustrations (images d'information)
    • les éléments de maquette (squelettes de page)
    • la présentation (CSS, images de fond…)
    • les programmes
    • la configuration

Structure des répertoires (2)

Exemple

|---contenu
|     |---fichier1.frg.html
|     `---fichier2.frg.html
|---images
|     |---image1.jpg
|     `---image2.jpg
|--config
|     `---config.php
|--squelettes
|     |---fichier.squel.php
|     `---`ragments
|     		`---entete.frg.php
|---style
|     |---screen.css
|     |---print.css
|     `---images
|     	   |---logo.png
|     	   `---icone1.png
`---index.php

Protéger ses répertoires

  • interdire l'accès aux répertoires de « service »
  • se fait dans la configuration du serveur
  • si pas accès, utilisation de fichiers Apache .htaccess



Architecture MVC**

  • Sera détaillée plus tard en L3
  • MVC : modèle-vue-contrôleur
  • De façon générale :
    • le modèle décrit le fonctionnement de l'application
    • la vue est l'affichage produit en fonction de l'état du modèle
    • le contrôleur capte les actions de l'utilisateur et les transmet au modèle
  • À partir de là : dizaines de variantes et de conceptions différentes
  • Objectif : modularité, séparation des composants
  • Règle générale : faire en sorte de pouvoir au maximum modifier chaque composant indépendamment
  • Principes indépendants de l'utilisation de la programmation objet !