logo

Types de blocs supplémentaires (EBT) – Nouvelle expérience de Layout Builder❗

Types de blocs supplémentaires (EBT) – types de blocs stylisés et personnalisables : diaporamas, onglets, cartes, accordéons et bien d’autres. Paramètres intégrés pour l’arrière-plan, la boîte DOM, les plugins JavaScript. Découvrez dès aujourd’hui le futur de la création de mises en page.

Démo des modules EBT Télécharger les modules EBT

❗Types de paragraphes supplémentaires (EPT) – Nouvelle expérience Paragraphes

Types de paragraphes supplémentaires (EPT) – ensemble de modules basé sur les paragraphes analogiques.

Démo des modules EPT Télécharger les modules EPT

Défilement

1.5. Connexion des classes pour travailler avec la base de données et les templates

05/07/2025, by Ivan

Nous avons créé la structure de notre framework, il est maintenant temps de penser au stockage des données : actualités, produits. Un objet pour travailler avec la base de données doit pouvoir :

  • Gérer la connexion à la base de données
  • Fournir une petite abstraction de la base de données
  • Mettre en cache les requêtes
  • Simplifier les opérations communes sur la base de données

Pour cela, nous allons créer l'objet Registry/objects/db.class.php :

<?php

/**
 * Gestion de la base de données
 * Fournit une abstraction légère de la base de données
 */
class database {

  /**
   * Permet plusieurs connexions à la base de données
   * rarement utilisé, mais parfois utile
   */
  private $connections = array();

  /**
   * Connexion active
   * setActiveConnection($id) permet de changer la connexion active
   */
  private $activeConnection = 0;

  /**
   * Requêtes exécutées et mises en cache
   */
  private $queryCache = array();

  /**
   * Données extraites et mises en cache
   */
  private $dataCache = array();

  /**
   * Dernière requête exécutée
   */
  private $last;

  /**
   * Constructeur
   */
  public function __construct()
  {

  }

  /**
   * Création d'une nouvelle connexion
   * @param string hostname de la base
   * @param string nom d'utilisateur
   * @param string mot de passe
   * @param string nom de la base utilisée
   * @return int identifiant de la nouvelle connexion
   */
  public function newConnection( $host, $user, $password, $database )
  {
    $this->connections[] = new mysqli( $host, $user, $password, $database );
    $connection_id = count( $this->connections )-1;
    if( mysqli_connect_errno() )
    {
      trigger_error('Erreur de connexion : '.$this->connections[$connection_id]->error, E_USER_ERROR);
    }

    return $connection_id;
  }

  /**
   * Fermer la connexion active
   */
  public function closeConnection()
  {
    $this->connections[$this->activeConnection]->close();
  }

  /**
   * Changer la connexion active
   * @param int nouvel id de connexion
   */
  public function setActiveConnection( int $new )
  {
    $this->activeConnection = $new;
  }

  /**
   * Mettre en cache une requête
   * @param string requête SQL
   * @return int identifiant de la requête en cache
   */
  public function cacheQuery( $queryStr )
  {
    if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) )
    {
      trigger_error('Erreur d’exécution et mise en cache de la requête : '.$this->connections[$this->activeConnection]->error, E_USER_ERROR);
      return -1;
    }
    else
    {
      $this->queryCache[] = $result;
      return count($this->queryCache)-1;
    }
  }

  /**
   * Obtenir le nombre de lignes dans le cache
   * @param int id cache requête
   * @return int nombre de lignes
   */
  public function numRowsFromCache( $cache_id )
  {
    return $this->queryCache[$cache_id]->num_rows;
  }

  /**
   * Obtenir les résultats d'une requête mise en cache
   * @param int id cache requête
   * @return array ligne de résultat
   */
  public function resultsFromCache( $cache_id )
  {
    return $this->queryCache[$cache_id]->fetch_array(MYSQLI_ASSOC);
  }

  /**
   * Mettre en cache des données
   * @param array données
   * @return int identifiant dans le cache données
   */
  public function cacheData( $data )
  {
    $this->dataCache[] = $data;
    return count( $this->dataCache )-1;
  }

  /**
   * Obtenir des données du cache
   * @param int id cache données
   * @return array données
   */
  public function dataFromCache( $cache_id )
  {
    return $this->dataCache[$cache_id];
  }

  /**
   * Supprimer des enregistrements dans une table
   * @param string table
   * @param string condition de suppression
   * @param int nombre de lignes à supprimer
   */
  public function deleteRecords( $table, $condition, $limit )
  {
    $limit = ( $limit == '' ) ? '' : ' LIMIT ' . $limit;
    $delete = "DELETE FROM {$table} WHERE {$condition} {$limit}";
    $this->executeQuery( $delete );
  }

  /**
   * Mettre à jour des enregistrements dans une table
   * @param string table
   * @param array changements champ => valeur
   * @param string condition
   * @return bool
   */
  public function updateRecords( $table, $changes, $condition )
  {
    $update = "UPDATE " . $table . " SET ";
    foreach( $changes as $field => $value )
    {
      $update .= "`" . $field . "`='{$value}',";
    }

    // supprimer la dernière virgule
    $update = substr($update, 0, -1);
    if( $condition != '' )
    {
      $update .= " WHERE " . $condition;
    }

    $this->executeQuery( $update );

    return true;
  }

  /**
   * Insérer des enregistrements dans une table
   * @param string table
   * @param array données champ => valeur
   * @return bool
   */
  public function insertRecords( $table, $data )
  {
    $fields  = "";
    $values = "";

    foreach ($data as $f => $v)
    {
      $fields  .= "`$f`,";
      $values .= ( is_numeric( $v ) && ( intval( $v ) == $v ) ) ? $v."," : "'$v',";
    }

    $fields = substr($fields, 0, -1);
    $values = substr($values, 0, -1);

    $insert = "INSERT INTO $table ({$fields}) VALUES({$values})";
    $this->executeQuery( $insert );
    return true;
  }

  /**
   * Exécuter une requête SQL
   * @param string requête
   */
  public function executeQuery( $queryStr )
  {
    if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) )
    {
      trigger_error('Erreur lors de l’exécution de la requête : '.$this->connections[$this->activeConnection]->error, E_USER_ERROR);
    }
    else
    {
      $this->last = $result;
    }
  }

  /**
   * Obtenir les lignes du dernier résultat (hors cache)
   * @return array
   */
  public function getRows()
  {
    return $this->last->fetch_array(MYSQLI_ASSOC);
  }

  /**
   * Obtenir le nombre de lignes affectées du dernier résultat
   * @return int
   */
  public function affectedRows()
  {
    return $this->connections[$this->activeConnection]->affected_rows;
  }

  /**
   * Assainir les données pour sécurité
   * @param string données à nettoyer
   * @return string données nettoyées
   */
  public function sanitizeData( $data )
  {
    return $this->connections[$this->activeConnection]->real_escape_string( $data );
  }

  /**
   * Destructeur, ferme toutes les connexions
   */
  public function __destruct()
  {
    foreach( $this->connections as $connection )
    {
      $connection->close();
    }
  }
}
?>

Avant de connecter la base de données, regardons ce que fait notre classe. Nous pourrons effectuer des opérations simples d’insertion, mise à jour, suppression via les méthodes :

// Insertion
$registry->getObject('db')->insertRecords( 'products', array('name'=>'Tasse' ) );
// Mise à jour
$registry->getObject('db')->updateRecords( 'products', array('name'=>'Tasse rouge' ), 'ID=2' );
// Suppression
$registry->getObject('db')->deleteRecords( 'products', "name='Tasse rouge'", 5 );

Notre classe supporte aussi la mise en cache.

Créons maintenant un autre objet pour gérer les templates dans Registry/objects/template.class.php :

<?php

if ( ! defined( 'FW' ) )
{
  echo 'Ce fichier ne peut être appelé que depuis index.php et non directement';
  exit();
}

/**
 * Classe de gestion des templates
 */
class template {

  private $page;

  public function __construct()
  {
    include( APP_PATH . '/Registry/objects/page.class.php');
    $this->page = new Page();
  }

  public function addTemplateBit( $tag, $bit )
  {
    if( strpos( $bit, 'Views/' ) === false )
    {
      $bit = 'Views/Templates/' . $bit;
    }
    $this->page->addTemplateBit( $tag, $bit );
  }

  private function replaceBits()
  {
    $bits = $this->page->getBits();
    foreach( $bits as $tag => $template )
    {
      $templateContent = file_get_contents( $template );
      $newContent = str_replace( '{' . $tag . '}', $templateContent, $this->page->getContent() );
      $this->page->setContent( $newContent );
    }
  }

  private function replaceTags()
  {
    $tags = $this->page->getTags();
    foreach( $tags as $tag => $data )
    {
      if( is_array( $data ) )
      {
        if( $data[0] == 'SQL' )
        {
          $this->replaceDBTags( $tag, $data[1] );
        }
        elseif( $data[0] == 'DATA' )
        {
          $this->replaceDataTags( $tag, $data[1] );
        }
      }
      else
      {
        $newContent = str_replace( '{' . $tag . '}', $data, $this->page->getContent() );
        $this->page->setContent( $newContent );
      }
    }
  }

  private function replaceDBTags( $tag, $cacheId )
  {
    $block = '';
    $blockOld = $this->page->getBlock( $tag );

    while ($tags = Registry::getObject('db')->resultsFromCache( $cacheId ) )
    {
      $blockNew = $blockOld;
      foreach ($tags as $ntag => $data)
      {
        $blockNew = str_replace("{" . $ntag . "}", $data, $blockNew);
      }
      $block .= $blockNew;
    }
    $pageContent = $this->page->getContent();
    $newContent = str_replace( '' . $blockOld . '', $block, $pageContent );
    $this->page->setContent( $newContent );
  }

  private function replaceDataTags( $tag, $cacheId )
  {
    $block = $this->page->getBlock( $tag );
    $blockOld = $block;
    while ($tags = Registry::getObject('db')->dataFromCache( $cacheId ) )
    {
      foreach ($tags as $tag => $data)
      {
        $blockNew = $blockOld;
        $blockNew = str_replace("{" . $tag . "}", $data, $blockNew);
      }
      $block .= $blockNew;
    }
    $pageContent = $this->page->getContent();
    $newContent = str_replace( $blockOld, $block, $pageContent );
    $this->page->setContent( $newContent );
  }

  public function getPage()
  {
    return $this->page;
  }

  public function buildFromTemplates()
  {
    $bits = func_get_args();
    $content = "";
    foreach( $bits as $bit )
    {
      if( strpos( $bit, 'skins/' ) === false )
      {
        $bit = 'Views/Templates/' . $bit;
      }
      if( file_exists( $bit ) == true )
      {
        $content .= file_get_contents( $bit );
      }
    }
    $this->page->setContent( $content );
  }

  public function dataToTags( $data, $prefix )
  {
    foreach( $data as $key => $content )
    {
      $this->page->addTag( $key.$prefix, $content);
    }
  }

  public function parseTitle()
  {
    $newContent = str_replace('', '<title>'. $this->page->getTitle(), $this->page->getContent() );
    $this->page->setContent( $newContent );
  }

  public function parseOutput()
  {
    $this->replaceBits();
    $this->replaceTags();
    $this->parseTitle();
  }

}
?>
</pre>

<p>Nous avons aussi défini que l’objet Page est appelé dans le templateur, donc nous devons définir ce dernier dans <code>Registry/objects/page.class.php</code> :</p>

<pre class="1;">
<?php

/**
 * Notre classe Page
 * Permet d’ajouter des fonctionnalités utiles
 * Par exemple : pages protégées par mot de passe, ajout de js/css, etc.
 */
class page {

  private $css = array();
  private $js = array();
  private $bodyTag = '';
  private $bodyTagInsert = '';

  private $authorised = true;
  private $password = '';

  private $title = '';
  private $tags = array();
  private $postParseTags = array();
  private $bits = array();
  private $content = "";

  function __construct() { }

  public function getTitle()
  {
    return $this->title;
  }

  public function setPassword( $password )
  {
    $this->password = $password;
  }

  public function setTitle( $title )
  {
    $this->title = $title;
  }

  public function setContent( $content )
  {
    $this->content = $content;
  }

  public function addTag( $key, $data )
  {
    $this->tags[$key] = $data;
  }

  public function getTags()
  {
    return $this->tags;
  }

  public function addPPTag( $key, $data )
  {
    $this->postParseTags[$key] = $data;
  }

  public function getPPTags()
  {
    return $this->postParseTags;
  }

  public function addTemplateBit( $tag, $bit )
  {
    $this->bits[ $tag ] = $bit;
  }

  public function getBits()
  {
    return $this->bits;
  }

  public function getBlock( $tag )
  {
    preg_match ('#<!-- START '. $tag . ' -->(.+?)<!-- END '. $tag . ' -->#si', $this->content, $tor);

    $tor = str_replace ('<!-- START '. $tag . ' -->', "", $tor[0]);
    $tor = str_replace ('<!-- END '  . $tag . ' -->', "", $tor);

    return $tor;
  }

  public function getContent()
  {
    return $this->content;
  }

}
?>
</pre>

<p>Maintenant que nous avons créé les classes pour la base de données et les templates, connectons-les.</p>

<p>Créons la méthode <code>storeCoreObjects()</code> dans <code>Registry/registry.class.php</code> :</p>

<pre class="1;">
public function storeCoreObjects()
{
  $this->storeObject('database', 'db' );
  $this->storeObject('template', 'template' );
}
</pre>

<p>Dans cette méthode, nous indiquerons les classes à connecter.</p>

<p>Créons aussi une table <code>users</code> avec trois champs : id, name, email. Je joindrai un fichier SQL sur Git pour l'exemple.</p>

<p>Créons un template <code>Views/Templates/main.tpl.php</code> pour afficher la page principale :</p>

<pre class="1;">
<html>
<head>
    <title> Powered by PCA Framework</title>
</head>
<body>
<h1>Nos membres</h1>
<p>Voici la liste de nos membres :</p>
<ul>
<!-- START members -->
<li>{name} {email}</li>
<!-- END members -->
</ul>
</body>
</html>
</pre>

<p>Nous avons défini une balise members et des tokens {name}, {email}. Nous verrons plus en détail le fonctionnement du templateur dans un autre article.</p>

<p>Enfin, modifions <code>index.php</code> pour connecter la base de données et le template :</p>

<pre class="1;">
<?php
/**
 * Framework
 * Point d'entrée de notre framework
 */

// démarrer la session
session_start();

error_reporting(E_ALL);

// constantes
define( "APP_PATH", dirname( __FILE__ ) ."/" );
define( "FW", true );

/**
 * Fonction d'autoload magique
 * Charge les controllers à la demande
 * @param string nom de la classe
 */
function __autoload( $class_name )
{
    require_once('Controllers/' . $class_name . '/' . $class_name . '.php' );
}

// charger le registry
require_once('Registry/registry.class.php');
$registry = Registry::singleton();

// stocker les objets principaux
$registry->storeCoreObjects();

// accès à la base de données
$registry->getObject('db')->newConnection('localhost', 'root', '', 'framework');

// charger le template principal
$registry->getObject('template')->buildFromTemplates('main.tpl.php');

// requête à la table users
$cache = $registry->getObject('db')->cacheQuery('SELECT * FROM users');

// associer la balise users aux résultats SQL
$registry->getObject('template')->getPage()->addTag('users', array('SQL', $cache) );

// définir le titre de la page
$registry->getObject('template')->getPage()->setTitle('Nos utilisateurs');

// parser et afficher la page
$registry->getObject('template')->parseOutput();
print $registry->getObject('template')->getPage()->getContent();

// afficher le nom du framework pour vérification
print $registry->getFrameworkName();

exit();

?>
</pre>

<p>Si tout fonctionne et que la base contient des utilisateurs, vous devriez voir un résultat similaire :</p>

<p><img alt="Nos utilisateurs" src="/sites/default/files/inline-images/7511265.png" /></p>

<p>En cas d'erreur, vous pouvez consulter le code sur GitHub. Voici quelques corrections que j'ai dû faire :</p>

<p><strong>Renommer la classe database en db dans <code>Registry/objects/db.class.php</code> :</strong></p>

<pre class="1;">
-class database {
+class db {
</pre>

<p><strong>Définir les méthodes statiques dans <code>Registry/registry.class.php</code> :</strong></p>

<pre class="1;">
-    public function storeObject( $object, $key )
+    public static function storeObject( $object, $key )
-        require_once('objects/' . $object . '.class.php');
+        require_once('Registry/objects/' . $object . '.class.php');
-        self::$objects[ $key ] = new $object( self::$instance );
+        self::$objects[ $key ] = new $object( self::$instance );

-    public function storeCoreObjects()
+    public function storeCoreObjects()
-      $this->storeObject('database', 'db' );
+      $this->storeObject('db', 'db' );
       $this->storeObject('template', 'template' );
-    }
-
-    public function getObject( $key )
+    }

-    public function getObject( $key )
+    public static function getObject( $key )
     {
         if( is_object ( self::$objects[ $key ] ) )
         {
</pre>

<p>Il a également fallu créer un contrôleur db (Controllers/db/db.php) et corriger une erreur dans le templateur <code>Registry/objects/template.class.php</code> :</p>

<pre class="1;">
-  public function parseTitle()
-  {
-    $newContent = str_replace('<title>', '<title>'. $this->$page->getTitle(), $this->page->getContent() );
-    $this->page->setContent( $newContent );
-  }
+  public function parseTitle()
+  {
+    $newContent = str_replace('<title>', '<title>'. $this->page->getTitle(), $this->page->getContent() );
+    $this->page->setContent( $newContent );
+  }
</pre>