Architecture de type MVC avec PHP

Problème

Lorsqu’un projet augmente, le besoin de s’organiser et de permettre plus de réutilisabilité et de lisibilité demande une certaine méthode. MVC = Modèle Vue Controleur peut être une solution intéressante.

Nous allons commencer à nous familiariser avec les composants d’un Framework MVC et à voir l’utilité de recourir à de tels outils.

Une introduction générale à ce sujet se trouve ici

Du PHP pur aux templates PHP:

Considérons le code suivant en interrogeant la table CARNET vue précemment depuis PHP avec PDO:

<?php
  require("connect.php");
  $dsn="mysql:dbname=".BASE.";host=".SERVER;
  try{
  $connexion=new PDO($dsn,USER,PASSWD);
  }
  catch(PDOException $e){
  printf("Echec connexion : %s\n", $e->getMessage());
  exit();
  }
  $sql="SELECT * from CARNET";
  if(!$connexion->query($sql))
  echo "Pb pour acceder au CARNET";
  else
  {
      foreach ($connexion->query($sql) as $row){
          echo $row['NOM']<br/>\n";
      }
  }

On peut observer quelques défauts dans le code ci-dessus:

  • Réutilisabilté du code très réduite

  • Si on fabrique un formulaire avec les entrées du carnet, où doit-on mettre le code correspondant ?

Un template PHP:

On peut améliorer un peu les choses:

<?php
   require("connect.php");
   $dsn="mysql:dbname=".BASE.";host=".SERVER;
   try
   {
       $connexion=new PDO($dsn,USER,PASSWD);
   }
   catch(PDOException $e)
   {
       printf("Echec connexion : %s\n", $e->getMessage());
       exit();
   }
   $sql="SELECT * from CARNET";
   if(!$connexion->query($sql))
       echo "Pb pour acceder au CARNET";
   else
   {
       $amis=Array();
       foreach ($connexion->query($sql) as $row){
           $amis[]=$row;
       }
       require "templates/listeamis.php";
   }

Avec un template listeamis.php à placer dans templates/listeamis.php:

<!DOCTYPE html>
<html>
<head>
    <title>Liste de mes Amis</title>
</head>
<body>
    <h1>List of friends</h1>
    <ul>
        <?php foreach ($amis as $ami): ?>
        <li>
        <a href="/recherche?nom=<?php echo $ami['ID'] ?>">
        </a>
        </li>
        <?php endforeach; ?>
    </ul>
</body>
</html>

On commence ainsi à séparer la présentation du codage « métier ».

Isolons la logique applicative:

<?php
  //modele.php
  require("connect.php");

  function connect_db()
  {
      $dsn="mysql:dbname=".BASE.";host=".SERVER;
  try
  {
      $connexion=new PDO($dsn,USER,PASSWD);
  }
  catch(PDOException $e)
  {
  printf("Echec connexion : %s\n",
  $e->getMessage());
  exit();
  }
  return $connexion;
  }
  // Puis
  function get_all_friends()
  {
      $connexion=connect_db();
      $amis=Array();
      $sql="SELECT * from CARNET";
      foreach ($connexion->query($sql) as $row)
      {
          $amis[]=$row;
      }

      return $amis;
  }

On peut maintenant avoir un controleur très simple qui interroge le modèle puis passe les données au template pour affichage.

<?php
  //c-list.php
  require_once 'modele.php';

  $amis = get_all_friends();

  require 'templates/listamis.php';

Layout:

Il reste une partie non réutilisable dans le code à savoir le layout. Essayons de remédier à ça:

<!-- templates/baseLayout.php -->
<!DOCTYPE html>
<html>
<head>
    <title><?php echo $title ?></title>
</head>
<body>
    <?php echo $content ?>
</body>
</html>

Héritage de templates:

<?php
// templates/t-list.php
  $title = 'Liste des amis';
  ob_start();
?>
  <h1>List de mes amis</h1>
  <ul>
  <?php foreach ($amis as $ami): ?>
  <li>
      <a href="/recherche?nom=<?php echo $ami['nom'] ?>">
          <?php echo $ami['VILLE'] ?>
      </a>
  </li>
  <?php endforeach; ?>
  </ul>
<?php
  $content = ob_get_clean();
  include 'baseLayout.php'
?>

Observez l’utilisation de la bufferisation avec ob_start() et ob_get_clean(). Cette dernière fonction récupère le contenu bufferisé et nettoie ensuite le buffer.

Affichage des détails d’une personne

On va ajouter à notre modèle une fonction pour afficher les détails d’une personne:

<?php
  function get_friend_by_id($id)
  {
      $connexion=connect_bd();
      $sql="SELECT * from CARNET where ID=:id";
      $stmt=$connexion->prepare($sql);
      $stmt->bindParam(':id', $id, PDO::PARAM_INT);
      $stmt->execute();
      return $stmt->fetch();
  }

On peut maintenant créer un nouveau controleur c-details.php :

<?php
  //c-details.php
  require_once 'modele.php';
  $pers = get_friend_by_id($_GET['id']);
  require 'templates/t-details.php';
?>

Qui utilise le template:

<?php
  //templates/t-details.php
  $title = $pers['NOM'];
  ob_start();
?>
<h1>details sur
<?php echo $pers['PRENOM'].' '.$pers['NOM'] ?>
</h1>
<p>
<?php
  echo ' Ne le '.$pers['NAISSANCE'];
  echo '<br/>Ville:'.$pers['VILLE'];
  $content = ob_get_clean();
  include 'baseLayout.php'
?>

Vous pouvez tester en entrant l’URL de c-details.php avec un paramètre id. Le code est similaire à celui du premier template et nous pouvons réutiliser le template de base, mais il subsiste plusieurs problèmes:

  • Si le paramètre id n’est pas fourni, notre application va provoquer une erreur.

  • Nous n’avons pas de controleur principal.

Regroupons d’abord le code des 2 contrôleurs (c-list.php et c-details.php) dans un fichier unique controllers.php

<?php
// controllers.php
  function list_action()
  {
    $amis = get_all_friends();
    require 'templates/t-list.php';
  }

  function detail_action($id)
  {
   $pers = get_friend_by_id($id);
   require 'templates/t-detail.php';
  }
?>

Nous pouvons enfin proposer un controleur principal (Front Controller) index.php:

<?php
  // index.php
  // On charge les modeles et les controleurs
  require_once 'modele.php';
  require_once 'controllers.php';
  // gestion des routes
  $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
  if ('/index.php' == $uri)
    {
        list_action();
    }
  elseif ('/index.php/detail' == $uri && isset($_GET['id']))
    {
        detail_action($_GET['id']);
    }
    else
    {
        header('Status: 404 Not Found');
        echo '<html><body><h1>Page Not Found</h1></body></html>';
    }
  ?>

Nous avons maintenant une structure de ce type:

├── connect.php
├── connexion.php
├── controlleur.php
├── modele.php
├── recherche.php
└── templates
    ├── layout.php
    └── listeamis.php

On peut améliorer tout cela en intégrant dans un même Objet tout le modèle:

Les templates PHP présentés ne sont pas très simples à utiliser, aussi nous allons étudier un système de templating plus puissant: Twig.